1use std::collections::HashMap;
9use std::sync::Arc;
10use std::time::Instant;
11
12use a2a_protocol_types::events::{StreamResponse, TaskStatusUpdateEvent};
13use a2a_protocol_types::params::{MessageSendParams, SendMessageConfiguration};
14use a2a_protocol_types::push::TaskPushNotificationConfig;
15use a2a_protocol_types::responses::SendMessageResponse;
16use a2a_protocol_types::task::{ContextId, Task, TaskId, TaskState, TaskStatus};
17
18use crate::error::{ServerError, ServerResult};
19use crate::request_context::RequestContext;
20use crate::streaming::EventQueueWriter;
21
22use super::helpers::{build_call_context, truncate_history, validate_id, validate_metadata_object};
23use super::{CancellationEntry, RequestHandler, SendMessageResult};
24
25pub const MAX_TASK_HISTORY_MESSAGES: usize = 1024;
31
32fn shape_response_history(task: &mut Task, history_length: Option<u32>) {
42 let history = task.history.take();
43 task.history = history_length.and_then(|n| truncate_history(history, n));
47}
48
49fn json_byte_len(value: &serde_json::Value) -> serde_json::Result<usize> {
51 struct CountWriter(usize);
52 impl std::io::Write for CountWriter {
53 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
54 self.0 += buf.len();
55 Ok(buf.len())
56 }
57 fn flush(&mut self) -> std::io::Result<()> {
58 Ok(())
59 }
60 }
61 let mut w = CountWriter(0);
62 serde_json::to_writer(&mut w, value)?;
63 Ok(w.0)
64}
65
66fn second_send_blocked(entry: &CancellationEntry) -> bool {
76 !entry.token.is_cancelled()
77}
78
79fn token_aged(elapsed: std::time::Duration, max_token_age: std::time::Duration) -> bool {
82 elapsed >= max_token_age
83}
84
85const fn evict_aged_token(queue_live: bool) -> bool {
89 !queue_live
90}
91
92fn token_still_evictable(
103 entry: &CancellationEntry,
104 now: Instant,
105 max_token_age: std::time::Duration,
106) -> bool {
107 entry.token.is_cancelled() || token_aged(now.duration_since(entry.created_at), max_token_age)
108}
109
110impl RequestHandler {
111 pub async fn on_send_message(
120 &self,
121 params: MessageSendParams,
122 streaming: bool,
123 headers: Option<&HashMap<String, String>>,
124 ) -> ServerResult<SendMessageResult> {
125 let method_name = if streaming {
126 "SendStreamingMessage"
127 } else {
128 "SendMessage"
129 };
130 let start = Instant::now();
131 trace_info!(method = method_name, streaming, "handling send message");
132 self.metrics.on_request(method_name);
133
134 let tenant = self
135 .resolve_tenant(method_name, headers, params.tenant.as_deref())
136 .await?;
137 let result = crate::store::tenant::TenantContext::scope(tenant, async {
138 self.send_message_inner(params, streaming, method_name, headers)
139 .await
140 })
141 .await;
142 let elapsed = start.elapsed();
143 match &result {
144 Ok(_) => {
145 self.metrics.on_response(method_name);
146 self.metrics.on_latency(method_name, elapsed);
147 }
148 Err(e) => {
149 self.metrics.on_error(method_name, e.metric_label());
150 self.metrics.on_latency(method_name, elapsed);
151 }
152 }
153 result
154 }
155
156 async fn register_inline_push_config(
172 &self,
173 configuration: Option<&SendMessageConfiguration>,
174 task_id: &TaskId,
175 ) -> ServerResult<()> {
176 let Some(inline) = configuration.and_then(|c| c.task_push_notification_config.clone())
177 else {
178 return Ok(());
179 };
180 self.validate_and_store_push_config(TaskPushNotificationConfig {
184 task_id: Some(task_id.0.clone()),
185 ..inline
186 })
187 .await?;
188 Ok(())
189 }
190
191 #[allow(clippy::too_many_lines)]
194 async fn send_message_inner(
195 &self,
196 params: MessageSendParams,
197 streaming: bool,
198 method_name: &str,
199 headers: Option<&HashMap<String, String>>,
200 ) -> ServerResult<SendMessageResult> {
201 let call_ctx = build_call_context(method_name, headers);
202 self.interceptors.run_before(&call_ctx).await?;
203 self.ensure_required_extensions(&call_ctx)?;
206
207 if streaming {
211 self.ensure_streaming_supported()?;
212 }
213
214 if let Some(ref ctx_id) = params.message.context_id {
216 validate_id(&ctx_id.0, "context_id", self.limits.max_id_length)?;
217 }
218 if let Some(ref task_id) = params.message.task_id {
219 validate_id(&task_id.0, "task_id", self.limits.max_id_length)?;
220 }
221
222 if params.message.parts.is_empty() {
224 return Err(ServerError::InvalidParams(
225 "message must contain at least one part".into(),
226 ));
227 }
228
229 validate_metadata_object(params.message.metadata.as_ref(), "message")?;
235 validate_metadata_object(params.metadata.as_ref(), "request")?;
236 for (i, part) in params.message.parts.iter().enumerate() {
237 validate_metadata_object(part.metadata.as_ref(), &format!("message part {i}"))?;
238 }
239
240 let max_meta = self.limits.max_metadata_size;
243 if let Some(ref meta) = params.message.metadata {
244 let meta_size = json_byte_len(meta).map_err(|_| {
245 ServerError::InvalidParams("message metadata is not serializable".into())
246 })?;
247 if meta_size > max_meta {
248 return Err(ServerError::InvalidParams(format!(
249 "message metadata exceeds maximum size ({meta_size} bytes, max {max_meta})"
250 )));
251 }
252 }
253 if let Some(ref meta) = params.metadata {
254 let meta_size = json_byte_len(meta).map_err(|_| {
255 ServerError::InvalidParams("request metadata is not serializable".into())
256 })?;
257 if meta_size > max_meta {
258 return Err(ServerError::InvalidParams(format!(
259 "request metadata exceeds maximum size ({meta_size} bytes, max {max_meta})"
260 )));
261 }
262 }
263
264 let context_id = if let Some(ref ctx) = params.message.context_id {
270 ctx.0.clone()
271 } else if let Some(ref msg_task_id) = params.message.task_id {
272 match self.task_store.get(msg_task_id).await? {
273 Some(task) => task.context_id.0.clone(),
274 None => return Err(ServerError::TaskNotFound(msg_task_id.clone())),
277 }
278 } else {
279 uuid::Uuid::new_v4().to_string()
280 };
281
282 let context_lock = {
286 let mut locks = self.context_locks.write().await;
287 if locks.len() >= self.limits.max_context_locks {
291 locks.retain(|_, v| Arc::strong_count(v) > 1);
292 }
293 locks.entry(context_id.clone()).or_default().clone()
294 };
295 let context_guard = context_lock.lock().await;
296
297 let stored_task = self.find_task_by_context(&context_id).await?;
299
300 let task_id = if let Some(ref msg_task_id) = params.message.task_id {
304 if let Some(ref stored) = stored_task {
305 if msg_task_id != &stored.id {
306 return Err(ServerError::InvalidParams(
307 "message task_id does not match task found for context".into(),
308 ));
309 }
310 if stored.status.state.is_terminal() {
314 return Err(ServerError::UnsupportedOperation(format!(
315 "task {} is in terminal state '{}' and cannot accept new messages",
316 stored.id, stored.status.state
317 )));
318 }
319 } else {
321 let exists = self.task_store.get(msg_task_id).await?.is_some();
325 if !exists {
326 return Err(ServerError::TaskNotFound(msg_task_id.clone()));
327 }
328 return Err(ServerError::InvalidParams(
330 "task_id exists but belongs to a different context".into(),
331 ));
332 }
333 msg_task_id.clone()
334 } else {
335 TaskId::new(uuid::Uuid::new_v4().to_string())
339 };
340
341 let return_immediately = params
343 .configuration
344 .as_ref()
345 .and_then(|c| c.return_immediately)
346 .unwrap_or(false);
347 let response_history_length = params.configuration.as_ref().and_then(|c| c.history_length);
348
349 let use_background = streaming || return_immediately;
354
355 {
364 let tokens = self.cancellation_tokens.read().await;
365 if let Some(entry) = tokens.get(&task_id) {
366 if second_send_blocked(entry) {
367 return Err(ServerError::UnsupportedOperation(format!(
368 "task {task_id} is already being processed; \
369 wait for it to reach input-required or a terminal state before sending again"
370 )));
371 }
372 }
373 }
374
375 trace_debug!(
377 task_id = %task_id,
378 context_id = %context_id,
379 "creating task"
380 );
381 let mut history = stored_task
388 .as_ref()
389 .and_then(|s| s.history.clone())
390 .unwrap_or_default();
391 history.push(params.message.clone());
392 let excess = history.len().saturating_sub(MAX_TASK_HISTORY_MESSAGES);
399 history.drain(..excess);
400 let task = Task {
401 id: task_id.clone(),
402 context_id: ContextId::new(&context_id),
403 status: TaskStatus::with_timestamp(TaskState::Submitted),
404 history: Some(history),
405 artifacts: stored_task.as_ref().and_then(|s| s.artifacts.clone()),
406 metadata: stored_task.as_ref().and_then(|s| s.metadata.clone()),
407 };
408
409 let mut ctx = RequestContext::new(params.message, task_id.clone(), context_id);
412 if let Some(stored) = stored_task {
413 ctx = ctx.with_stored_task(stored);
414 }
415 if let Some(meta) = params.metadata {
416 ctx = ctx.with_metadata(meta);
417 }
418
419 let (writer, reader, persistence_rx) = match self
426 .event_queue_manager
427 .lease(&task_id, use_background)
428 .await
429 {
430 crate::streaming::QueueLease::Created {
431 writer,
432 reader,
433 persistence_rx,
434 } => (writer, reader, persistence_rx),
435 crate::streaming::QueueLease::Existing => {
436 return Err(ServerError::UnsupportedOperation(format!(
446 "task {task_id} is already being processed; wait for it to reach \
447 input-required or a terminal state before sending again"
448 )));
449 }
450 crate::streaming::QueueLease::CapacityExhausted => {
451 let cap = self
452 .event_queue_manager
453 .max_concurrent_queues()
454 .map_or_else(String::new, |n| format!(" ({n})"));
455 return Err(ServerError::Overloaded(format!(
456 "server at maximum concurrent stream capacity{cap}; retry later"
457 )));
458 }
459 };
460
461 {
466 let (cancelled_ids, aged_candidates): (Vec<TaskId>, Vec<TaskId>) = {
475 let tokens = self.cancellation_tokens.read().await;
476 if tokens.len() >= self.limits.max_cancellation_tokens {
477 let now = Instant::now();
478 let mut cancelled = Vec::new();
479 let mut aged = Vec::new();
480 for (id, entry) in tokens.iter() {
481 if entry.token.is_cancelled() {
482 cancelled.push(id.clone());
483 } else if token_aged(
484 now.duration_since(entry.created_at),
485 self.limits.max_token_age,
486 ) {
487 aged.push(id.clone());
488 }
489 }
490 drop(tokens);
491 (cancelled, aged)
492 } else {
493 (Vec::new(), Vec::new())
494 }
495 };
496
497 let mut stale_ids = cancelled_ids;
502 for id in aged_candidates {
503 let queue_live = self.event_queue_manager.has_queue(&id).await;
504 if evict_aged_token(queue_live) {
505 stale_ids.push(id);
506 }
507 }
508
509 if !stale_ids.is_empty() {
514 let now = Instant::now();
515 let mut tokens = self.cancellation_tokens.write().await;
516 for id in &stale_ids {
517 let evict = tokens
518 .get(id)
519 .is_some_and(|e| token_still_evictable(e, now, self.limits.max_token_age));
520 if evict {
521 tokens.remove(id);
522 }
523 }
524 }
525
526 let mut tokens = self.cancellation_tokens.write().await;
528 tokens.insert(
529 task_id.clone(),
530 CancellationEntry {
531 token: ctx.cancellation_token.clone(),
532 created_at: Instant::now(),
533 },
534 );
535 }
536
537 if let Err(e) = self.task_store.save(&task).await {
540 self.event_queue_manager.destroy(&task_id).await;
541 self.cancellation_tokens.write().await.remove(&task_id);
542 return Err(e.into());
543 }
544
545 drop(context_guard);
548
549 if let Err(e) =
560 Box::pin(self.register_inline_push_config(params.configuration.as_ref(), &task_id))
561 .await
562 {
563 self.event_queue_manager.destroy(&task_id).await;
564 self.cancellation_tokens.write().await.remove(&task_id);
565 return Err(e);
566 }
567
568 let executor = Arc::clone(&self.executor);
572 let task_id_for_cleanup = task_id.clone();
573 let event_queue_mgr = self.event_queue_manager.clone();
574 let cancel_tokens = Arc::clone(&self.cancellation_tokens);
575 let executor_timeout = self.executor_timeout;
576 let executor_handle = tokio::spawn(async move {
577 trace_debug!(task_id = %ctx.task_id, "executor started");
578
579 #[allow(clippy::items_after_statements)]
584 struct CleanupGuard {
585 task_id: Option<TaskId>,
586 queue_mgr: crate::streaming::EventQueueManager,
587 tokens: std::sync::Arc<tokio::sync::RwLock<HashMap<TaskId, CancellationEntry>>>,
588 }
589 #[allow(clippy::items_after_statements)]
590 impl Drop for CleanupGuard {
591 fn drop(&mut self) {
592 if let Some(tid) = self.task_id.take() {
593 let qmgr = self.queue_mgr.clone();
594 let tokens = std::sync::Arc::clone(&self.tokens);
595 tokio::task::spawn(async move {
596 qmgr.destroy(&tid).await;
597 tokens.write().await.remove(&tid);
598 });
599 }
600 }
601 }
602 let mut cleanup_guard = CleanupGuard {
603 task_id: Some(task_id_for_cleanup.clone()),
604 queue_mgr: event_queue_mgr.clone(),
605 tokens: Arc::clone(&cancel_tokens),
606 };
607
608 let result = {
610 let exec_future = if let Some(timeout) = executor_timeout {
611 tokio::time::timeout(timeout, executor.execute(&ctx, writer.as_ref()))
612 .await
613 .unwrap_or_else(|_| {
614 Err(a2a_protocol_types::error::A2aError::internal(format!(
615 "executor timed out after {}s",
616 timeout.as_secs()
617 )))
618 })
619 } else {
620 executor.execute(&ctx, writer.as_ref()).await
621 };
622 exec_future
623 };
624
625 if let Err(ref e) = result {
626 trace_error!(task_id = %ctx.task_id, error = %e, "executor failed");
627 let fail_event = StreamResponse::StatusUpdate(TaskStatusUpdateEvent {
629 task_id: ctx.task_id.clone(),
630 context_id: ContextId::new(ctx.context_id.clone()),
631 status: TaskStatus::with_timestamp(TaskState::Failed),
632 metadata: Some(serde_json::json!({ "error": e.to_string() })),
633 });
634 if let Err(_write_err) = writer.write(fail_event).await {
635 trace_error!(
636 task_id = %ctx.task_id,
637 error = %_write_err,
638 "failed to write failure event to queue"
639 );
640 }
641 }
642 drop(writer);
644 event_queue_mgr.destroy(&task_id_for_cleanup).await;
647 cancel_tokens.write().await.remove(&task_id_for_cleanup);
648 cleanup_guard.task_id = None;
649 });
650
651 self.interceptors.run_after(&call_ctx).await?;
652
653 if use_background {
654 self.spawn_background_event_processor(
670 task_id.clone(),
671 executor_handle,
672 persistence_rx,
673 task.clone(),
674 );
675
676 if streaming {
677 let mut reader = reader;
680 let mut snapshot = task.clone();
681 shape_response_history(&mut snapshot, response_history_length);
682 reader.set_first_event(StreamResponse::Task(snapshot));
683 Ok(SendMessageResult::Stream(reader))
684 } else {
685 drop(reader);
689 let mut task = task;
690 shape_response_history(&mut task, response_history_length);
691 Ok(SendMessageResult::Response(SendMessageResponse::Task(task)))
692 }
693 } else {
694 let collected = self
698 .collect_events(reader, task_id.clone(), executor_handle)
699 .await?;
700
701 if let Some(message) = collected.direct_message {
708 return Ok(SendMessageResult::Response(SendMessageResponse::Message(
709 message,
710 )));
711 }
712
713 let mut final_task = collected.task;
714 shape_response_history(&mut final_task, response_history_length);
715 Ok(SendMessageResult::Response(SendMessageResponse::Task(
716 final_task,
717 )))
718 }
719 }
720}
721
722#[cfg(test)]
723mod tests {
724 use super::*;
725 use a2a_protocol_types::message::{Message, MessageId, MessageRole, Part};
726 use a2a_protocol_types::params::{MessageSendParams, SendMessageConfiguration};
727 use a2a_protocol_types::task::ContextId;
728
729 use crate::agent_executor;
730 use crate::builder::RequestHandlerBuilder;
731
732 struct DummyExecutor;
733 agent_executor!(DummyExecutor, |_ctx, _queue| async { Ok(()) });
734
735 fn make_handler() -> RequestHandler {
736 RequestHandlerBuilder::new(DummyExecutor)
737 .build()
738 .expect("default build should succeed")
739 }
740
741 fn make_params(context_id: Option<&str>) -> MessageSendParams {
742 MessageSendParams {
743 message: Message {
744 id: MessageId::new("msg-1"),
745 role: MessageRole::User,
746 parts: vec![Part::text("hello")],
747 context_id: context_id.map(ContextId::new),
748 task_id: None,
749 reference_task_ids: None,
750 extensions: None,
751 metadata: None,
752 },
753 configuration: None,
754 metadata: None,
755 tenant: None,
756 }
757 }
758
759 #[tokio::test]
779 async fn cleanup_guard_releases_the_token_when_the_executor_panics() {
780 struct PanicExec;
781 impl crate::executor::AgentExecutor for PanicExec {
782 fn execute<'a>(
783 &'a self,
784 _ctx: &'a crate::request_context::RequestContext,
785 _queue: &'a dyn crate::streaming::EventQueueWriter,
786 ) -> std::pin::Pin<
787 Box<
788 dyn std::future::Future<Output = a2a_protocol_types::error::A2aResult<()>>
789 + Send
790 + 'a,
791 >,
792 > {
793 Box::pin(async { panic!("executor panics before it can clean up") })
794 }
795 }
796
797 let handler = RequestHandlerBuilder::new(PanicExec)
798 .build()
799 .expect("build should succeed");
800
801 let _ = handler.on_send_message(make_params(None), true, None).await;
802
803 let mut cleaned = false;
805 for _ in 0..200 {
806 if handler.cancellation_tokens.read().await.is_empty() {
807 cleaned = true;
808 break;
809 }
810 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
811 }
812 assert!(
813 cleaned,
814 "a panicking executor must still release its cancellation token; \
815 CleanupGuard::drop is the only path that does so"
816 );
817 handler.shutdown().await;
818 }
819
820 #[tokio::test]
836 async fn oversized_stored_history_is_trimmed_back_to_the_cap() {
837 let handler = make_handler();
838 let task_id = TaskId::new("t-overlong");
839
840 let seeded = MAX_TASK_HISTORY_MESSAGES * 2 - 1;
842 let mut task = task_with_history(seeded);
843 task.id = task_id.clone();
844 handler
845 .task_store
846 .save(&task)
847 .await
848 .expect("seed the oversized task");
849
850 let mut params = make_params(None);
851 params.message.task_id = Some(task_id.clone());
852 let _ = handler.on_send_message(params, false, None).await;
853
854 let stored = handler
855 .task_store
856 .get(&task_id)
857 .await
858 .expect("load")
859 .expect("task should still exist");
860 assert_eq!(
861 stored.history.map(|h| h.len()),
862 Some(MAX_TASK_HISTORY_MESSAGES),
863 "an oversized history must be trimmed back to exactly the cap"
864 );
865 handler.shutdown().await;
866 }
867
868 async fn seed_cancelled_tokens(handler: &RequestHandler, n: usize) -> Vec<TaskId> {
873 let mut ids = Vec::new();
874 let mut tokens = handler.cancellation_tokens.write().await;
878 for i in 0..n {
879 let id = TaskId::new(format!("stale-{i}"));
880 let token = tokio_util::sync::CancellationToken::new();
881 token.cancel();
882 tokens.insert(
883 id.clone(),
884 CancellationEntry {
885 token,
886 created_at: Instant::now(),
887 },
888 );
889 ids.push(id);
890 }
891 drop(tokens);
892 ids
893 }
894
895 #[tokio::test]
909 async fn sweep_evicts_cancelled_tokens_once_the_map_is_at_capacity() {
910 let handler = RequestHandlerBuilder::new(DummyExecutor)
911 .with_handler_limits(
912 crate::handler::limits::HandlerLimits::default().with_max_cancellation_tokens(2),
913 )
914 .build()
915 .expect("build should succeed");
916
917 let stale = seed_cancelled_tokens(&handler, 2).await;
918 assert_eq!(handler.cancellation_tokens.read().await.len(), 2);
919
920 let _ = handler
923 .on_send_message(make_params(None), false, None)
924 .await;
925
926 let tokens = handler.cancellation_tokens.read().await;
927 for id in &stale {
928 assert!(
929 !tokens.contains_key(id),
930 "cancelled token {id:?} should have been evicted by the sweep"
931 );
932 }
933 drop(tokens);
934 handler.shutdown().await;
935 }
936
937 #[tokio::test]
946 async fn context_locks_are_not_pruned_below_the_limit() {
947 let handler = RequestHandlerBuilder::new(DummyExecutor)
948 .with_handler_limits(
949 crate::handler::limits::HandlerLimits::default().with_max_context_locks(5),
950 )
951 .build()
952 .expect("build should succeed");
953
954 for ctx in ["ctx-a", "ctx-b", "ctx-c"] {
955 let _ = handler
956 .on_send_message(make_params(Some(ctx)), false, None)
957 .await;
958 }
959
960 assert_eq!(
961 handler.context_locks.read().await.len(),
962 3,
963 "with a limit of 5, three contexts must all be retained"
964 );
965 handler.shutdown().await;
966 }
967
968 #[tokio::test]
982 async fn context_lock_pruning_spares_locks_still_in_use() {
983 let handler = RequestHandlerBuilder::new(DummyExecutor)
984 .with_handler_limits(
985 crate::handler::limits::HandlerLimits::default().with_max_context_locks(2),
986 )
987 .build()
988 .expect("build should succeed");
989
990 let live = std::sync::Arc::new(tokio::sync::Mutex::new(()));
992 {
993 let mut locks = handler.context_locks.write().await;
994 locks.insert("live-ctx".to_string(), std::sync::Arc::clone(&live));
995 locks.insert(
996 "stale-1".to_string(),
997 std::sync::Arc::new(tokio::sync::Mutex::new(())),
998 );
999 locks.insert(
1000 "stale-2".to_string(),
1001 std::sync::Arc::new(tokio::sync::Mutex::new(())),
1002 );
1003 }
1004
1005 let _ = handler
1007 .on_send_message(make_params(Some("ctx-new")), false, None)
1008 .await;
1009
1010 let locks = handler.context_locks.read().await;
1011 assert!(
1012 locks.contains_key("live-ctx"),
1013 "a lock another owner still holds must survive pruning"
1014 );
1015 assert!(
1016 !locks.contains_key("stale-1") && !locks.contains_key("stale-2"),
1017 "locks owned only by the map must be reclaimed"
1018 );
1019 drop(locks);
1020 drop(live);
1021 handler.shutdown().await;
1022 }
1023
1024 fn handler_with_metadata_limit(max: usize) -> RequestHandler {
1028 RequestHandlerBuilder::new(DummyExecutor)
1029 .with_handler_limits(
1030 crate::handler::limits::HandlerLimits::default().with_max_metadata_size(max),
1031 )
1032 .build()
1033 .expect("build with custom limits should succeed")
1034 }
1035
1036 fn metadata_of_exactly(n: usize) -> serde_json::Value {
1044 let value = serde_json::json!({ "k": "x".repeat(n - 8) });
1045 assert_eq!(
1046 serde_json::to_vec(&value).expect("serialise").len(),
1047 n,
1048 "fixture must serialise to exactly {n} bytes"
1049 );
1050 value
1051 }
1052
1053 #[tokio::test]
1063 async fn metadata_exactly_at_limit_is_accepted_but_one_byte_over_is_not() {
1064 const MAX: usize = 64;
1065
1066 let mut params = make_params(None);
1068 params.message.metadata = Some(metadata_of_exactly(MAX));
1069 assert!(
1070 !matches!(
1071 handler_with_metadata_limit(MAX)
1072 .on_send_message(params, false, None)
1073 .await,
1074 Err(ServerError::InvalidParams(_))
1075 ),
1076 "message metadata of exactly {MAX} bytes is within the limit"
1077 );
1078
1079 let mut params = make_params(None);
1080 params.message.metadata = Some(metadata_of_exactly(MAX + 1));
1081 assert!(
1082 matches!(
1083 handler_with_metadata_limit(MAX)
1084 .on_send_message(params, false, None)
1085 .await,
1086 Err(ServerError::InvalidParams(_))
1087 ),
1088 "message metadata one byte over the limit must be rejected"
1089 );
1090
1091 let mut params = make_params(None);
1093 params.metadata = Some(metadata_of_exactly(MAX));
1094 assert!(
1095 !matches!(
1096 handler_with_metadata_limit(MAX)
1097 .on_send_message(params, false, None)
1098 .await,
1099 Err(ServerError::InvalidParams(_))
1100 ),
1101 "request metadata of exactly {MAX} bytes is within the limit"
1102 );
1103
1104 let mut params = make_params(None);
1105 params.metadata = Some(metadata_of_exactly(MAX + 1));
1106 assert!(
1107 matches!(
1108 handler_with_metadata_limit(MAX)
1109 .on_send_message(params, false, None)
1110 .await,
1111 Err(ServerError::InvalidParams(_))
1112 ),
1113 "request metadata one byte over the limit must be rejected"
1114 );
1115 }
1116
1117 fn task_with_history(len: usize) -> Task {
1122 Task {
1123 id: TaskId::new("t-hist"),
1124 context_id: ContextId::new("ctx-hist"),
1125 status: TaskStatus::new(TaskState::Submitted),
1126 artifacts: None,
1127 history: Some(
1128 (0..len)
1129 .map(|i| Message {
1130 id: MessageId::new(format!("h{i}")),
1131 role: MessageRole::User,
1132 parts: vec![Part::text("x")],
1133 context_id: None,
1134 task_id: None,
1135 reference_task_ids: None,
1136 extensions: None,
1137 metadata: None,
1138 })
1139 .collect(),
1140 ),
1141 metadata: None,
1142 }
1143 }
1144
1145 fn shaped_ids(len: usize, history_length: Option<u32>) -> Option<Vec<String>> {
1147 let mut task = task_with_history(len);
1148 shape_response_history(&mut task, history_length);
1149 task.history
1150 .map(|msgs| msgs.into_iter().map(|m| m.id.0).collect())
1151 }
1152
1153 #[test]
1169 fn shape_response_history_covers_every_branch() {
1170 assert_eq!(shaped_ids(3, None), None);
1172
1173 assert_eq!(shaped_ids(3, Some(0)), None);
1175
1176 assert_eq!(
1178 shaped_ids(6, Some(2)),
1179 Some(vec!["h4".to_string(), "h5".to_string()])
1180 );
1181 assert_eq!(shaped_ids(4, Some(1)), Some(vec!["h3".to_string()]));
1182
1183 assert_eq!(
1185 shaped_ids(3, Some(3)),
1186 Some(vec!["h0".to_string(), "h1".to_string(), "h2".to_string()])
1187 );
1188 assert_eq!(
1189 shaped_ids(2, Some(5)),
1190 Some(vec!["h0".to_string(), "h1".to_string()])
1191 );
1192
1193 assert_eq!(shaped_ids(0, Some(3)), Some(vec![]));
1195 }
1196
1197 #[tokio::test]
1198 async fn empty_message_parts_returns_invalid_params() {
1199 let handler = make_handler();
1200 let mut params = make_params(None);
1201 params.message.parts = vec![];
1202
1203 let result = handler.on_send_message(params, false, None).await;
1204
1205 assert!(
1206 matches!(result, Err(ServerError::InvalidParams(_))),
1207 "expected InvalidParams for empty parts"
1208 );
1209 }
1210
1211 #[tokio::test]
1212 async fn oversized_message_metadata_returns_invalid_params() {
1213 let handler = make_handler();
1214 let mut params = make_params(None);
1215 params.message.metadata = Some(serde_json::json!({ "k": "x".repeat(1_100_000) }));
1221
1222 let result = handler.on_send_message(params, false, None).await;
1223
1224 assert!(
1225 matches!(result, Err(ServerError::InvalidParams(_))),
1226 "expected InvalidParams for oversized message metadata"
1227 );
1228 }
1229
1230 #[tokio::test]
1231 async fn oversized_request_metadata_returns_invalid_params() {
1232 let handler = make_handler();
1233 let mut params = make_params(None);
1234 let big_value = "x".repeat(1_100_000);
1236 params.metadata = Some(serde_json::json!(big_value));
1237
1238 let result = handler.on_send_message(params, false, None).await;
1239
1240 assert!(
1241 matches!(result, Err(ServerError::InvalidParams(_))),
1242 "expected InvalidParams for oversized request metadata"
1243 );
1244 }
1245
1246 #[tokio::test]
1247 async fn non_object_message_metadata_returns_invalid_params() {
1248 let handler = make_handler();
1251 let mut params = make_params(None);
1252 params.message.metadata = Some(serde_json::json!([1, 2, 3]));
1253
1254 let result = handler.on_send_message(params, false, None).await;
1255 assert!(
1256 matches!(result, Err(ServerError::InvalidParams(ref msg))
1257 if msg.contains("JSON object") && msg.contains("array")),
1258 "expected InvalidParams naming the offending kind (array), got: {result:?}"
1259 );
1260 }
1261
1262 #[tokio::test]
1263 async fn scalar_request_metadata_returns_invalid_params() {
1264 let handler = make_handler();
1265 let mut params = make_params(None);
1266 params.metadata = Some(serde_json::json!("a bare string"));
1267
1268 let result = handler.on_send_message(params, false, None).await;
1269 assert!(
1270 matches!(result, Err(ServerError::InvalidParams(ref msg))
1271 if msg.contains("JSON object") && msg.contains("string")),
1272 "expected InvalidParams naming the offending kind (string), got: {result:?}"
1273 );
1274 }
1275
1276 #[tokio::test]
1277 async fn non_object_part_metadata_returns_invalid_params() {
1278 let handler = make_handler();
1279 let mut params = make_params(None);
1280 params.message.parts[0].metadata = Some(serde_json::json!(42));
1281
1282 let result = handler.on_send_message(params, false, None).await;
1283 assert!(
1284 matches!(result, Err(ServerError::InvalidParams(ref msg))
1285 if msg.contains("part 0") && msg.contains("number")),
1286 "expected InvalidParams naming the part index and kind (number), got: {result:?}"
1287 );
1288 }
1289
1290 #[tokio::test]
1291 async fn object_metadata_is_accepted() {
1292 let handler = make_handler();
1294 let mut params = make_params(None);
1295 params.message.metadata = Some(serde_json::json!({"k": "v"}));
1296 params.metadata = Some(serde_json::json!({"trace": 1}));
1297
1298 let result = handler.on_send_message(params, false, None).await;
1299 assert!(
1300 result.is_ok(),
1301 "object metadata must be accepted, got: {result:?}"
1302 );
1303 }
1304
1305 #[tokio::test]
1306 async fn valid_message_returns_ok() {
1307 let handler = make_handler();
1308 let params = make_params(None);
1309
1310 let result = handler.on_send_message(params, false, None).await;
1311
1312 let send_result = result.expect("expected Ok for valid message");
1313 assert!(
1314 matches!(
1315 send_result,
1316 SendMessageResult::Response(SendMessageResponse::Task(_))
1317 ),
1318 "expected Response(Task) for non-streaming send"
1319 );
1320 }
1321
1322 #[tokio::test]
1323 async fn return_immediately_returns_task() {
1324 let handler = make_handler();
1325 let mut params = make_params(None);
1326 params.configuration = Some(SendMessageConfiguration {
1327 accepted_output_modes: vec!["text/plain".into()],
1328 task_push_notification_config: None,
1329 history_length: None,
1330 return_immediately: Some(true),
1331 });
1332
1333 let result = handler.on_send_message(params, false, None).await;
1334
1335 assert!(
1336 matches!(
1337 result,
1338 Ok(SendMessageResult::Response(SendMessageResponse::Task(_)))
1339 ),
1340 "expected Response(Task) for return_immediately=true"
1341 );
1342 }
1343
1344 struct CompletingExecutor;
1346 agent_executor!(CompletingExecutor, |ctx, queue| async {
1347 for state in [TaskState::Working, TaskState::Completed] {
1348 let ev = StreamResponse::StatusUpdate(TaskStatusUpdateEvent {
1349 task_id: ctx.task_id.clone(),
1350 context_id: ContextId::new(ctx.context_id.clone()),
1351 status: TaskStatus::with_timestamp(state),
1352 metadata: None,
1353 });
1354 let _ = queue.write(ev).await;
1355 }
1356 Ok(())
1357 });
1358
1359 struct BlockingExecutor;
1362 agent_executor!(BlockingExecutor, |_ctx, _queue| async {
1363 tokio::time::sleep(std::time::Duration::from_secs(30)).await;
1364 Ok(())
1365 });
1366
1367 async fn poll_task_state(
1368 handler: &RequestHandler,
1369 task_id: &TaskId,
1370 want: TaskState,
1371 ) -> TaskState {
1372 for _ in 0..200 {
1373 if let Ok(Some(t)) = handler.task_store.get(task_id).await {
1374 if t.status.state == want {
1375 return want;
1376 }
1377 }
1378 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
1379 }
1380 handler
1381 .task_store
1382 .get(task_id)
1383 .await
1384 .ok()
1385 .flatten()
1386 .map_or(TaskState::Submitted, |t| t.status.state)
1387 }
1388
1389 #[tokio::test]
1394 async fn return_immediately_persists_final_state() {
1395 let handler = RequestHandlerBuilder::new(CompletingExecutor)
1396 .build()
1397 .unwrap();
1398 let mut params = make_params(Some("ctx-ri"));
1399 params.configuration = Some(SendMessageConfiguration {
1400 accepted_output_modes: vec!["text/plain".into()],
1401 task_push_notification_config: None,
1402 history_length: None,
1403 return_immediately: Some(true),
1404 });
1405
1406 let SendMessageResult::Response(SendMessageResponse::Task(task)) =
1407 handler.on_send_message(params, false, None).await.unwrap()
1408 else {
1409 panic!("expected an immediate Task response");
1410 };
1411 assert_eq!(
1412 task.status.state,
1413 TaskState::Submitted,
1414 "snapshot is Submitted"
1415 );
1416
1417 let final_state = poll_task_state(&handler, &task.id, TaskState::Completed).await;
1418 assert_eq!(
1419 final_state,
1420 TaskState::Completed,
1421 "fire-and-forget task must reach Completed in the store"
1422 );
1423 }
1424
1425 #[tokio::test]
1429 async fn concurrent_send_to_in_flight_task_is_rejected() {
1430 let handler = RequestHandlerBuilder::new(BlockingExecutor)
1431 .build()
1432 .unwrap();
1433
1434 let mut first = make_params(Some("ctx-dup"));
1436 first.configuration = Some(SendMessageConfiguration {
1437 accepted_output_modes: vec!["text/plain".into()],
1438 task_push_notification_config: None,
1439 history_length: None,
1440 return_immediately: Some(true),
1441 });
1442 let SendMessageResult::Response(SendMessageResponse::Task(task)) =
1443 handler.on_send_message(first, false, None).await.unwrap()
1444 else {
1445 panic!("expected an immediate Task response");
1446 };
1447
1448 let mut second = make_params(Some("ctx-dup"));
1450 second.message.task_id = Some(task.id.clone());
1451 let result = handler.on_send_message(second, false, None).await;
1452 assert!(
1453 matches!(result, Err(ServerError::UnsupportedOperation(_))),
1454 "expected rejection of a send to an in-flight task, got {result:?}"
1455 );
1456 }
1457
1458 #[tokio::test]
1463 async fn stream_cap_exhaustion_returns_overloaded_without_orphan() {
1464 let handler = RequestHandlerBuilder::new(BlockingExecutor)
1465 .with_max_concurrent_streams(1)
1466 .build()
1467 .unwrap();
1468
1469 let mut first = make_params(Some("ctx-a"));
1472 first.configuration = Some(SendMessageConfiguration {
1473 accepted_output_modes: vec!["text/plain".into()],
1474 task_push_notification_config: None,
1475 history_length: None,
1476 return_immediately: Some(true),
1477 });
1478 handler.on_send_message(first, false, None).await.unwrap();
1479 assert_eq!(handler.event_queue_manager.active_count().await, 1);
1480
1481 let mut second = make_params(Some("ctx-b"));
1483 second.configuration = Some(SendMessageConfiguration {
1484 accepted_output_modes: vec!["text/plain".into()],
1485 task_push_notification_config: None,
1486 history_length: None,
1487 return_immediately: Some(true),
1488 });
1489 let result = handler.on_send_message(second, false, None).await;
1490 assert!(
1491 matches!(result, Err(ServerError::Overloaded(_))),
1492 "expected Overloaded at capacity, got {result:?}"
1493 );
1494 assert_eq!(
1496 handler.event_queue_manager.active_count().await,
1497 1,
1498 "capacity rejection must not create a queue"
1499 );
1500 }
1501
1502 #[tokio::test]
1503 async fn empty_context_id_returns_invalid_params() {
1504 let handler = make_handler();
1505 let params = make_params(Some(""));
1506
1507 let result = handler.on_send_message(params, false, None).await;
1508
1509 assert!(
1510 matches!(result, Err(ServerError::InvalidParams(_))),
1511 "expected InvalidParams for empty context_id"
1512 );
1513 }
1514
1515 #[tokio::test]
1516 async fn too_long_context_id_returns_invalid_params() {
1517 use crate::handler::limits::HandlerLimits;
1519
1520 let handler = RequestHandlerBuilder::new(DummyExecutor)
1521 .with_handler_limits(HandlerLimits::default().with_max_id_length(10))
1522 .build()
1523 .unwrap();
1524 let long_ctx = "x".repeat(20);
1525 let params = make_params(Some(&long_ctx));
1526
1527 let result = handler.on_send_message(params, false, None).await;
1528 assert!(
1529 matches!(result, Err(ServerError::InvalidParams(ref msg)) if msg.contains("maximum length")),
1530 "expected InvalidParams for too-long context_id"
1531 );
1532 }
1533
1534 #[tokio::test]
1535 async fn too_long_task_id_returns_invalid_params() {
1536 use crate::handler::limits::HandlerLimits;
1538 use a2a_protocol_types::task::TaskId;
1539
1540 let handler = RequestHandlerBuilder::new(DummyExecutor)
1541 .with_handler_limits(HandlerLimits::default().with_max_id_length(10))
1542 .build()
1543 .unwrap();
1544 let mut params = make_params(None);
1545 params.message.task_id = Some(TaskId::new("a".repeat(20)));
1546
1547 let result = handler.on_send_message(params, false, None).await;
1548 assert!(
1549 matches!(result, Err(ServerError::InvalidParams(ref msg)) if msg.contains("maximum length")),
1550 "expected InvalidParams for too-long task_id"
1551 );
1552 }
1553
1554 #[tokio::test]
1555 async fn empty_task_id_returns_invalid_params() {
1556 use a2a_protocol_types::task::TaskId;
1558
1559 let handler = make_handler();
1560 let mut params = make_params(None);
1561 params.message.task_id = Some(TaskId::new(""));
1562
1563 let result = handler.on_send_message(params, false, None).await;
1564 assert!(
1565 matches!(result, Err(ServerError::InvalidParams(ref msg)) if msg.contains("empty")),
1566 "expected InvalidParams for empty task_id"
1567 );
1568 }
1569
1570 #[tokio::test]
1571 async fn task_id_mismatch_returns_invalid_params() {
1572 use a2a_protocol_types::task::{Task, TaskId, TaskState, TaskStatus};
1574
1575 let handler = make_handler();
1576
1577 let task = Task {
1579 id: TaskId::new("stored-task-id"),
1580 context_id: ContextId::new("ctx-existing"),
1581 status: TaskStatus::new(TaskState::InputRequired),
1582 history: None,
1583 artifacts: None,
1584 metadata: None,
1585 };
1586 handler.task_store.save(&task).await.unwrap();
1587
1588 let mut params = make_params(Some("ctx-existing"));
1590 params.message.task_id = Some(TaskId::new("different-task-id"));
1591
1592 let result = handler.on_send_message(params, false, None).await;
1593 assert!(
1594 matches!(result, Err(ServerError::InvalidParams(ref msg)) if msg.contains("does not match")),
1595 "expected InvalidParams for task_id mismatch, got: {result:?}"
1596 );
1597 }
1598
1599 #[tokio::test]
1600 async fn send_message_records_user_message_in_history() {
1601 let handler = make_handler();
1604 let result = handler
1605 .on_send_message(make_params(None), false, None)
1606 .await
1607 .expect("send should succeed");
1608 let task_id = match result {
1609 SendMessageResult::Response(SendMessageResponse::Task(t)) => t.id,
1610 other => panic!("expected task response, got {other:?}"),
1611 };
1612 let stored = handler
1613 .task_store
1614 .get(&task_id)
1615 .await
1616 .expect("get")
1617 .expect("task stored");
1618 let history = stored.history.expect("history populated on send");
1619 assert_eq!(history.len(), 1, "exactly the incoming user message");
1620 assert_eq!(history[0].role, MessageRole::User);
1621 assert_eq!(
1622 history[0].parts[0].text_content(),
1623 Some("hello"),
1624 "history records the message content"
1625 );
1626 }
1627
1628 #[tokio::test]
1629 async fn continuation_appends_history_and_preserves_artifacts() {
1630 use a2a_protocol_types::artifact::Artifact;
1633 let handler = make_handler();
1634 let prior = Task {
1635 id: TaskId::new("cont-task"),
1636 context_id: ContextId::new("ctx-cont"),
1637 status: TaskStatus::new(TaskState::InputRequired),
1638 history: Some(vec![Message {
1639 id: MessageId::new("m-prior"),
1640 role: MessageRole::User,
1641 parts: vec![Part::text("first turn")],
1642 context_id: None,
1643 task_id: None,
1644 reference_task_ids: None,
1645 extensions: None,
1646 metadata: None,
1647 }]),
1648 artifacts: Some(vec![Artifact::new("a1", vec![Part::text("turn-1 output")])]),
1649 metadata: Some(serde_json::json!({"k": "v"})),
1650 };
1651 handler.task_store.save(&prior).await.unwrap();
1652
1653 let mut params = make_params(Some("ctx-cont"));
1654 params.message.task_id = Some(TaskId::new("cont-task"));
1655 handler
1656 .on_send_message(params, false, None)
1657 .await
1658 .expect("continuation should succeed");
1659
1660 let stored = handler
1661 .task_store
1662 .get(&TaskId::new("cont-task"))
1663 .await
1664 .expect("get")
1665 .expect("task stored");
1666 let history = stored.history.expect("history preserved");
1667 assert_eq!(history.len(), 2, "prior message + continuation message");
1668 assert_eq!(history[0].parts[0].text_content(), Some("first turn"));
1669 assert_eq!(history[1].parts[0].text_content(), Some("hello"));
1670 assert!(
1671 stored.artifacts.as_ref().is_some_and(|a| a.len() == 1),
1672 "continuation must not wipe accumulated artifacts"
1673 );
1674 assert_eq!(
1675 stored.metadata,
1676 Some(serde_json::json!({"k": "v"})),
1677 "continuation must not wipe task metadata"
1678 );
1679 }
1680
1681 #[tokio::test]
1682 async fn history_is_capped_at_max_messages() {
1683 let handler = make_handler();
1685 let mut long_history: Vec<Message> = (0..MAX_TASK_HISTORY_MESSAGES)
1686 .map(|i| Message {
1687 id: MessageId::new(format!("m-{i}")),
1688 role: MessageRole::User,
1689 parts: vec![Part::text(format!("msg {i}"))],
1690 context_id: None,
1691 task_id: None,
1692 reference_task_ids: None,
1693 extensions: None,
1694 metadata: None,
1695 })
1696 .collect();
1697 long_history[0].parts = vec![Part::text("OLDEST")];
1698 let prior = Task {
1699 id: TaskId::new("cap-task"),
1700 context_id: ContextId::new("ctx-cap"),
1701 status: TaskStatus::new(TaskState::InputRequired),
1702 history: Some(long_history),
1703 artifacts: None,
1704 metadata: None,
1705 };
1706 handler.task_store.save(&prior).await.unwrap();
1707
1708 let mut params = make_params(Some("ctx-cap"));
1709 params.message.task_id = Some(TaskId::new("cap-task"));
1710 handler
1711 .on_send_message(params, false, None)
1712 .await
1713 .expect("continuation should succeed");
1714
1715 let stored = handler
1716 .task_store
1717 .get(&TaskId::new("cap-task"))
1718 .await
1719 .unwrap()
1720 .unwrap();
1721 let history = stored.history.unwrap();
1722 assert_eq!(history.len(), MAX_TASK_HISTORY_MESSAGES, "capped");
1723 assert_ne!(
1724 history[0].parts[0].text_content(),
1725 Some("OLDEST"),
1726 "the oldest message is dropped first"
1727 );
1728 assert_eq!(
1729 history[MAX_TASK_HISTORY_MESSAGES - 1].parts[0].text_content(),
1730 Some("hello"),
1731 "the newest message is retained"
1732 );
1733 }
1734
1735 #[tokio::test]
1736 async fn send_response_omits_history_by_default_and_honors_history_length() {
1737 use a2a_protocol_types::params::SendMessageConfiguration;
1742 let handler = make_handler();
1743
1744 let result = handler
1745 .on_send_message(make_params(Some("ctx-resp")), false, None)
1746 .await
1747 .expect("send should succeed");
1748 let task = match result {
1749 SendMessageResult::Response(SendMessageResponse::Task(t)) => t,
1750 other => panic!("expected task response, got {other:?}"),
1751 };
1752 assert!(
1753 task.history.is_none(),
1754 "default send response must not echo history"
1755 );
1756 let stored = handler
1757 .task_store
1758 .get(&task.id)
1759 .await
1760 .unwrap()
1761 .expect("task stored");
1762 assert_eq!(
1763 stored.history.as_ref().map(Vec::len),
1764 Some(1),
1765 "the store still keeps the full history"
1766 );
1767
1768 let mut params = make_params(Some("ctx-resp"));
1769 params.message.task_id = Some(task.id.clone());
1770 params.configuration = Some(SendMessageConfiguration {
1771 history_length: Some(10),
1772 ..Default::default()
1773 });
1774 let result = handler
1775 .on_send_message(params, false, None)
1776 .await
1777 .expect("continuation should succeed");
1778 let task = match result {
1779 SendMessageResult::Response(SendMessageResponse::Task(t)) => t,
1780 other => panic!("expected task response, got {other:?}"),
1781 };
1782 assert_eq!(
1783 task.history.as_ref().map(Vec::len),
1784 Some(2),
1785 "historyLength=10 returns the (2) stored messages"
1786 );
1787 }
1788
1789 #[tokio::test]
1790 async fn send_message_with_request_metadata() {
1791 let handler = make_handler();
1793 let mut params = make_params(None);
1794 params.metadata = Some(serde_json::json!({"key": "value"}));
1795
1796 let result = handler.on_send_message(params, false, None).await;
1797 assert!(
1798 result.is_ok(),
1799 "send_message with request metadata should succeed"
1800 );
1801 }
1802
1803 #[tokio::test]
1804 async fn send_message_error_path_records_metrics() {
1805 use crate::call_context::CallContext;
1807 use crate::interceptor::ServerInterceptor;
1808 use std::future::Future;
1809 use std::pin::Pin;
1810
1811 struct FailInterceptor;
1812 impl ServerInterceptor for FailInterceptor {
1813 fn before<'a>(
1814 &'a self,
1815 _ctx: &'a CallContext,
1816 ) -> Pin<Box<dyn Future<Output = a2a_protocol_types::error::A2aResult<()>> + Send + 'a>>
1817 {
1818 Box::pin(async {
1819 Err(a2a_protocol_types::error::A2aError::internal(
1820 "forced failure",
1821 ))
1822 })
1823 }
1824 fn after<'a>(
1825 &'a self,
1826 _ctx: &'a CallContext,
1827 ) -> Pin<Box<dyn Future<Output = a2a_protocol_types::error::A2aResult<()>> + Send + 'a>>
1828 {
1829 Box::pin(async { Ok(()) })
1830 }
1831 }
1832
1833 let handler = RequestHandlerBuilder::new(DummyExecutor)
1834 .with_interceptor(FailInterceptor)
1835 .build()
1836 .unwrap();
1837
1838 let params = make_params(None);
1839 let result = handler.on_send_message(params, false, None).await;
1840 assert!(
1841 result.is_err(),
1842 "send_message should fail when interceptor rejects, exercising error metrics path"
1843 );
1844 }
1845
1846 #[tokio::test]
1847 async fn send_streaming_message_error_path_records_metrics() {
1848 use crate::call_context::CallContext;
1850 use crate::interceptor::ServerInterceptor;
1851 use std::future::Future;
1852 use std::pin::Pin;
1853
1854 struct FailInterceptor;
1855 impl ServerInterceptor for FailInterceptor {
1856 fn before<'a>(
1857 &'a self,
1858 _ctx: &'a CallContext,
1859 ) -> Pin<Box<dyn Future<Output = a2a_protocol_types::error::A2aResult<()>> + Send + 'a>>
1860 {
1861 Box::pin(async {
1862 Err(a2a_protocol_types::error::A2aError::internal(
1863 "forced failure",
1864 ))
1865 })
1866 }
1867 fn after<'a>(
1868 &'a self,
1869 _ctx: &'a CallContext,
1870 ) -> Pin<Box<dyn Future<Output = a2a_protocol_types::error::A2aResult<()>> + Send + 'a>>
1871 {
1872 Box::pin(async { Ok(()) })
1873 }
1874 }
1875
1876 let handler = RequestHandlerBuilder::new(DummyExecutor)
1877 .with_interceptor(FailInterceptor)
1878 .build()
1879 .unwrap();
1880
1881 let params = make_params(None);
1882 let result = handler.on_send_message(params, true, None).await;
1883 assert!(
1884 result.is_err(),
1885 "streaming send_message should fail when interceptor rejects"
1886 );
1887 }
1888
1889 #[tokio::test]
1890 async fn streaming_mode_returns_stream_result() {
1891 let handler = make_handler();
1893 let params = make_params(None);
1894
1895 let result = handler.on_send_message(params, true, None).await;
1896 assert!(
1897 matches!(result, Ok(SendMessageResult::Stream(_))),
1898 "expected Stream result in streaming mode"
1899 );
1900 }
1901
1902 #[tokio::test]
1903 async fn send_message_with_stored_task_continuation() {
1904 use a2a_protocol_types::task::{Task, TaskState, TaskStatus};
1907
1908 let handler = make_handler();
1909
1910 let task = Task {
1912 id: TaskId::new("existing-task"),
1913 context_id: ContextId::new("continue-ctx"),
1914 status: TaskStatus::new(TaskState::InputRequired),
1915 history: None,
1916 artifacts: None,
1917 metadata: None,
1918 };
1919 handler.task_store.save(&task).await.unwrap();
1920
1921 let params = make_params(Some("continue-ctx"));
1923 let result = handler.on_send_message(params, false, None).await;
1924 assert!(
1925 result.is_ok(),
1926 "send_message with existing non-terminal context should succeed"
1927 );
1928 }
1929
1930 #[tokio::test]
1931 async fn send_message_to_terminal_task_returns_unsupported_operation() {
1932 use a2a_protocol_types::task::{Task, TaskState, TaskStatus};
1935
1936 let handler = make_handler();
1937
1938 let task = Task {
1940 id: TaskId::new("done-task"),
1941 context_id: ContextId::new("done-ctx"),
1942 status: TaskStatus::new(TaskState::Completed),
1943 history: None,
1944 artifacts: None,
1945 metadata: None,
1946 };
1947 handler.task_store.save(&task).await.unwrap();
1948
1949 let mut params = make_params(Some("done-ctx"));
1951 params.message.task_id = Some(TaskId::new("done-task"));
1952 let result = handler.on_send_message(params, false, None).await;
1953 assert!(
1954 matches!(result, Err(ServerError::UnsupportedOperation(ref msg)) if msg.contains("terminal")),
1955 "expected UnsupportedOperation for terminal task, got: {result:?}"
1956 );
1957 }
1958
1959 #[tokio::test]
1960 async fn send_message_to_terminal_context_without_task_id_creates_new_task() {
1961 use a2a_protocol_types::task::{Task, TaskState, TaskStatus};
1964
1965 let handler = make_handler();
1966
1967 let task = Task {
1969 id: TaskId::new("old-task"),
1970 context_id: ContextId::new("reuse-ctx"),
1971 status: TaskStatus::new(TaskState::Completed),
1972 history: None,
1973 artifacts: None,
1974 metadata: None,
1975 };
1976 handler.task_store.save(&task).await.unwrap();
1977
1978 let params = make_params(Some("reuse-ctx"));
1980 let result = handler.on_send_message(params, false, None).await;
1981 assert!(
1982 result.is_ok(),
1983 "should create new task on terminal context, got: {result:?}"
1984 );
1985 }
1986
1987 #[tokio::test]
1988 async fn send_message_with_headers() {
1989 let handler = make_handler();
1991 let params = make_params(None);
1992 let mut headers = HashMap::new();
1993 headers.insert("authorization".to_string(), "Bearer test-token".to_string());
1994
1995 let result = handler.on_send_message(params, false, Some(&headers)).await;
1996 let send_result = result.expect("send_message with headers should succeed");
1997 assert!(
1998 matches!(
1999 send_result,
2000 SendMessageResult::Response(SendMessageResponse::Task(_))
2001 ),
2002 "expected Response(Task) for send with headers"
2003 );
2004 }
2005
2006 #[tokio::test]
2007 async fn duplicate_task_id_without_context_match_returns_error() {
2008 use a2a_protocol_types::task::{Task, TaskId as TId, TaskState, TaskStatus};
2010
2011 let handler = make_handler();
2012
2013 let task = Task {
2015 id: TId::new("dup-task"),
2016 context_id: ContextId::new("other-ctx"),
2017 status: TaskStatus::new(TaskState::Completed),
2018 history: None,
2019 artifacts: None,
2020 metadata: None,
2021 };
2022 handler.task_store.save(&task).await.unwrap();
2023
2024 let mut params = make_params(Some("brand-new-ctx"));
2026 params.message.task_id = Some(TId::new("dup-task"));
2027
2028 let result = handler.on_send_message(params, false, None).await;
2029 assert!(
2030 matches!(result, Err(ServerError::InvalidParams(ref msg)) if msg.contains("different context")),
2031 "expected InvalidParams for task_id in different context, got: {result:?}"
2032 );
2033 }
2034
2035 #[tokio::test]
2036 async fn unknown_task_id_returns_task_not_found() {
2037 use a2a_protocol_types::task::TaskId as TId;
2039
2040 let handler = make_handler();
2041
2042 let mut params = make_params(Some("fresh-ctx"));
2044 params.message.task_id = Some(TId::new("nonexistent-task"));
2045
2046 let result = handler.on_send_message(params, false, None).await;
2047 assert!(
2048 matches!(result, Err(ServerError::TaskNotFound(_))),
2049 "expected TaskNotFound for unknown task_id, got: {result:?}"
2050 );
2051 }
2052
2053 #[tokio::test]
2054 async fn send_message_with_tenant() {
2055 let handler = make_handler();
2057 let mut params = make_params(None);
2058 params.tenant = Some("test-tenant".to_string());
2059
2060 let result = handler.on_send_message(params, false, None).await;
2061 let send_result = result.expect("send_message with tenant should succeed");
2062 assert!(
2063 matches!(
2064 send_result,
2065 SendMessageResult::Response(SendMessageResponse::Task(_))
2066 ),
2067 "expected Response(Task) for send with tenant"
2068 );
2069 }
2070
2071 #[tokio::test]
2072 async fn executor_timeout_returns_failed_task() {
2073 use a2a_protocol_types::error::A2aResult;
2075 use std::time::Duration;
2076
2077 struct SlowExecutor;
2078 impl crate::executor::AgentExecutor for SlowExecutor {
2079 fn execute<'a>(
2080 &'a self,
2081 _ctx: &'a crate::request_context::RequestContext,
2082 _queue: &'a dyn crate::streaming::EventQueueWriter,
2083 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = A2aResult<()>> + Send + 'a>>
2084 {
2085 Box::pin(async {
2086 tokio::time::sleep(Duration::from_secs(60)).await;
2087 Ok(())
2088 })
2089 }
2090 }
2091
2092 let handler = RequestHandlerBuilder::new(SlowExecutor)
2093 .with_executor_timeout(Duration::from_millis(50))
2094 .build()
2095 .unwrap();
2096
2097 let params = make_params(None);
2098 let result = handler.on_send_message(params, false, None).await;
2100 assert!(
2102 result.is_ok(),
2103 "executor timeout should still return a task result"
2104 );
2105 }
2106
2107 #[tokio::test]
2108 async fn executor_failure_writes_failed_event() {
2109 use a2a_protocol_types::error::{A2aError, A2aResult};
2111
2112 struct FailExecutor;
2113 impl crate::executor::AgentExecutor for FailExecutor {
2114 fn execute<'a>(
2115 &'a self,
2116 _ctx: &'a crate::request_context::RequestContext,
2117 _queue: &'a dyn crate::streaming::EventQueueWriter,
2118 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = A2aResult<()>> + Send + 'a>>
2119 {
2120 Box::pin(async { Err(A2aError::internal("executor exploded")) })
2121 }
2122 }
2123
2124 let handler = RequestHandlerBuilder::new(FailExecutor).build().unwrap();
2125 let params = make_params(None);
2126
2127 let result = handler.on_send_message(params, false, None).await;
2128 assert!(
2130 result.is_ok(),
2131 "executor failure should produce a task result"
2132 );
2133 }
2134
2135 #[tokio::test]
2136 async fn cancellation_token_sweep_runs_when_map_is_full() {
2137 use crate::handler::limits::HandlerLimits;
2140
2141 struct SlowExec;
2143 impl crate::executor::AgentExecutor for SlowExec {
2144 fn execute<'a>(
2145 &'a self,
2146 _ctx: &'a crate::request_context::RequestContext,
2147 _queue: &'a dyn crate::streaming::EventQueueWriter,
2148 ) -> std::pin::Pin<
2149 Box<
2150 dyn std::future::Future<Output = a2a_protocol_types::error::A2aResult<()>>
2151 + Send
2152 + 'a,
2153 >,
2154 > {
2155 Box::pin(async {
2156 tokio::time::sleep(std::time::Duration::from_secs(10)).await;
2158 Ok(())
2159 })
2160 }
2161 }
2162
2163 let handler = RequestHandlerBuilder::new(SlowExec)
2164 .with_handler_limits(HandlerLimits::default().with_max_cancellation_tokens(2))
2165 .build()
2166 .unwrap();
2167
2168 for _ in 0..3 {
2171 let params = make_params(None);
2172 let _ = handler.on_send_message(params, true, None).await;
2173 }
2174 handler.shutdown().await;
2177 }
2178
2179 #[tokio::test]
2180 async fn stale_cancellation_tokens_cleaned_up() {
2181 use crate::handler::limits::HandlerLimits;
2183 use std::time::Duration;
2184
2185 struct SlowExec2;
2187 impl crate::executor::AgentExecutor for SlowExec2 {
2188 fn execute<'a>(
2189 &'a self,
2190 _ctx: &'a crate::request_context::RequestContext,
2191 _queue: &'a dyn crate::streaming::EventQueueWriter,
2192 ) -> std::pin::Pin<
2193 Box<
2194 dyn std::future::Future<Output = a2a_protocol_types::error::A2aResult<()>>
2195 + Send
2196 + 'a,
2197 >,
2198 > {
2199 Box::pin(async {
2200 tokio::time::sleep(Duration::from_secs(10)).await;
2201 Ok(())
2202 })
2203 }
2204 }
2205
2206 let handler = RequestHandlerBuilder::new(SlowExec2)
2207 .with_handler_limits(
2208 HandlerLimits::default()
2209 .with_max_cancellation_tokens(2)
2210 .with_max_token_age(Duration::from_millis(1)),
2212 )
2213 .build()
2214 .unwrap();
2215
2216 for _ in 0..2 {
2218 let params = make_params(None);
2219 let _ = handler.on_send_message(params, true, None).await;
2220 }
2221
2222 tokio::time::sleep(Duration::from_millis(50)).await;
2224
2225 let params = make_params(None);
2229 let _ = handler.on_send_message(params, true, None).await;
2230
2231 handler.shutdown().await;
2233 }
2234
2235 #[tokio::test]
2236 async fn streaming_executor_failure_writes_error_event() {
2237 use a2a_protocol_types::error::{A2aError, A2aResult};
2239
2240 struct FailExecutor;
2241 impl crate::executor::AgentExecutor for FailExecutor {
2242 fn execute<'a>(
2243 &'a self,
2244 _ctx: &'a crate::request_context::RequestContext,
2245 _queue: &'a dyn crate::streaming::EventQueueWriter,
2246 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = A2aResult<()>> + Send + 'a>>
2247 {
2248 Box::pin(async { Err(A2aError::internal("streaming fail")) })
2249 }
2250 }
2251
2252 let handler = RequestHandlerBuilder::new(FailExecutor).build().unwrap();
2253 let params = make_params(None);
2254
2255 let result = handler.on_send_message(params, true, None).await;
2256 assert!(
2257 matches!(result, Ok(SendMessageResult::Stream(_))),
2258 "streaming executor failure should still return stream"
2259 );
2260 }
2261
2262 #[tokio::test]
2263 async fn input_required_continuation_reuses_task_id() {
2264 use a2a_protocol_types::task::{Task, TaskId, TaskState, TaskStatus};
2268
2269 let handler = make_handler();
2270
2271 let existing_task_id = TaskId::new("input-required-task");
2273 let task = Task {
2274 id: existing_task_id.clone(),
2275 context_id: ContextId::new("ctx-input"),
2276 status: TaskStatus::new(TaskState::InputRequired),
2277 history: None,
2278 artifacts: None,
2279 metadata: None,
2280 };
2281 handler.task_store.save(&task).await.unwrap();
2282
2283 let mut params = make_params(Some("ctx-input"));
2285 params.message.task_id = Some(existing_task_id.clone());
2286
2287 let result = handler.on_send_message(params, false, None).await;
2288 let send_result = result.expect("continuation should succeed");
2289 match send_result {
2290 SendMessageResult::Response(SendMessageResponse::Task(t)) => {
2291 assert_eq!(
2292 t.id, existing_task_id,
2293 "task_id should be reused for input-required continuation"
2294 );
2295 }
2296 _ => panic!("expected Response(Task)"),
2297 }
2298 }
2299
2300 #[test]
2303 fn second_send_blocked_iff_token_live() {
2304 let live = CancellationEntry {
2305 token: tokio_util::sync::CancellationToken::new(),
2306 created_at: Instant::now(),
2307 };
2308 assert!(
2309 second_send_blocked(&live),
2310 "a live token means an executor is in flight → block the second send"
2311 );
2312
2313 let token = tokio_util::sync::CancellationToken::new();
2314 token.cancel();
2315 let cancelled = CancellationEntry {
2316 token,
2317 created_at: Instant::now(),
2318 };
2319 assert!(
2320 !second_send_blocked(&cancelled),
2321 "a cancelled token no longer blocks a resend"
2322 );
2323 }
2324
2325 #[test]
2326 fn token_aged_at_or_past_max_age() {
2327 let max = std::time::Duration::from_secs(3600);
2328 assert!(
2329 !token_aged(std::time::Duration::from_secs(3599), max),
2330 "younger than max is not aged"
2331 );
2332 assert!(
2335 token_aged(std::time::Duration::from_secs(3600), max),
2336 "exactly max_age is aged"
2337 );
2338 assert!(token_aged(std::time::Duration::from_secs(3601), max));
2339 }
2340
2341 #[test]
2342 fn evict_aged_token_only_when_queue_gone() {
2343 assert!(
2344 evict_aged_token(false),
2345 "no live queue → the executor finished → evict the lingering token"
2346 );
2347 assert!(
2348 !evict_aged_token(true),
2349 "a live queue means the task is still running → keep its token"
2350 );
2351 }
2352
2353 #[test]
2358 fn token_still_evictable_spares_fresh_live_token() {
2359 let max_age = std::time::Duration::from_secs(3600);
2360 let now = Instant::now();
2361
2362 let fresh = CancellationEntry {
2364 token: tokio_util::sync::CancellationToken::new(),
2365 created_at: now,
2366 };
2367 assert!(
2368 !token_still_evictable(&fresh, now, max_age),
2369 "a fresh live token must never be swept"
2370 );
2371
2372 let cancelled = CancellationEntry {
2374 token: tokio_util::sync::CancellationToken::new(),
2375 created_at: now,
2376 };
2377 cancelled.token.cancel();
2378 assert!(token_still_evictable(&cancelled, now, max_age));
2379
2380 let aged = CancellationEntry {
2387 token: tokio_util::sync::CancellationToken::new(),
2388 created_at: now,
2389 };
2390 let later = now
2391 .checked_add(max_age)
2392 .expect("now + max_age is representable");
2393 assert!(token_still_evictable(&aged, later, max_age));
2394 }
2395}