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