1use 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 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#[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
430pub 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(¶ms, 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(¶ms, 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 ¶ms,
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 ¶ms,
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 active.publish(Err(&error), Some(task.clone())).await;
1248
1249 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 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 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 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 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(¶ms, 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(¶ms, req).await.unwrap();
1365 let task = handler
1366 .get_task(
1367 ¶ms,
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(¶ms, 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(¶ms, 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 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(¶ms, 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(¶ms, 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(¶ms, 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 ¶ms,
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 ¶ms,
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 ¶ms,
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 ¶ms,
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 ¶ms,
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 ¶ms,
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 ¶ms,
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 ¶ms,
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 ¶ms,
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 ¶ms,
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 ¶ms,
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 ¶ms,
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 ¶ms,
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 ¶ms,
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 ¶ms,
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(¶ms, 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}