Skip to main content

a2a_server/
handler.rs

1// Copyright AGNTCY Contributors (https://github.com/agntcy)
2// SPDX-License-Identifier: Apache-2.0
3use a2a::*;
4use async_trait::async_trait;
5use futures::{StreamExt, stream::BoxStream};
6use std::{collections::HashMap, sync::Arc};
7use tokio::sync::{RwLock, broadcast};
8
9use crate::middleware::ServiceParams;
10
11const EXECUTION_BUFFER_CAPACITY: usize = 32;
12
13#[derive(Clone)]
14struct ExecutionEvent {
15    sequence: u64,
16    result: Result<StreamResponse, A2AError>,
17}
18
19#[derive(Default)]
20struct ActiveExecutionState {
21    sequence: u64,
22    snapshot_task: Option<Task>,
23}
24
25struct ActiveExecution {
26    sender: broadcast::Sender<ExecutionEvent>,
27    state: RwLock<ActiveExecutionState>,
28}
29
30impl ActiveExecution {
31    fn new(task: Task) -> Arc<Self> {
32        let (sender, _) = broadcast::channel(EXECUTION_BUFFER_CAPACITY);
33        Arc::new(Self {
34            sender,
35            state: RwLock::new(ActiveExecutionState {
36                sequence: 0,
37                snapshot_task: Some(task),
38            }),
39        })
40    }
41
42    fn subscribe(&self) -> broadcast::Receiver<ExecutionEvent> {
43        self.sender.subscribe()
44    }
45
46    async fn resubscribe(&self) -> (broadcast::Receiver<ExecutionEvent>, Option<Task>, u64) {
47        let receiver = self.sender.subscribe();
48        let state = self.state.read().await;
49        (receiver, state.snapshot_task.clone(), state.sequence)
50    }
51
52    async fn publish(&self, result: Result<StreamResponse, A2AError>, snapshot_task: Option<Task>) {
53        let event = {
54            let mut state = self.state.write().await;
55            if let Some(task) = snapshot_task {
56                state.snapshot_task = Some(task);
57            }
58            state.sequence += 1;
59            ExecutionEvent {
60                sequence: state.sequence,
61                result,
62            }
63        };
64        let _ = self.sender.send(event);
65    }
66
67    async fn snapshot_is_terminal(&self) -> bool {
68        let state = self.state.read().await;
69        state
70            .snapshot_task
71            .as_ref()
72            .is_some_and(|task| task.status.state.is_terminal())
73    }
74}
75
76#[derive(Default)]
77struct ExecutionManager {
78    executions: RwLock<HashMap<TaskId, Arc<ActiveExecution>>>,
79}
80
81impl ExecutionManager {
82    async fn start(&self, task: Task) -> Result<Arc<ActiveExecution>, A2AError> {
83        let mut executions = self.executions.write().await;
84        if executions.contains_key(&task.id) {
85            return Err(A2AError::invalid_request(format!(
86                "task execution is already in progress: {}",
87                task.id
88            )));
89        }
90
91        let active = ActiveExecution::new(task.clone());
92        executions.insert(task.id.clone(), active.clone());
93        Ok(active)
94    }
95
96    async fn get(&self, task_id: &str) -> Option<Arc<ActiveExecution>> {
97        let executions = self.executions.read().await;
98        executions.get(task_id).cloned()
99    }
100
101    async fn resubscribe(
102        &self,
103        task_id: &str,
104    ) -> Option<(broadcast::Receiver<ExecutionEvent>, Option<Task>, u64)> {
105        let active = self.get(task_id).await?;
106        let (receiver, snapshot_task, sequence) = active.resubscribe().await;
107        Some((receiver, snapshot_task, sequence))
108    }
109
110    async fn finish(&self, task_id: &str, active: &Arc<ActiveExecution>) {
111        let mut executions = self.executions.write().await;
112        let should_remove = executions
113            .get(task_id)
114            .is_some_and(|current| Arc::ptr_eq(current, active));
115        if should_remove {
116            executions.remove(task_id);
117        }
118    }
119}
120
121struct ExecutionRuntime {
122    executor: Arc<dyn crate::AgentExecutor>,
123    task_store: Arc<dyn crate::TaskStore>,
124    push_config_store: Option<Arc<dyn crate::PushConfigStore>>,
125    push_sender: Option<Arc<crate::HttpPushSender>>,
126    execution_manager: Arc<ExecutionManager>,
127}
128
129struct SubscriptionState {
130    receiver: broadcast::Receiver<ExecutionEvent>,
131    snapshot_task: Option<Task>,
132    min_sequence: u64,
133    done: bool,
134}
135
136fn is_terminal_event(event: &StreamResponse) -> bool {
137    match event {
138        StreamResponse::Task(task) => task.status.state.is_terminal(),
139        StreamResponse::StatusUpdate(update) => update.status.state.is_terminal(),
140        _ => false,
141    }
142}
143
144fn task_id_for_event(event: &StreamResponse) -> Option<&str> {
145    match event {
146        StreamResponse::Task(task) => Some(&task.id),
147        StreamResponse::StatusUpdate(update) => Some(&update.task_id),
148        StreamResponse::ArtifactUpdate(update) => Some(&update.task_id),
149        StreamResponse::Message(_) => None,
150    }
151}
152
153fn should_interrupt_non_streaming(
154    req: &SendMessageRequest,
155    event: &StreamResponse,
156) -> Option<TaskId> {
157    if req
158        .configuration
159        .as_ref()
160        .and_then(|config| config.return_immediately)
161        .unwrap_or(false)
162    {
163        return match event {
164            StreamResponse::Message(_) => None,
165            _ => task_id_for_event(event).map(ToOwned::to_owned),
166        };
167    }
168
169    match event {
170        StreamResponse::Task(task) if task.status.state == TaskState::AuthRequired => {
171            Some(task.id.clone())
172        }
173        StreamResponse::StatusUpdate(update) if update.status.state == TaskState::AuthRequired => {
174            Some(update.task_id.clone())
175        }
176        _ => None,
177    }
178}
179
180async fn send_push_notifications(
181    task_id: &str,
182    push_config_store: Option<&dyn crate::PushConfigStore>,
183    push_sender: Option<&crate::HttpPushSender>,
184    event: &StreamResponse,
185) -> Result<(), A2AError> {
186    let Some(push_config_store) = push_config_store else {
187        return Ok(());
188    };
189    let Some(push_sender) = push_sender else {
190        return Ok(());
191    };
192
193    let configs = push_config_store.list(task_id).await?;
194    for config in configs {
195        push_sender.send_push(&config, event.clone()).await?;
196    }
197
198    Ok(())
199}
200
201async fn save_task(task_store: &dyn crate::TaskStore, task: Task) -> Result<Task, A2AError> {
202    match task_store.update(task.clone()).await {
203        Ok(_) => Ok(task),
204        Err(error) if error.code == error_code::TASK_NOT_FOUND => {
205            task_store.create(task.clone()).await?;
206            Ok(task)
207        }
208        Err(error) => Err(error),
209    }
210}
211
212async fn apply_event_to_task(
213    task_store: &dyn crate::TaskStore,
214    current_task: Option<Task>,
215    event: &StreamResponse,
216) -> Result<Option<Task>, A2AError> {
217    match event {
218        StreamResponse::Task(task) => save_task(task_store, task.clone()).await.map(Some),
219        StreamResponse::StatusUpdate(update) => {
220            let mut task = current_task
221                .or(task_store.get(&update.task_id).await?)
222                .ok_or_else(|| A2AError::task_not_found(&update.task_id))?;
223            task.status = update.status.clone();
224            save_task(task_store, task).await.map(Some)
225        }
226        StreamResponse::ArtifactUpdate(update) => {
227            let mut task = current_task
228                .or(task_store.get(&update.task_id).await?)
229                .ok_or_else(|| A2AError::task_not_found(&update.task_id))?;
230            let artifacts = task.artifacts.get_or_insert_with(Vec::new);
231            artifacts.push(update.artifact.clone());
232            save_task(task_store, task).await.map(Some)
233        }
234        StreamResponse::Message(_) => Ok(current_task),
235    }
236}
237
238fn subscription_stream(
239    receiver: broadcast::Receiver<ExecutionEvent>,
240    snapshot_task: Option<Task>,
241    min_sequence: u64,
242) -> BoxStream<'static, Result<StreamResponse, A2AError>> {
243    Box::pin(futures::stream::unfold(
244        SubscriptionState {
245            receiver,
246            snapshot_task,
247            min_sequence,
248            done: false,
249        },
250        |mut state| async move {
251            if state.done {
252                return None;
253            }
254
255            if let Some(task) = state.snapshot_task.take() {
256                if task.status.state.is_terminal() {
257                    state.done = true;
258                }
259                return Some((Ok(StreamResponse::Task(task)), state));
260            }
261
262            loop {
263                match state.receiver.recv().await {
264                    Ok(event) if event.sequence <= state.min_sequence => continue,
265                    Ok(event) => {
266                        state.min_sequence = event.sequence;
267                        match &event.result {
268                            Ok(item) if is_terminal_event(item) => state.done = true,
269                            Err(_) => state.done = true,
270                            _ => {}
271                        }
272                        return Some((event.result.clone(), state));
273                    }
274                    Err(broadcast::error::RecvError::Lagged(_)) => {
275                        state.done = true;
276                        return Some((
277                            Err(A2AError::internal(
278                                "subscription fell behind active execution",
279                            )),
280                            state,
281                        ));
282                    }
283                    Err(broadcast::error::RecvError::Closed) => return None,
284                }
285            }
286        },
287    ))
288}
289
290async fn drive_execution(
291    runtime: ExecutionRuntime,
292    task_id: TaskId,
293    active: Arc<ActiveExecution>,
294    exec_ctx: crate::ExecutorContext,
295) {
296    let mut current_task = {
297        let state = active.state.read().await;
298        state.snapshot_task.clone()
299    };
300    let mut stream = runtime.executor.execute(exec_ctx);
301
302    while let Some(result) = stream.next().await {
303        if active.snapshot_is_terminal().await {
304            break;
305        }
306
307        match result {
308            Ok(event) => {
309                match apply_event_to_task(runtime.task_store.as_ref(), current_task.clone(), &event)
310                    .await
311                {
312                    Ok(updated_task) => {
313                        current_task = updated_task.clone();
314                        if let Err(error) = send_push_notifications(
315                            &task_id,
316                            runtime.push_config_store.as_deref(),
317                            runtime.push_sender.as_deref(),
318                            &event,
319                        )
320                        .await
321                        {
322                            active.publish(Err(error), None).await;
323                            break;
324                        }
325                        active.publish(Ok(event.clone()), updated_task).await;
326                        if is_terminal_event(&event) {
327                            break;
328                        }
329                    }
330                    Err(error) => {
331                        active.publish(Err(error), None).await;
332                        break;
333                    }
334                }
335            }
336            Err(error) => {
337                active.publish(Err(error), None).await;
338                break;
339            }
340        }
341    }
342
343    runtime.execution_manager.finish(&task_id, &active).await;
344}
345
346/// Transport-agnostic request handler interface.
347///
348/// This is the core interface consumed by all protocol bindings
349/// (JSON-RPC, REST, gRPC). The [`DefaultRequestHandler`] provides a
350/// standard implementation that orchestrates task lifecycle, storage,
351/// and executor dispatch.
352#[async_trait]
353pub trait RequestHandler: Send + Sync + 'static {
354    async fn send_message(
355        &self,
356        params: &ServiceParams,
357        req: SendMessageRequest,
358    ) -> Result<SendMessageResponse, A2AError>;
359
360    async fn send_streaming_message(
361        &self,
362        params: &ServiceParams,
363        req: SendMessageRequest,
364    ) -> Result<BoxStream<'static, Result<StreamResponse, A2AError>>, A2AError>;
365
366    async fn get_task(&self, params: &ServiceParams, req: GetTaskRequest)
367    -> Result<Task, A2AError>;
368
369    async fn list_tasks(
370        &self,
371        params: &ServiceParams,
372        req: ListTasksRequest,
373    ) -> Result<ListTasksResponse, A2AError>;
374
375    async fn cancel_task(
376        &self,
377        params: &ServiceParams,
378        req: CancelTaskRequest,
379    ) -> Result<Task, A2AError>;
380
381    async fn subscribe_to_task(
382        &self,
383        params: &ServiceParams,
384        req: SubscribeToTaskRequest,
385    ) -> Result<BoxStream<'static, Result<StreamResponse, A2AError>>, A2AError>;
386
387    async fn create_push_config(
388        &self,
389        params: &ServiceParams,
390        req: CreateTaskPushNotificationConfigRequest,
391    ) -> Result<TaskPushNotificationConfig, A2AError>;
392
393    async fn get_push_config(
394        &self,
395        params: &ServiceParams,
396        req: GetTaskPushNotificationConfigRequest,
397    ) -> Result<TaskPushNotificationConfig, A2AError>;
398
399    async fn list_push_configs(
400        &self,
401        params: &ServiceParams,
402        req: ListTaskPushNotificationConfigsRequest,
403    ) -> Result<ListTaskPushNotificationConfigsResponse, A2AError>;
404
405    async fn delete_push_config(
406        &self,
407        params: &ServiceParams,
408        req: DeleteTaskPushNotificationConfigRequest,
409    ) -> Result<(), A2AError>;
410
411    async fn get_extended_agent_card(
412        &self,
413        params: &ServiceParams,
414        req: GetExtendedAgentCardRequest,
415    ) -> Result<AgentCard, A2AError>;
416}
417
418/// Default implementation of [`RequestHandler`] that orchestrates
419/// task lifecycle management, storage, and executor dispatch.
420pub struct DefaultRequestHandler {
421    executor: Arc<dyn crate::AgentExecutor>,
422    task_store: Arc<dyn crate::TaskStore>,
423    execution_manager: Arc<ExecutionManager>,
424    push_config_store: Option<Arc<dyn crate::PushConfigStore>>,
425    push_sender: Option<Arc<crate::HttpPushSender>>,
426    capabilities: AgentCapabilities,
427}
428
429impl DefaultRequestHandler {
430    pub fn new(executor: impl crate::AgentExecutor, task_store: impl crate::TaskStore) -> Self {
431        DefaultRequestHandler {
432            executor: Arc::new(executor),
433            task_store: Arc::new(task_store),
434            execution_manager: Arc::new(ExecutionManager::default()),
435            push_config_store: None,
436            push_sender: None,
437            capabilities: AgentCapabilities::default(),
438        }
439    }
440
441    pub fn with_push_config_store(
442        mut self,
443        push_config_store: impl crate::PushConfigStore,
444    ) -> Self {
445        self.push_sender = Some(Arc::new(crate::HttpPushSender::new(None)));
446        self.push_config_store = Some(Arc::new(push_config_store));
447        self.capabilities.push_notifications = Some(true);
448        self
449    }
450
451    pub fn with_push_notifications(
452        mut self,
453        push_config_store: impl crate::PushConfigStore,
454        push_sender: crate::HttpPushSender,
455    ) -> Self {
456        self.push_config_store = Some(Arc::new(push_config_store));
457        self.push_sender = Some(Arc::new(push_sender));
458        self.capabilities.push_notifications = Some(true);
459        self
460    }
461
462    pub fn with_capabilities(mut self, capabilities: AgentCapabilities) -> Self {
463        self.capabilities = capabilities;
464        self
465    }
466
467    fn push_config_store(&self) -> Result<&dyn crate::PushConfigStore, A2AError> {
468        self.push_config_store
469            .as_deref()
470            .ok_or_else(A2AError::push_notification_not_supported)
471    }
472
473    async fn load_task(&self, task_id: &str) -> Result<Task, A2AError> {
474        self.task_store
475            .get(task_id)
476            .await?
477            .ok_or_else(|| A2AError::task_not_found(task_id))
478    }
479
480    async fn prepare_task_for_execution(
481        &self,
482        req: &SendMessageRequest,
483    ) -> Result<(Task, Option<Task>, String), A2AError> {
484        let task_id = req.message.task_id.clone().unwrap_or_else(new_task_id);
485        let stored = self.task_store.get(&task_id).await?;
486        let context_id = stored
487            .as_ref()
488            .map(|task| task.context_id.clone())
489            .or_else(|| req.message.context_id.clone())
490            .unwrap_or_else(new_context_id);
491
492        let task = if let Some(existing) = stored.clone() {
493            existing
494        } else {
495            let task = Task {
496                id: task_id,
497                context_id: context_id.clone(),
498                status: TaskStatus {
499                    state: TaskState::Submitted,
500                    message: None,
501                    timestamp: Some(chrono::Utc::now()),
502                },
503                artifacts: None,
504                history: Some(vec![req.message.clone()]),
505                metadata: None,
506            };
507            self.task_store.create(task.clone()).await?;
508            task
509        };
510
511        Ok((task, stored, context_id))
512    }
513
514    async fn save_request_push_config(
515        &self,
516        task_id: &str,
517        req: &SendMessageRequest,
518    ) -> Result<(), A2AError> {
519        let Some(config) = req
520            .configuration
521            .as_ref()
522            .and_then(|configuration| configuration.push_notification_config.clone())
523        else {
524            return Ok(());
525        };
526
527        self.push_config_store()?.save(task_id, config).await?;
528        Ok(())
529    }
530
531    async fn start_execution(
532        &self,
533        params: &ServiceParams,
534        req: SendMessageRequest,
535        include_task_snapshot_in_context: bool,
536    ) -> Result<(TaskId, BoxStream<'static, Result<StreamResponse, A2AError>>), A2AError> {
537        let (task, stored_task, context_id) = self.prepare_task_for_execution(&req).await?;
538        let task_id = task.id.clone();
539        self.save_request_push_config(&task_id, &req).await?;
540        let active = self.execution_manager.start(task.clone()).await?;
541        let receiver = active.subscribe();
542
543        let exec_ctx = crate::ExecutorContext {
544            message: Some(req.message),
545            task_id: task_id.clone(),
546            stored_task: if include_task_snapshot_in_context {
547                Some(task.clone())
548            } else {
549                stored_task
550            },
551            context_id,
552            metadata: req.metadata,
553            user: None,
554            service_params: params.clone(),
555            tenant: req.tenant,
556        };
557
558        let runtime = ExecutionRuntime {
559            executor: Arc::clone(&self.executor),
560            task_store: Arc::clone(&self.task_store),
561            push_config_store: self.push_config_store.clone(),
562            push_sender: self.push_sender.clone(),
563            execution_manager: Arc::clone(&self.execution_manager),
564        };
565        let active_execution = active.clone();
566        let execution_task_id = task_id.clone();
567
568        tokio::spawn(async move {
569            drive_execution(runtime, execution_task_id, active_execution, exec_ctx).await;
570        });
571
572        Ok((task_id, subscription_stream(receiver, None, 0)))
573    }
574}
575
576#[async_trait]
577impl RequestHandler for DefaultRequestHandler {
578    async fn send_message(
579        &self,
580        params: &ServiceParams,
581        req: SendMessageRequest,
582    ) -> Result<SendMessageResponse, A2AError> {
583        let (task_id, mut stream) = self.start_execution(params, req.clone(), true).await?;
584        let mut last_event = None;
585
586        while let Some(item) = stream.next().await {
587            let event = item?;
588
589            if let Some(interrupt_task_id) = should_interrupt_non_streaming(&req, &event) {
590                return Ok(SendMessageResponse::Task(
591                    self.load_task(&interrupt_task_id).await?,
592                ));
593            }
594
595            match event {
596                StreamResponse::Message(message) => {
597                    return Ok(SendMessageResponse::Message(message));
598                }
599                other => last_event = Some(other),
600            }
601        }
602
603        match last_event {
604            Some(StreamResponse::Task(task)) => Ok(SendMessageResponse::Task(task)),
605            Some(StreamResponse::StatusUpdate(update)) => Ok(SendMessageResponse::Task(
606                self.load_task(&update.task_id).await?,
607            )),
608            Some(StreamResponse::ArtifactUpdate(update)) => Ok(SendMessageResponse::Task(
609                self.load_task(&update.task_id).await?,
610            )),
611            Some(StreamResponse::Message(message)) => Ok(SendMessageResponse::Message(message)),
612            None => Ok(SendMessageResponse::Task(self.load_task(&task_id).await?)),
613        }
614    }
615
616    async fn send_streaming_message(
617        &self,
618        params: &ServiceParams,
619        req: SendMessageRequest,
620    ) -> Result<BoxStream<'static, Result<StreamResponse, A2AError>>, A2AError> {
621        let (_, stream) = self.start_execution(params, req, false).await?;
622        Ok(stream)
623    }
624
625    async fn get_task(
626        &self,
627        _params: &ServiceParams,
628        req: GetTaskRequest,
629    ) -> Result<Task, A2AError> {
630        self.task_store
631            .get(&req.id)
632            .await?
633            .ok_or_else(|| A2AError::task_not_found(&req.id))
634    }
635
636    async fn list_tasks(
637        &self,
638        _params: &ServiceParams,
639        req: ListTasksRequest,
640    ) -> Result<ListTasksResponse, A2AError> {
641        self.task_store.list(&req).await
642    }
643
644    async fn cancel_task(
645        &self,
646        params: &ServiceParams,
647        req: CancelTaskRequest,
648    ) -> Result<Task, A2AError> {
649        let task = self.load_task(&req.id).await?;
650
651        if task.status.state.is_terminal() {
652            return Err(A2AError::task_not_cancelable(&req.id));
653        }
654
655        let active_execution = self.execution_manager.get(&req.id).await;
656
657        let exec_ctx = crate::ExecutorContext {
658            message: None,
659            task_id: req.id.clone(),
660            stored_task: Some(task.clone()),
661            context_id: task.context_id.clone(),
662            metadata: req.metadata,
663            user: None,
664            service_params: params.clone(),
665            tenant: req.tenant,
666        };
667
668        let mut stream = self.executor.cancel(exec_ctx);
669        let mut current_task = Some(task);
670
671        while let Some(event) = stream.next().await {
672            match event {
673                Ok(event) => {
674                    let updated_task =
675                        apply_event_to_task(self.task_store.as_ref(), current_task.clone(), &event)
676                            .await?;
677                    current_task = updated_task.clone();
678
679                    send_push_notifications(
680                        &req.id,
681                        self.push_config_store.as_deref(),
682                        self.push_sender.as_deref(),
683                        &event,
684                    )
685                    .await?;
686
687                    if let Some(active) = &active_execution {
688                        active.publish(Ok(event.clone()), updated_task).await;
689                    }
690
691                    if is_terminal_event(&event) {
692                        break;
693                    }
694                }
695                Err(error) => {
696                    if let Some(active) = &active_execution {
697                        active.publish(Err(error.clone()), None).await;
698                    }
699                    return Err(error);
700                }
701            }
702        }
703
704        if let Some(active) = active_execution {
705            self.execution_manager.finish(&req.id, &active).await;
706        }
707
708        self.load_task(&req.id).await
709    }
710
711    async fn subscribe_to_task(
712        &self,
713        _params: &ServiceParams,
714        req: SubscribeToTaskRequest,
715    ) -> Result<BoxStream<'static, Result<StreamResponse, A2AError>>, A2AError> {
716        let (receiver, snapshot_task, sequence) = self
717            .execution_manager
718            .resubscribe(&req.id)
719            .await
720            .ok_or_else(|| A2AError::task_not_found(&req.id))?;
721        Ok(subscription_stream(receiver, snapshot_task, sequence))
722    }
723
724    async fn create_push_config(
725        &self,
726        _params: &ServiceParams,
727        req: CreateTaskPushNotificationConfigRequest,
728    ) -> Result<TaskPushNotificationConfig, A2AError> {
729        let saved = self
730            .push_config_store()?
731            .save(&req.task_id, req.config)
732            .await?;
733        Ok(TaskPushNotificationConfig {
734            task_id: req.task_id,
735            config: saved,
736            tenant: req.tenant,
737        })
738    }
739
740    async fn get_push_config(
741        &self,
742        _params: &ServiceParams,
743        req: GetTaskPushNotificationConfigRequest,
744    ) -> Result<TaskPushNotificationConfig, A2AError> {
745        let config = self.push_config_store()?.get(&req.task_id, &req.id).await?;
746        Ok(TaskPushNotificationConfig {
747            task_id: req.task_id,
748            config,
749            tenant: req.tenant,
750        })
751    }
752
753    async fn list_push_configs(
754        &self,
755        _params: &ServiceParams,
756        req: ListTaskPushNotificationConfigsRequest,
757    ) -> Result<ListTaskPushNotificationConfigsResponse, A2AError> {
758        let mut configs = self.push_config_store()?.list(&req.task_id).await?;
759        configs.sort_by(|left, right| left.id.cmp(&right.id));
760
761        let page_size = match req.page_size {
762            Some(size) if size > 0 => size as usize,
763            _ => 50,
764        };
765        let start = req
766            .page_token
767            .as_deref()
768            .and_then(|token| token.parse::<usize>().ok())
769            .unwrap_or(0)
770            .min(configs.len());
771        let end = (start + page_size).min(configs.len());
772        let next_page_token = (end < configs.len()).then(|| end.to_string());
773
774        Ok(ListTaskPushNotificationConfigsResponse {
775            configs: configs[start..end]
776                .iter()
777                .cloned()
778                .map(|config| TaskPushNotificationConfig {
779                    task_id: req.task_id.clone(),
780                    config,
781                    tenant: req.tenant.clone(),
782                })
783                .collect(),
784            next_page_token,
785        })
786    }
787
788    async fn delete_push_config(
789        &self,
790        _params: &ServiceParams,
791        req: DeleteTaskPushNotificationConfigRequest,
792    ) -> Result<(), A2AError> {
793        self.push_config_store()?
794            .delete(&req.task_id, &req.id)
795            .await
796    }
797
798    async fn get_extended_agent_card(
799        &self,
800        _params: &ServiceParams,
801        _req: GetExtendedAgentCardRequest,
802    ) -> Result<AgentCard, A2AError> {
803        Err(A2AError::unsupported_operation(
804            "extended agent card not configured",
805        ))
806    }
807}
808
809#[cfg(test)]
810mod tests {
811    use super::*;
812    use crate::executor::ExecutorContext;
813    use crate::push::InMemoryPushConfigStore;
814    use crate::task_store::InMemoryTaskStore;
815    use axum::{
816        Router,
817        body::Bytes,
818        extract::State,
819        http::{HeaderMap, StatusCode, header},
820        routing::post,
821    };
822    use futures::stream;
823    use std::sync::Arc;
824    use tokio::{
825        net::TcpListener,
826        sync::{Notify, mpsc, oneshot},
827        time::{Duration, timeout},
828    };
829
830    struct EchoExecutor;
831
832    struct PushEventExecutor;
833
834    struct ResumableExecutor {
835        release: Arc<Notify>,
836    }
837
838    #[derive(Debug)]
839    struct CapturedPush {
840        authorization: Option<String>,
841        notification_token: Option<String>,
842        event: StreamResponse,
843    }
844
845    impl crate::AgentExecutor for EchoExecutor {
846        fn execute(
847            &self,
848            ctx: ExecutorContext,
849        ) -> BoxStream<'static, Result<StreamResponse, A2AError>> {
850            let task = Task {
851                id: ctx.task_id.clone(),
852                context_id: ctx.context_id.clone(),
853                status: TaskStatus {
854                    state: TaskState::Completed,
855                    message: ctx.message,
856                    timestamp: None,
857                },
858                artifacts: None,
859                history: None,
860                metadata: None,
861            };
862            Box::pin(stream::once(async move { Ok(StreamResponse::Task(task)) }))
863        }
864
865        fn cancel(
866            &self,
867            ctx: ExecutorContext,
868        ) -> BoxStream<'static, Result<StreamResponse, A2AError>> {
869            let task = Task {
870                id: ctx.task_id.clone(),
871                context_id: ctx.context_id.clone(),
872                status: TaskStatus {
873                    state: TaskState::Canceled,
874                    message: None,
875                    timestamp: None,
876                },
877                artifacts: None,
878                history: None,
879                metadata: None,
880            };
881            Box::pin(stream::once(async move { Ok(StreamResponse::Task(task)) }))
882        }
883    }
884
885    impl crate::AgentExecutor for PushEventExecutor {
886        fn execute(
887            &self,
888            ctx: ExecutorContext,
889        ) -> BoxStream<'static, Result<StreamResponse, A2AError>> {
890            let working = StreamResponse::StatusUpdate(TaskStatusUpdateEvent {
891                task_id: ctx.task_id.clone(),
892                context_id: ctx.context_id.clone(),
893                status: TaskStatus {
894                    state: TaskState::Working,
895                    message: None,
896                    timestamp: None,
897                },
898                metadata: None,
899            });
900            let completed = StreamResponse::Task(Task {
901                id: ctx.task_id.clone(),
902                context_id: ctx.context_id.clone(),
903                status: TaskStatus {
904                    state: TaskState::Completed,
905                    message: ctx.message,
906                    timestamp: None,
907                },
908                artifacts: None,
909                history: ctx.stored_task.and_then(|task| task.history),
910                metadata: None,
911            });
912
913            Box::pin(stream::iter(vec![Ok(working), Ok(completed)]))
914        }
915
916        fn cancel(
917            &self,
918            ctx: ExecutorContext,
919        ) -> BoxStream<'static, Result<StreamResponse, A2AError>> {
920            let task = Task {
921                id: ctx.task_id.clone(),
922                context_id: ctx.context_id.clone(),
923                status: TaskStatus {
924                    state: TaskState::Canceled,
925                    message: None,
926                    timestamp: None,
927                },
928                artifacts: None,
929                history: None,
930                metadata: None,
931            };
932            Box::pin(stream::once(async move { Ok(StreamResponse::Task(task)) }))
933        }
934    }
935
936    impl crate::AgentExecutor for ResumableExecutor {
937        fn execute(
938            &self,
939            ctx: ExecutorContext,
940        ) -> BoxStream<'static, Result<StreamResponse, A2AError>> {
941            let release = self.release.clone();
942            let working_task = Task {
943                id: ctx.task_id.clone(),
944                context_id: ctx.context_id.clone(),
945                status: TaskStatus {
946                    state: TaskState::Working,
947                    message: None,
948                    timestamp: None,
949                },
950                artifacts: None,
951                history: ctx.stored_task.and_then(|task| task.history),
952                metadata: None,
953            };
954            let completed_task = Task {
955                id: working_task.id.clone(),
956                context_id: working_task.context_id.clone(),
957                status: TaskStatus {
958                    state: TaskState::Completed,
959                    message: None,
960                    timestamp: None,
961                },
962                artifacts: None,
963                history: working_task.history.clone(),
964                metadata: None,
965            };
966
967            Box::pin(stream::unfold(
968                (Some(working_task), Some((release, completed_task))),
969                |(working_task, completion)| async move {
970                    if let Some(task) = working_task {
971                        return Some((Ok(StreamResponse::Task(task)), (None, completion)));
972                    }
973
974                    if let Some((release, task)) = completion {
975                        release.notified().await;
976                        return Some((Ok(StreamResponse::Task(task)), (None, None)));
977                    }
978
979                    None
980                },
981            ))
982        }
983
984        fn cancel(
985            &self,
986            ctx: ExecutorContext,
987        ) -> BoxStream<'static, Result<StreamResponse, A2AError>> {
988            let task = Task {
989                id: ctx.task_id.clone(),
990                context_id: ctx.context_id.clone(),
991                status: TaskStatus {
992                    state: TaskState::Canceled,
993                    message: None,
994                    timestamp: None,
995                },
996                artifacts: None,
997                history: None,
998                metadata: None,
999            };
1000            Box::pin(stream::once(async move { Ok(StreamResponse::Task(task)) }))
1001        }
1002    }
1003
1004    fn make_handler() -> DefaultRequestHandler {
1005        DefaultRequestHandler::new(EchoExecutor, InMemoryTaskStore::new())
1006    }
1007
1008    fn make_handler_with_push_configs() -> DefaultRequestHandler {
1009        DefaultRequestHandler::new(EchoExecutor, InMemoryTaskStore::new())
1010            .with_push_config_store(InMemoryPushConfigStore::new())
1011    }
1012
1013    fn make_push_delivery_handler() -> DefaultRequestHandler {
1014        DefaultRequestHandler::new(PushEventExecutor, InMemoryTaskStore::new())
1015            .with_push_config_store(InMemoryPushConfigStore::new())
1016    }
1017
1018    fn make_resumable_handler() -> (DefaultRequestHandler, Arc<Notify>) {
1019        let release = Arc::new(Notify::new());
1020        (
1021            DefaultRequestHandler::new(
1022                ResumableExecutor {
1023                    release: release.clone(),
1024                },
1025                InMemoryTaskStore::new(),
1026            ),
1027            release,
1028        )
1029    }
1030
1031    fn make_message() -> Message {
1032        Message::new(Role::User, vec![Part::text("hello")])
1033    }
1034
1035    async fn capture_push(
1036        State(sender): State<mpsc::UnboundedSender<CapturedPush>>,
1037        headers: HeaderMap,
1038        body: Bytes,
1039    ) -> StatusCode {
1040        let captured = CapturedPush {
1041            authorization: headers
1042                .get(header::AUTHORIZATION)
1043                .and_then(|value| value.to_str().ok())
1044                .map(ToOwned::to_owned),
1045            notification_token: headers
1046                .get("A2A-Notification-Token")
1047                .and_then(|value| value.to_str().ok())
1048                .map(ToOwned::to_owned),
1049            event: serde_json::from_slice(&body).unwrap(),
1050        };
1051        sender.send(captured).unwrap();
1052        StatusCode::ACCEPTED
1053    }
1054
1055    async fn start_push_webhook() -> (
1056        String,
1057        mpsc::UnboundedReceiver<CapturedPush>,
1058        oneshot::Sender<()>,
1059        tokio::task::JoinHandle<()>,
1060    ) {
1061        let (sender, receiver) = mpsc::unbounded_channel();
1062        let app = Router::new()
1063            .route("/", post(capture_push))
1064            .with_state(sender);
1065        let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
1066        let address = listener.local_addr().unwrap();
1067        let (shutdown_tx, shutdown_rx) = oneshot::channel();
1068        let server = tokio::spawn(async move {
1069            axum::serve(listener, app)
1070                .with_graceful_shutdown(async {
1071                    let _ = shutdown_rx.await;
1072                })
1073                .await
1074                .unwrap();
1075        });
1076
1077        (format!("http://{address}/"), receiver, shutdown_tx, server)
1078    }
1079
1080    async fn next_push(receiver: &mut mpsc::UnboundedReceiver<CapturedPush>) -> CapturedPush {
1081        timeout(Duration::from_secs(5), receiver.recv())
1082            .await
1083            .unwrap()
1084            .unwrap()
1085    }
1086
1087    #[tokio::test]
1088    async fn test_send_message_creates_task() {
1089        let handler = make_handler();
1090        let params = ServiceParams::new();
1091        let req = SendMessageRequest {
1092            message: make_message(),
1093            configuration: None,
1094            metadata: None,
1095            tenant: None,
1096        };
1097        let result = handler.send_message(&params, req).await.unwrap();
1098        match result {
1099            SendMessageResponse::Task(task) => {
1100                assert_eq!(task.status.state, TaskState::Completed);
1101            }
1102            _ => panic!("expected Task response"),
1103        }
1104    }
1105
1106    #[tokio::test]
1107    async fn test_send_streaming_message() {
1108        use futures::StreamExt;
1109        let handler = make_handler();
1110        let params = ServiceParams::new();
1111        let req = SendMessageRequest {
1112            message: make_message(),
1113            configuration: None,
1114            metadata: None,
1115            tenant: None,
1116        };
1117        let mut stream = handler.send_streaming_message(&params, req).await.unwrap();
1118        let event = stream.next().await.unwrap().unwrap();
1119        assert!(matches!(event, StreamResponse::Task(_)));
1120    }
1121
1122    #[tokio::test]
1123    async fn test_get_task_not_found() {
1124        let handler = make_handler();
1125        let params = ServiceParams::new();
1126        let req = GetTaskRequest {
1127            id: "nonexistent".into(),
1128            history_length: None,
1129            tenant: None,
1130        };
1131        let result = handler.get_task(&params, req).await;
1132        assert!(result.is_err());
1133    }
1134
1135    #[tokio::test]
1136    async fn test_get_task_after_send() {
1137        let handler = make_handler();
1138        let params = ServiceParams::new();
1139        let mut msg = make_message();
1140        msg.task_id = Some("t1".into());
1141        msg.context_id = Some("c1".into());
1142        let req = SendMessageRequest {
1143            message: msg,
1144            configuration: None,
1145            metadata: None,
1146            tenant: None,
1147        };
1148        handler.send_message(&params, req).await.unwrap();
1149        let task = handler
1150            .get_task(
1151                &params,
1152                GetTaskRequest {
1153                    id: "t1".into(),
1154                    history_length: None,
1155                    tenant: None,
1156                },
1157            )
1158            .await
1159            .unwrap();
1160        assert_eq!(task.id, "t1");
1161    }
1162
1163    #[tokio::test]
1164    async fn test_list_tasks() {
1165        let handler = make_handler();
1166        let params = ServiceParams::new();
1167        let req = ListTasksRequest {
1168            context_id: None,
1169            status: None,
1170            page_size: None,
1171            page_token: None,
1172            history_length: None,
1173            status_timestamp_after: None,
1174            include_artifacts: None,
1175            tenant: None,
1176        };
1177        let resp = handler.list_tasks(&params, req).await.unwrap();
1178        assert!(resp.tasks.is_empty());
1179    }
1180
1181    #[tokio::test]
1182    async fn test_cancel_task_not_found() {
1183        let handler = make_handler();
1184        let params = ServiceParams::new();
1185        let req = CancelTaskRequest {
1186            id: "nonexistent".into(),
1187            metadata: None,
1188            tenant: None,
1189        };
1190        let result = handler.cancel_task(&params, req).await;
1191        assert!(result.is_err());
1192    }
1193
1194    #[tokio::test]
1195    async fn test_cancel_task_working() {
1196        let handler = make_handler();
1197        let params = ServiceParams::new();
1198        // First create a task via the store directly
1199        let task = Task {
1200            id: "t2".into(),
1201            context_id: "c2".into(),
1202            status: TaskStatus {
1203                state: TaskState::Working,
1204                message: None,
1205                timestamp: None,
1206            },
1207            artifacts: None,
1208            history: None,
1209            metadata: None,
1210        };
1211        handler.task_store.create(task).await.unwrap();
1212        let req = CancelTaskRequest {
1213            id: "t2".into(),
1214            metadata: None,
1215            tenant: None,
1216        };
1217        let result = handler.cancel_task(&params, req).await.unwrap();
1218        assert_eq!(result.status.state, TaskState::Canceled);
1219    }
1220
1221    #[tokio::test]
1222    async fn test_cancel_task_already_completed() {
1223        let handler = make_handler();
1224        let params = ServiceParams::new();
1225        let task = Task {
1226            id: "t3".into(),
1227            context_id: "c3".into(),
1228            status: TaskStatus {
1229                state: TaskState::Completed,
1230                message: None,
1231                timestamp: None,
1232            },
1233            artifacts: None,
1234            history: None,
1235            metadata: None,
1236        };
1237        handler.task_store.create(task).await.unwrap();
1238        let req = CancelTaskRequest {
1239            id: "t3".into(),
1240            metadata: None,
1241            tenant: None,
1242        };
1243        let result = handler.cancel_task(&params, req).await;
1244        assert!(result.is_err());
1245    }
1246
1247    #[tokio::test]
1248    async fn test_subscribe_requires_active_execution() {
1249        let handler = make_handler();
1250        let params = ServiceParams::new();
1251        let req = SubscribeToTaskRequest {
1252            id: "t1".into(),
1253            tenant: None,
1254        };
1255        let result = handler.subscribe_to_task(&params, req).await;
1256        match result {
1257            Ok(_) => panic!("expected subscribe_to_task to fail"),
1258            Err(error) => assert_eq!(error.code, error_code::TASK_NOT_FOUND),
1259        }
1260    }
1261
1262    #[tokio::test]
1263    async fn test_send_message_return_immediately_allows_resubscribe() {
1264        use futures::StreamExt;
1265
1266        let (handler, release) = make_resumable_handler();
1267        let params = ServiceParams::new();
1268        let mut message = make_message();
1269        message.task_id = Some("t-resume".into());
1270        message.context_id = Some("c-resume".into());
1271
1272        let response = handler
1273            .send_message(
1274                &params,
1275                SendMessageRequest {
1276                    message,
1277                    configuration: Some(SendMessageConfiguration {
1278                        accepted_output_modes: None,
1279                        push_notification_config: None,
1280                        history_length: None,
1281                        return_immediately: Some(true),
1282                    }),
1283                    metadata: None,
1284                    tenant: None,
1285                },
1286            )
1287            .await
1288            .unwrap();
1289
1290        match response {
1291            SendMessageResponse::Task(task) => {
1292                assert_eq!(task.status.state, TaskState::Working);
1293            }
1294            _ => panic!("expected Task response"),
1295        }
1296
1297        let mut subscription = handler
1298            .subscribe_to_task(
1299                &params,
1300                SubscribeToTaskRequest {
1301                    id: "t-resume".into(),
1302                    tenant: None,
1303                },
1304            )
1305            .await
1306            .unwrap();
1307
1308        let snapshot = subscription.next().await.unwrap().unwrap();
1309        match snapshot {
1310            StreamResponse::Task(task) => assert_eq!(task.status.state, TaskState::Working),
1311            _ => panic!("expected snapshot task"),
1312        }
1313
1314        release.notify_waiters();
1315
1316        let terminal = subscription.next().await.unwrap().unwrap();
1317        match terminal {
1318            StreamResponse::Task(task) => assert_eq!(task.status.state, TaskState::Completed),
1319            _ => panic!("expected terminal task"),
1320        }
1321
1322        assert!(subscription.next().await.is_none());
1323
1324        let result = handler
1325            .subscribe_to_task(
1326                &params,
1327                SubscribeToTaskRequest {
1328                    id: "t-resume".into(),
1329                    tenant: None,
1330                },
1331            )
1332            .await;
1333        match result {
1334            Ok(_) => panic!("expected subscribe_to_task to fail after completion"),
1335            Err(error) => assert_eq!(error.code, error_code::TASK_NOT_FOUND),
1336        }
1337    }
1338
1339    #[tokio::test]
1340    async fn test_push_config_not_supported_without_store() {
1341        let handler = make_handler();
1342        let params = ServiceParams::new();
1343        let result = handler
1344            .create_push_config(
1345                &params,
1346                CreateTaskPushNotificationConfigRequest {
1347                    task_id: "t1".into(),
1348                    config: PushNotificationConfig {
1349                        url: "http://example.com".into(),
1350                        id: None,
1351                        token: None,
1352                        authentication: None,
1353                    },
1354                    tenant: None,
1355                },
1356            )
1357            .await;
1358        assert!(result.is_err());
1359    }
1360
1361    #[tokio::test]
1362    async fn test_push_config_crud_with_store() {
1363        let handler = make_handler_with_push_configs();
1364        let params = ServiceParams::new();
1365
1366        let created = handler
1367            .create_push_config(
1368                &params,
1369                CreateTaskPushNotificationConfigRequest {
1370                    task_id: "t1".into(),
1371                    config: PushNotificationConfig {
1372                        url: "https://example.com/first".into(),
1373                        id: Some("cfg-1".into()),
1374                        token: None,
1375                        authentication: None,
1376                    },
1377                    tenant: Some("tenant-a".into()),
1378                },
1379            )
1380            .await
1381            .unwrap();
1382        assert_eq!(created.config.id.as_deref(), Some("cfg-1"));
1383
1384        handler
1385            .create_push_config(
1386                &params,
1387                CreateTaskPushNotificationConfigRequest {
1388                    task_id: "t1".into(),
1389                    config: PushNotificationConfig {
1390                        url: "https://example.com/second".into(),
1391                        id: Some("cfg-2".into()),
1392                        token: None,
1393                        authentication: None,
1394                    },
1395                    tenant: Some("tenant-a".into()),
1396                },
1397            )
1398            .await
1399            .unwrap();
1400
1401        let fetched = handler
1402            .get_push_config(
1403                &params,
1404                GetTaskPushNotificationConfigRequest {
1405                    task_id: "t1".into(),
1406                    id: "cfg-1".into(),
1407                    tenant: Some("tenant-a".into()),
1408                },
1409            )
1410            .await
1411            .unwrap();
1412        assert_eq!(fetched.config.url, "https://example.com/first");
1413
1414        let first_page = handler
1415            .list_push_configs(
1416                &params,
1417                ListTaskPushNotificationConfigsRequest {
1418                    task_id: "t1".into(),
1419                    page_size: Some(1),
1420                    page_token: Some("0".into()),
1421                    tenant: Some("tenant-a".into()),
1422                },
1423            )
1424            .await
1425            .unwrap();
1426        assert_eq!(first_page.configs.len(), 1);
1427        assert_eq!(first_page.configs[0].config.id.as_deref(), Some("cfg-1"));
1428        assert_eq!(first_page.next_page_token.as_deref(), Some("1"));
1429
1430        let default_page = handler
1431            .list_push_configs(
1432                &params,
1433                ListTaskPushNotificationConfigsRequest {
1434                    task_id: "t1".into(),
1435                    page_size: Some(0),
1436                    page_token: None,
1437                    tenant: Some("tenant-a".into()),
1438                },
1439            )
1440            .await
1441            .unwrap();
1442        assert_eq!(default_page.configs.len(), 2);
1443        assert!(default_page.next_page_token.is_none());
1444
1445        handler
1446            .delete_push_config(
1447                &params,
1448                DeleteTaskPushNotificationConfigRequest {
1449                    task_id: "t1".into(),
1450                    id: "cfg-1".into(),
1451                    tenant: Some("tenant-a".into()),
1452                },
1453            )
1454            .await
1455            .unwrap();
1456
1457        let remaining = handler
1458            .list_push_configs(
1459                &params,
1460                ListTaskPushNotificationConfigsRequest {
1461                    task_id: "t1".into(),
1462                    page_size: None,
1463                    page_token: None,
1464                    tenant: Some("tenant-a".into()),
1465                },
1466            )
1467            .await
1468            .unwrap();
1469        assert_eq!(remaining.configs.len(), 1);
1470        assert_eq!(remaining.configs[0].config.id.as_deref(), Some("cfg-2"));
1471    }
1472
1473    #[tokio::test]
1474    async fn test_send_message_request_push_config_delivers_webhooks() {
1475        let (url, mut receiver, shutdown_tx, server) = start_push_webhook().await;
1476        let handler = make_push_delivery_handler();
1477        let params = ServiceParams::new();
1478        let mut message = make_message();
1479        message.task_id = Some("t-push-request".into());
1480        message.context_id = Some("c-push-request".into());
1481
1482        let response = handler
1483            .send_message(
1484                &params,
1485                SendMessageRequest {
1486                    message,
1487                    configuration: Some(SendMessageConfiguration {
1488                        accepted_output_modes: None,
1489                        push_notification_config: Some(PushNotificationConfig {
1490                            url: url.clone(),
1491                            id: Some("cfg-request".into()),
1492                            token: Some("notify-token".into()),
1493                            authentication: Some(AuthenticationInfo {
1494                                scheme: "bearer".into(),
1495                                credentials: Some("secret-token".into()),
1496                            }),
1497                        }),
1498                        history_length: None,
1499                        return_immediately: None,
1500                    }),
1501                    metadata: None,
1502                    tenant: None,
1503                },
1504            )
1505            .await
1506            .unwrap();
1507
1508        match response {
1509            SendMessageResponse::Task(task) => assert_eq!(task.status.state, TaskState::Completed),
1510            _ => panic!("expected Task response"),
1511        }
1512
1513        let saved = handler
1514            .get_push_config(
1515                &params,
1516                GetTaskPushNotificationConfigRequest {
1517                    task_id: "t-push-request".into(),
1518                    id: "cfg-request".into(),
1519                    tenant: None,
1520                },
1521            )
1522            .await
1523            .unwrap();
1524        assert_eq!(saved.config.url, url);
1525
1526        let first = next_push(&mut receiver).await;
1527        assert_eq!(first.authorization.as_deref(), Some("Bearer secret-token"));
1528        assert_eq!(first.notification_token.as_deref(), Some("notify-token"));
1529        match first.event {
1530            StreamResponse::StatusUpdate(update) => {
1531                assert_eq!(update.task_id, "t-push-request");
1532                assert_eq!(update.status.state, TaskState::Working);
1533            }
1534            _ => panic!("expected status update push"),
1535        }
1536
1537        let second = next_push(&mut receiver).await;
1538        assert_eq!(second.authorization.as_deref(), Some("Bearer secret-token"));
1539        assert_eq!(second.notification_token.as_deref(), Some("notify-token"));
1540        match second.event {
1541            StreamResponse::Task(task) => {
1542                assert_eq!(task.id, "t-push-request");
1543                assert_eq!(task.status.state, TaskState::Completed);
1544            }
1545            _ => panic!("expected final task push"),
1546        }
1547
1548        shutdown_tx.send(()).unwrap();
1549        server.await.unwrap();
1550    }
1551
1552    #[tokio::test]
1553    async fn test_send_message_stored_push_config_delivers_webhooks() {
1554        let (url, mut receiver, shutdown_tx, server) = start_push_webhook().await;
1555        let handler = make_push_delivery_handler();
1556        let params = ServiceParams::new();
1557
1558        handler
1559            .create_push_config(
1560                &params,
1561                CreateTaskPushNotificationConfigRequest {
1562                    task_id: "t-push-stored".into(),
1563                    config: PushNotificationConfig {
1564                        url: url.clone(),
1565                        id: Some("cfg-stored".into()),
1566                        token: Some("stored-token".into()),
1567                        authentication: None,
1568                    },
1569                    tenant: None,
1570                },
1571            )
1572            .await
1573            .unwrap();
1574
1575        let mut message = make_message();
1576        message.task_id = Some("t-push-stored".into());
1577        message.context_id = Some("c-push-stored".into());
1578        let response = handler
1579            .send_message(
1580                &params,
1581                SendMessageRequest {
1582                    message,
1583                    configuration: None,
1584                    metadata: None,
1585                    tenant: None,
1586                },
1587            )
1588            .await
1589            .unwrap();
1590
1591        match response {
1592            SendMessageResponse::Task(task) => assert_eq!(task.status.state, TaskState::Completed),
1593            _ => panic!("expected Task response"),
1594        }
1595
1596        let first = next_push(&mut receiver).await;
1597        assert_eq!(first.notification_token.as_deref(), Some("stored-token"));
1598        match first.event {
1599            StreamResponse::StatusUpdate(update) => {
1600                assert_eq!(update.task_id, "t-push-stored");
1601                assert_eq!(update.status.state, TaskState::Working);
1602            }
1603            _ => panic!("expected status update push"),
1604        }
1605
1606        let second = next_push(&mut receiver).await;
1607        assert_eq!(second.notification_token.as_deref(), Some("stored-token"));
1608        match second.event {
1609            StreamResponse::Task(task) => {
1610                assert_eq!(task.id, "t-push-stored");
1611                assert_eq!(task.status.state, TaskState::Completed);
1612            }
1613            _ => panic!("expected final task push"),
1614        }
1615
1616        shutdown_tx.send(()).unwrap();
1617        server.await.unwrap();
1618    }
1619
1620    #[tokio::test]
1621    async fn test_extended_agent_card_not_configured() {
1622        let handler = make_handler();
1623        let params = ServiceParams::new();
1624        let result = handler
1625            .get_extended_agent_card(&params, GetExtendedAgentCardRequest { tenant: None })
1626            .await;
1627        assert!(result.is_err());
1628    }
1629
1630    #[test]
1631    fn test_with_capabilities() {
1632        let handler = make_handler().with_capabilities(AgentCapabilities {
1633            streaming: Some(true),
1634            push_notifications: None,
1635            extensions: None,
1636            extended_agent_card: None,
1637        });
1638        assert_eq!(handler.capabilities.streaming, Some(true));
1639    }
1640
1641    #[test]
1642    fn test_with_push_config_store_enables_push_capability() {
1643        let handler = make_handler_with_push_configs();
1644        assert_eq!(handler.capabilities.push_notifications, Some(true));
1645    }
1646}