1use crate::a2a::{
2 Message, TaskState, TaskStatus, TaskStatusUpdateEvent, UpdateEvent, events::message_to_event,
3 metadata::to_invocation_meta, processor::EventProcessor,
4};
5use adk_core::{Result, SessionId, UserId};
6use adk_runner::{Runner, RunnerConfig};
7use adk_session::{CreateRequest, GetRequest};
8use futures::StreamExt;
9use std::sync::Arc;
10use tokio_util::sync::CancellationToken;
11
12#[cfg(feature = "a2a-interceptors")]
13use crate::a2a::interceptor::{A2aDelegationContext, InterceptorChain, InterceptorDecision};
14
15pub struct ExecutorConfig {
16 pub app_name: String,
17 pub runner_config: Arc<RunnerConfig>,
18 pub cancellation_token: Option<CancellationToken>,
19 #[cfg(feature = "a2a-interceptors")]
24 pub interceptor_chain: Option<Arc<InterceptorChain>>,
25}
26
27pub struct Executor {
28 config: ExecutorConfig,
29}
30
31impl Executor {
32 pub fn new(config: ExecutorConfig) -> Self {
33 Self { config }
34 }
35
36 pub async fn execute(
48 &self,
49 context_id: &str,
50 task_id: &str,
51 message: &Message,
52 ) -> Result<Vec<UpdateEvent>> {
53 self.execute_for_user(None, context_id, task_id, message).await
54 }
55
56 pub async fn execute_for_user(
75 &self,
76 user_id: Option<&str>,
77 context_id: &str,
78 task_id: &str,
79 message: &Message,
80 ) -> Result<Vec<UpdateEvent>> {
81 #[cfg(feature = "a2a-interceptors")]
83 let interceptor_ctx = {
84 if let Some(chain) = &self.config.interceptor_chain {
85 let params = serde_json::to_value(message).unwrap_or(serde_json::Value::Null);
86
87 let metadata_map = message
88 .metadata
89 .as_ref()
90 .map(|m| {
91 m.iter()
92 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
93 .collect()
94 })
95 .unwrap_or_default();
96
97 let mut ctx = A2aDelegationContext {
98 method: "message/send".to_string(),
99 params,
100 caller_id: message
101 .metadata
102 .as_ref()
103 .and_then(|m| m.get("caller_id"))
104 .and_then(|v| v.as_str())
105 .map(String::from),
106 metadata: metadata_map,
107 };
108
109 let decision = chain
110 .run_before(&mut ctx)
111 .await
112 .map_err(|e| adk_core::AdkError::agent(e.to_string()))?;
113
114 match decision {
115 InterceptorDecision::Continue => {}
116 InterceptorDecision::ShortCircuit(response) => {
117 let results = vec![UpdateEvent::TaskStatusUpdate(TaskStatusUpdateEvent {
119 task_id: task_id.to_string(),
120 context_id: Some(context_id.to_string()),
121 status: TaskStatus {
122 state: TaskState::Completed,
123 message: response.as_str().map(String::from),
124 },
125 final_update: true,
126 })];
127 return Ok(results);
128 }
129 InterceptorDecision::Reject { code, message: msg } => {
130 return Err(adk_core::AdkError::agent(format!(
131 "A2A request rejected (code {code}): {msg}"
132 )));
133 }
134 }
135
136 Some(ctx)
137 } else {
138 None
139 }
140 };
141
142 let meta = to_invocation_meta(&self.config.app_name, context_id, user_id);
143 let cancellation_token = self.config.cancellation_token.clone();
144
145 self.prepare_session(&meta.user_id, &meta.session_id).await?;
147
148 let invocation_id = uuid::Uuid::new_v4().to_string();
150 let event = message_to_event(message, invocation_id)?;
151
152 let mut runner_builder = Runner::builder()
154 .app_name(self.config.runner_config.app_name.clone())
155 .agent(self.config.runner_config.agent.clone())
156 .session_service(self.config.runner_config.session_service.clone());
157 if let Some(ref artifact_service) = self.config.runner_config.artifact_service {
158 runner_builder = runner_builder.artifact_service(artifact_service.clone());
159 }
160 if let Some(ref memory_service) = self.config.runner_config.memory_service {
161 runner_builder = runner_builder.memory_service(memory_service.clone());
162 }
163 if let Some(ref plugin_manager) = self.config.runner_config.plugin_manager {
164 runner_builder = runner_builder.plugin_manager(plugin_manager.clone());
165 }
166 if let Some(ref run_config) = self.config.runner_config.run_config {
167 runner_builder = runner_builder.run_config(run_config.clone());
168 }
169 if let Some(ref compaction_config) = self.config.runner_config.compaction_config {
170 runner_builder = runner_builder.compaction_config(compaction_config.clone());
171 }
172 if let Some(ref context_cache_config) = self.config.runner_config.context_cache_config {
173 runner_builder = runner_builder.context_cache_config(context_cache_config.clone());
174 }
175 if let Some(ref cache_capable) = self.config.runner_config.cache_capable {
176 runner_builder = runner_builder.cache_capable(cache_capable.clone());
177 }
178 if let Some(ref request_context) = self.config.runner_config.request_context {
179 runner_builder = runner_builder.request_context(request_context.clone());
180 }
181 if let Some(cancellation_token) = cancellation_token.clone() {
182 runner_builder = runner_builder.cancellation_token(cancellation_token);
183 }
184 let runner = runner_builder.build()?;
185
186 let mut processor =
188 EventProcessor::new(context_id.to_string(), task_id.to_string(), meta.clone());
189
190 let mut results = vec![];
191
192 results.push(UpdateEvent::TaskStatusUpdate(TaskStatusUpdateEvent {
194 task_id: task_id.to_string(),
195 context_id: Some(context_id.to_string()),
196 status: TaskStatus { state: TaskState::Submitted, message: None },
197 final_update: false,
198 }));
199
200 results.push(UpdateEvent::TaskStatusUpdate(TaskStatusUpdateEvent {
202 task_id: task_id.to_string(),
203 context_id: Some(context_id.to_string()),
204 status: TaskStatus { state: TaskState::Working, message: None },
205 final_update: false,
206 }));
207
208 let content = event
210 .llm_response
211 .content
212 .ok_or_else(|| adk_core::AdkError::agent("Event has no content"))?;
213
214 let mut event_stream = runner
215 .run(
216 UserId::new(meta.user_id.clone())?,
217 SessionId::new(meta.session_id.clone())?,
218 content,
219 )
220 .await?;
221
222 while let Some(result) = event_stream.next().await {
224 if cancellation_token.as_ref().is_some_and(CancellationToken::is_cancelled) {
225 results.push(UpdateEvent::TaskStatusUpdate(TaskStatusUpdateEvent {
226 task_id: task_id.to_string(),
227 context_id: Some(context_id.to_string()),
228 status: TaskStatus { state: TaskState::Canceled, message: None },
229 final_update: true,
230 }));
231 return Ok(results);
232 }
233
234 match result {
235 Ok(adk_event) => {
236 if let Some(artifact_event) = processor.process(&adk_event)? {
237 results.push(UpdateEvent::TaskArtifactUpdate(artifact_event));
238 }
239 }
240 Err(e) => {
241 results.push(UpdateEvent::TaskStatusUpdate(TaskStatusUpdateEvent {
243 task_id: task_id.to_string(),
244 context_id: Some(context_id.to_string()),
245 status: TaskStatus {
246 state: TaskState::Failed,
247 message: Some(e.to_string()),
248 },
249 final_update: true,
250 }));
251 return Ok(results);
252 }
253 }
254 }
255
256 if cancellation_token.as_ref().is_some_and(CancellationToken::is_cancelled) {
257 results.push(UpdateEvent::TaskStatusUpdate(TaskStatusUpdateEvent {
258 task_id: task_id.to_string(),
259 context_id: Some(context_id.to_string()),
260 status: TaskStatus { state: TaskState::Canceled, message: None },
261 final_update: true,
262 }));
263 return Ok(results);
264 }
265
266 for terminal_event in processor.make_terminal_events() {
268 results.push(UpdateEvent::TaskStatusUpdate(terminal_event));
269 }
270
271 #[cfg(feature = "a2a-interceptors")]
273 if let Some(chain) = &self.config.interceptor_chain
274 && let Some(ctx) = &interceptor_ctx
275 {
276 let mut response_value =
277 serde_json::to_value(&results).unwrap_or(serde_json::Value::Null);
278 chain
279 .run_after(ctx, &mut response_value)
280 .await
281 .map_err(|e| adk_core::AdkError::agent(e.to_string()))?;
282 }
283
284 Ok(results)
285 }
286
287 pub async fn cancel(&self, context_id: &str, task_id: &str) -> Result<TaskStatusUpdateEvent> {
288 Ok(TaskStatusUpdateEvent {
289 task_id: task_id.to_string(),
290 context_id: Some(context_id.to_string()),
291 status: TaskStatus { state: TaskState::Canceled, message: None },
292 final_update: true,
293 })
294 }
295
296 async fn prepare_session(&self, user_id: &str, session_id: &str) -> Result<()> {
297 let session_service = &self.config.runner_config.session_service;
298
299 let get_result = session_service
301 .get(GetRequest {
302 app_name: self.config.app_name.clone(),
303 user_id: user_id.to_string(),
304 session_id: session_id.to_string(),
305 num_recent_events: None,
306 after: None,
307 })
308 .await;
309
310 if get_result.is_ok() {
311 return Ok(());
312 }
313
314 session_service
316 .create(CreateRequest {
317 app_name: self.config.app_name.clone(),
318 user_id: user_id.to_string(),
319 session_id: Some(session_id.to_string()),
320 state: std::collections::HashMap::new(),
321 })
322 .await?;
323
324 Ok(())
325 }
326}