Skip to main content

adk_server/a2a/
executor.rs

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    /// Optional interceptor chain for A2A request/response middleware.
20    ///
21    /// When set, the chain's `run_before` is called before processing a request,
22    /// and `run_after` is called after the executor produces a response.
23    #[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    /// Runs `message` in the session named by `context_id` for an unauthenticated caller.
37    ///
38    /// The session belongs to the synthetic user `A2A_USER_{context_id}`. Use
39    /// [`execute_for_user`](Self::execute_for_user) when the request carries an
40    /// authenticated principal, so a caller who knows another caller's `contextId`
41    /// cannot reach that caller's session.
42    ///
43    /// # Errors
44    ///
45    /// Returns an error when the session cannot be prepared, the message cannot be
46    /// converted, or the runner fails to start.
47    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    /// Runs `message` in the session named by `context_id`, owned by `user_id`.
57    ///
58    /// `user_id` is the authenticated principal. When it is `None` the session
59    /// belongs to the synthetic user `A2A_USER_{context_id}`, which is only safe
60    /// when no authentication is configured.
61    ///
62    /// # Example
63    ///
64    /// ```rust,ignore
65    /// let events = executor
66    ///     .execute_for_user(Some("alice"), "ctx-1", "task-1", &message)
67    ///     .await?;
68    /// ```
69    ///
70    /// # Errors
71    ///
72    /// Returns an error when the session cannot be prepared, the message cannot be
73    /// converted, or the runner fails to start.
74    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        // --- Interceptor: before delegation ---
82        #[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                        // Return the short-circuited response as a completed task
118                        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        // Prepare session
146        self.prepare_session(&meta.user_id, &meta.session_id).await?;
147
148        // Convert message to event
149        let invocation_id = uuid::Uuid::new_v4().to_string();
150        let event = message_to_event(message, invocation_id)?;
151
152        // Create runner
153        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        // Create processor
187        let mut processor =
188            EventProcessor::new(context_id.to_string(), task_id.to_string(), meta.clone());
189
190        let mut results = vec![];
191
192        // Send submitted event
193        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        // Send working event
201        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        // Run agent
209        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        // Process events
223        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                    // Send failed event
242                    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        // Send terminal events
267        for terminal_event in processor.make_terminal_events() {
268            results.push(UpdateEvent::TaskStatusUpdate(terminal_event));
269        }
270
271        // --- Interceptor: after delegation ---
272        #[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        // Try to get existing session
300        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        // Create new session
315        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}