1use adk_core::ToolConfirmationRequest;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub enum Interrupt {
10 Before(String),
12 After(String),
14 Dynamic {
16 message: String,
18 data: Option<Value>,
20 },
21}
22
23impl std::fmt::Display for Interrupt {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 match self {
26 Self::Before(node) => write!(f, "Interrupt before '{}'", node),
27 Self::After(node) => write!(f, "Interrupt after '{}'", node),
28 Self::Dynamic { message, .. } => write!(f, "Dynamic interrupt: {}", message),
29 }
30 }
31}
32
33pub fn interrupt(message: &str) -> Interrupt {
35 Interrupt::Dynamic { message: message.to_string(), data: None }
36}
37
38pub fn interrupt_with_data(message: &str, data: Value) -> Interrupt {
40 Interrupt::Dynamic { message: message.to_string(), data: Some(data) }
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
44struct PendingGraphToolConfirmation {
45 node: String,
46 request: ToolConfirmationRequest,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
55#[serde(rename_all = "camelCase")]
56pub struct GraphToolConfirmationPause {
57 pub node: String,
59 pub request: ToolConfirmationRequest,
61 pub thread_id: String,
63 pub checkpoint_id: String,
65}
66
67impl GraphToolConfirmationPause {
68 pub const KIND: &str = "tool_confirmation";
70
71 pub fn from_stream_event(event: &crate::stream::StreamEvent) -> Option<Self> {
73 let crate::stream::StreamEvent::Custom { event_type, data, .. } = event else {
74 return None;
75 };
76 (event_type == Self::KIND).then(|| serde_json::from_value(data.clone()).ok()).flatten()
77 }
78
79 pub fn from_interrupted_execution(
81 interrupted: &crate::error::InterruptedExecution,
82 ) -> Option<Self> {
83 let (node, request) = pending_from_interrupt(&interrupted.interrupt)?;
84 Some(Self {
85 node,
86 request,
87 thread_id: interrupted.thread_id.clone(),
88 checkpoint_id: interrupted.checkpoint_id.clone(),
89 })
90 }
91
92 pub(crate) fn pending_interrupt(node: String, request: ToolConfirmationRequest) -> Interrupt {
93 Interrupt::Dynamic {
94 message: Self::KIND.to_string(),
95 data: Some(Self::pending_data(node, request)),
96 }
97 }
98
99 pub(crate) fn pending_data(node: String, request: ToolConfirmationRequest) -> Value {
100 serde_json::to_value(PendingGraphToolConfirmation { node, request }).unwrap_or(Value::Null)
101 }
102}
103
104fn pending_from_interrupt(interrupt: &Interrupt) -> Option<(String, ToolConfirmationRequest)> {
105 let Interrupt::Dynamic { message, data } = interrupt else { return None };
106 if message != GraphToolConfirmationPause::KIND {
107 return None;
108 }
109 let pending: PendingGraphToolConfirmation = serde_json::from_value(data.clone()?).ok()?;
110 Some((pending.node, pending.request))
111}
112
113pub const INTERRUPT_METADATA_KEY: &str = "adk.graph.interrupt";
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct GraphInterruptPayload {
142 pub kind: String,
144 #[serde(default, skip_serializing_if = "Option::is_none")]
147 pub node: Option<String>,
148 #[serde(default, skip_serializing_if = "Option::is_none")]
150 pub message: Option<String>,
151 #[serde(default, skip_serializing_if = "Option::is_none")]
153 pub data: Option<Value>,
154 pub thread_id: String,
156 pub checkpoint_id: String,
158}
159
160impl GraphInterruptPayload {
161 pub fn new(interrupt: &Interrupt, thread_id: &str, checkpoint_id: &str) -> Self {
163 let (kind, node, message, data) = match interrupt {
164 Interrupt::Before(node) => ("before", Some(node.clone()), None, None),
165 Interrupt::After(node) => ("after", Some(node.clone()), None, None),
166 Interrupt::Dynamic { message, data } => {
167 ("dynamic", None, Some(message.clone()), data.clone())
168 }
169 };
170 Self {
171 kind: kind.to_string(),
172 node,
173 message,
174 data,
175 thread_id: thread_id.to_string(),
176 checkpoint_id: checkpoint_id.to_string(),
177 }
178 }
179
180 pub fn from_event(event: &adk_core::Event) -> Option<Self> {
183 let raw = event.provider_metadata.get(INTERRUPT_METADATA_KEY)?;
184 serde_json::from_str(raw).ok()
185 }
186
187 pub fn from_tool_confirmation_pause(pause: GraphToolConfirmationPause) -> Self {
189 let thread_id = pause.thread_id.clone();
190 let checkpoint_id = pause.checkpoint_id.clone();
191 Self {
192 kind: GraphToolConfirmationPause::KIND.to_string(),
193 node: Some(pause.node.clone()),
194 message: None,
195 data: serde_json::to_value(pause).ok(),
196 thread_id,
197 checkpoint_id,
198 }
199 }
200
201 pub fn tool_confirmation(&self) -> Option<GraphToolConfirmationPause> {
203 if self.kind != GraphToolConfirmationPause::KIND {
204 return None;
205 }
206 serde_json::from_value(self.data.clone()?).ok()
207 }
208
209 pub fn to_metadata_value(&self) -> String {
211 serde_json::to_string(self).unwrap_or_default()
212 }
213}