1use std::sync::Arc;
13use std::time::Duration;
14
15use a2a_protocol_types::agent_card::AgentCard;
16
17use crate::error::ServerResult;
18use crate::executor::AgentExecutor;
19use crate::handler::{HandlerLimits, RequestHandler};
20use crate::interceptor::{ServerInterceptor, ServerInterceptorChain};
21use crate::metrics::{Metrics, NoopMetrics};
22use crate::push::{InMemoryPushConfigStore, PushConfigStore, PushSender};
23use crate::store::{InMemoryTaskStore, TaskStore, TaskStoreConfig};
24use crate::streaming::EventQueueManager;
25use crate::tenant_config::PerTenantConfig;
26use crate::tenant_resolver::TenantResolver;
27
28pub const DEFAULT_MAX_CONCURRENT_STREAMS: usize = 1024;
37
38pub struct RequestHandlerBuilder {
60 executor: Arc<dyn AgentExecutor>,
61 task_store: Option<Arc<dyn TaskStore>>,
62 task_store_config: TaskStoreConfig,
63 push_config_store: Option<Arc<dyn PushConfigStore>>,
64 push_sender: Option<Arc<dyn PushSender>>,
65 interceptors: ServerInterceptorChain,
66 agent_card: Option<AgentCard>,
67 executor_timeout: Option<Duration>,
68 event_queue_capacity: Option<usize>,
69 max_event_size: Option<usize>,
70 max_concurrent_streams: Option<usize>,
71 event_queue_write_timeout: Option<Duration>,
72 metrics: Arc<dyn Metrics>,
73 handler_limits: HandlerLimits,
74 tenant_resolver: Option<Arc<dyn TenantResolver>>,
75 tenant_config: Option<PerTenantConfig>,
76 require_resolved_tenant: bool,
77 allow_unauthenticated_extended_card: bool,
78}
79
80impl RequestHandlerBuilder {
81 #[must_use]
85 pub fn new(executor: impl AgentExecutor) -> Self {
86 Self {
87 executor: Arc::new(executor),
88 task_store: None,
89 task_store_config: TaskStoreConfig::default(),
90 push_config_store: None,
91 push_sender: None,
92 interceptors: ServerInterceptorChain::new(),
93 agent_card: None,
94 executor_timeout: None,
95 event_queue_capacity: None,
96 max_event_size: None,
97 max_concurrent_streams: None,
98 event_queue_write_timeout: None,
99 metrics: Arc::new(NoopMetrics),
100 handler_limits: HandlerLimits::default(),
101 tenant_resolver: None,
102 tenant_config: None,
103 require_resolved_tenant: false,
104 allow_unauthenticated_extended_card: false,
105 }
106 }
107
108 #[must_use]
110 pub fn with_task_store(mut self, store: impl TaskStore + 'static) -> Self {
111 self.task_store = Some(Arc::new(store));
112 self
113 }
114
115 #[must_use]
120 pub fn with_task_store_arc(mut self, store: Arc<dyn TaskStore>) -> Self {
121 self.task_store = Some(store);
122 self
123 }
124
125 #[must_use]
133 pub fn with_task_store_config(mut self, config: TaskStoreConfig) -> Self {
134 debug_assert!(
135 self.task_store.is_none(),
136 "with_task_store_config() called after with_task_store(); \
137 the config will be ignored because a custom store was already set"
138 );
139 self.task_store_config = config;
140 self
141 }
142
143 #[must_use]
145 pub fn with_push_config_store(mut self, store: impl PushConfigStore + 'static) -> Self {
146 self.push_config_store = Some(Arc::new(store));
147 self
148 }
149
150 #[must_use]
152 pub fn with_push_sender(mut self, sender: impl PushSender + 'static) -> Self {
153 self.push_sender = Some(Arc::new(sender));
154 self
155 }
156
157 #[must_use]
159 pub fn with_interceptor(mut self, interceptor: impl ServerInterceptor + 'static) -> Self {
160 self.interceptors.push(Arc::new(interceptor));
161 self
162 }
163
164 #[must_use]
174 pub const fn with_executor_timeout(mut self, timeout: Duration) -> Self {
175 self.executor_timeout = Some(timeout);
176 self
177 }
178
179 #[must_use]
181 pub fn with_agent_card(mut self, card: AgentCard) -> Self {
182 self.agent_card = Some(card);
183 self
184 }
185
186 #[must_use]
194 pub const fn with_event_queue_capacity(mut self, capacity: usize) -> Self {
195 self.event_queue_capacity = Some(capacity);
196 self
197 }
198
199 #[must_use]
204 pub const fn with_max_event_size(mut self, max_event_size: usize) -> Self {
205 self.max_event_size = Some(max_event_size);
206 self
207 }
208
209 #[must_use]
216 pub const fn with_max_concurrent_streams(mut self, max: usize) -> Self {
217 self.max_concurrent_streams = Some(max);
218 self
219 }
220
221 #[deprecated(
228 since = "0.7.0",
229 note = "has no effect: event queue writes never block; slow consumers \
230 receive an explicit lag error instead. Will be removed in 0.8."
231 )]
232 #[must_use]
233 pub const fn with_event_queue_write_timeout(mut self, timeout: Duration) -> Self {
234 self.event_queue_write_timeout = Some(timeout);
235 self
236 }
237
238 #[must_use]
242 pub const fn with_handler_limits(mut self, limits: HandlerLimits) -> Self {
243 self.handler_limits = limits;
244 self
245 }
246
247 #[must_use]
251 pub fn with_metrics(mut self, metrics: impl Metrics + 'static) -> Self {
252 self.metrics = Arc::new(metrics);
253 self
254 }
255
256 #[must_use]
265 pub fn with_tenant_resolver(mut self, resolver: impl TenantResolver) -> Self {
266 self.tenant_resolver = Some(Arc::new(resolver));
267 self
268 }
269
270 #[must_use]
282 pub const fn require_resolved_tenant(mut self) -> Self {
283 self.require_resolved_tenant = true;
284 self
285 }
286
287 #[must_use]
300 pub const fn allow_unauthenticated_extended_card(mut self) -> Self {
301 self.allow_unauthenticated_extended_card = true;
302 self
303 }
304
305 #[must_use]
314 pub fn with_tenant_config(mut self, config: PerTenantConfig) -> Self {
315 self.tenant_config = Some(config);
316 self
317 }
318
319 #[allow(clippy::too_many_lines)]
327 pub fn build(self) -> ServerResult<RequestHandler> {
328 if let Some(ref card) = self.agent_card {
330 if card.supported_interfaces.is_empty() {
331 return Err(crate::error::ServerError::InvalidParams(
332 "agent card must have at least one supported interface".into(),
333 ));
334 }
335 }
336
337 if let Some(timeout) = self.executor_timeout {
339 if timeout.is_zero() {
340 return Err(crate::error::ServerError::InvalidParams(
341 "executor timeout must be greater than zero".into(),
342 ));
343 }
344 }
345
346 if self.handler_limits.max_id_length == 0 {
348 return Err(crate::error::ServerError::InvalidParams(
349 "max_id_length must be greater than zero".into(),
350 ));
351 }
352 if self.handler_limits.max_metadata_size == 0 {
353 return Err(crate::error::ServerError::InvalidParams(
354 "max_metadata_size must be greater than zero".into(),
355 ));
356 }
357 if self.handler_limits.push_delivery_timeout.is_zero() {
358 return Err(crate::error::ServerError::InvalidParams(
359 "push_delivery_timeout must be greater than zero".into(),
360 ));
361 }
362 let (required_extensions, declared_extensions) = self
365 .agent_card
366 .as_ref()
367 .and_then(|c| c.capabilities.extensions.as_ref())
368 .map(|exts| {
369 let declared: Vec<String> = exts.iter().map(|e| e.uri.clone()).collect();
370 let required: Vec<String> = exts
371 .iter()
372 .filter(|e| e.required == Some(true))
373 .map(|e| e.uri.clone())
374 .collect();
375 (required, declared)
376 })
377 .unwrap_or_default();
378
379 Ok(RequestHandler {
380 executor: self.executor,
381 task_store: self.task_store.unwrap_or_else(|| {
382 Arc::new(InMemoryTaskStore::with_config(self.task_store_config))
383 }),
384 push_config_store: self
385 .push_config_store
386 .unwrap_or_else(|| Arc::new(InMemoryPushConfigStore::new())),
387 push_sender: self.push_sender,
388 event_queue_manager: {
389 let mut mgr = self
390 .event_queue_capacity
391 .map_or_else(EventQueueManager::new, EventQueueManager::with_capacity);
392 if let Some(max_size) = self.max_event_size {
393 mgr = mgr.with_max_event_size(max_size);
394 }
395 if let Some(timeout) = self.event_queue_write_timeout {
396 #[allow(deprecated)] {
398 mgr = mgr.with_write_timeout(timeout);
399 }
400 }
401 mgr = mgr.with_max_concurrent_queues(
402 self.max_concurrent_streams
403 .unwrap_or(DEFAULT_MAX_CONCURRENT_STREAMS),
404 );
405 mgr = mgr.with_metrics(Arc::clone(&self.metrics));
406 mgr
407 },
408 interceptors: self.interceptors,
409 agent_card: self.agent_card,
410 executor_timeout: self.executor_timeout,
411 metrics: self.metrics,
412 limits: self.handler_limits,
413 tenant_resolver: self.tenant_resolver,
414 require_resolved_tenant: self.require_resolved_tenant,
415 allow_unauthenticated_extended_card: self.allow_unauthenticated_extended_card,
416 required_extensions,
417 declared_extensions,
418 tenant_config: self.tenant_config,
419 cancellation_tokens: Arc::new(tokio::sync::RwLock::new(
420 std::collections::HashMap::new(),
421 )),
422 context_locks: Arc::new(tokio::sync::RwLock::new(std::collections::HashMap::new())),
423 })
424 }
425}
426
427impl std::fmt::Debug for RequestHandlerBuilder {
428 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
429 f.debug_struct("RequestHandlerBuilder")
430 .field("executor", &"<dyn AgentExecutor>")
431 .field("task_store", &self.task_store.is_some())
432 .field("task_store_config", &self.task_store_config)
433 .field("push_config_store", &self.push_config_store.is_some())
434 .field("push_sender", &self.push_sender.is_some())
435 .field("interceptors", &self.interceptors)
436 .field("agent_card", &self.agent_card.is_some())
437 .field("executor_timeout", &self.executor_timeout)
438 .field("event_queue_capacity", &self.event_queue_capacity)
439 .field("max_event_size", &self.max_event_size)
440 .field("max_concurrent_streams", &self.max_concurrent_streams)
441 .field("event_queue_write_timeout", &self.event_queue_write_timeout)
442 .field("metrics", &"<dyn Metrics>")
443 .field("handler_limits", &self.handler_limits)
444 .field("tenant_resolver", &self.tenant_resolver.is_some())
445 .field("tenant_config", &self.tenant_config)
446 .field("require_resolved_tenant", &self.require_resolved_tenant)
447 .field(
448 "allow_unauthenticated_extended_card",
449 &self.allow_unauthenticated_extended_card,
450 )
451 .finish()
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458 use crate::agent_executor;
459
460 struct TestExecutor;
461
462 agent_executor!(TestExecutor, |_ctx, _queue| async { Ok(()) });
463
464 #[test]
465 fn builder_defaults_build_ok() {
466 let handler = RequestHandlerBuilder::new(TestExecutor).build();
467 let h = handler.expect("default builder should succeed");
468 assert!(
469 h.tenant_resolver().is_none(),
470 "default builder should have no tenant resolver"
471 );
472 assert!(
473 h.tenant_config().is_none(),
474 "default builder should have no tenant config"
475 );
476 }
477
478 #[test]
479 fn builder_zero_executor_timeout_errors() {
480 let result = RequestHandlerBuilder::new(TestExecutor)
481 .with_executor_timeout(Duration::ZERO)
482 .build();
483 assert!(result.is_err());
484 }
485
486 #[test]
487 fn builder_empty_agent_card_interfaces_errors() {
488 use a2a_protocol_types::{AgentCapabilities, AgentCard};
489
490 let card = AgentCard {
491 url: None,
492 name: "empty".into(),
493 version: "1.0".into(),
494 description: "No interfaces".into(),
495 supported_interfaces: vec![],
496 provider: None,
497 icon_url: None,
498 documentation_url: None,
499 capabilities: AgentCapabilities::none(),
500 security_schemes: None,
501 security_requirements: None,
502 default_input_modes: vec![],
503 default_output_modes: vec![],
504 skills: vec![],
505 signatures: None,
506 };
507
508 let result = RequestHandlerBuilder::new(TestExecutor)
509 .with_agent_card(card)
510 .build();
511 assert!(result.is_err());
512 }
513
514 #[test]
515 fn builder_with_all_options() {
516 use a2a_protocol_types::{AgentCapabilities, AgentCard, AgentInterface};
517
518 let card = AgentCard {
519 url: None,
520 name: "test".into(),
521 version: "1.0".into(),
522 description: "Test agent".into(),
523 supported_interfaces: vec![AgentInterface {
524 url: "http://localhost:8080".into(),
525 protocol_binding: "JSONRPC".into(),
526 protocol_version: "1.0.0".into(),
527 tenant: None,
528 }],
529 provider: None,
530 icon_url: None,
531 documentation_url: None,
532 capabilities: AgentCapabilities::none(),
533 security_schemes: None,
534 security_requirements: None,
535 default_input_modes: vec![],
536 default_output_modes: vec![],
537 skills: vec![],
538 signatures: None,
539 };
540
541 let result = RequestHandlerBuilder::new(TestExecutor)
542 .with_agent_card(card)
543 .with_executor_timeout(Duration::from_secs(30))
544 .with_event_queue_capacity(128)
545 .with_max_event_size(1024 * 1024)
546 .with_max_concurrent_streams(10)
547 .with_handler_limits(HandlerLimits::default().with_max_id_length(2048))
548 .build();
549 let h = result.expect("builder with all options should succeed");
550 assert!(h.tenant_resolver().is_none(), "no tenant resolver set");
551 }
552
553 #[test]
554 fn builder_with_tenant_resolver_and_config() {
555 use crate::tenant_config::{PerTenantConfig, TenantLimits};
556 use crate::tenant_resolver::HeaderTenantResolver;
557
558 let handler = RequestHandlerBuilder::new(TestExecutor)
559 .with_tenant_resolver(HeaderTenantResolver::default())
560 .with_tenant_config(
561 PerTenantConfig::builder()
562 .default_limits(TenantLimits::builder().rate_limit_rps(100).build())
563 .with_override(
564 "premium",
565 TenantLimits::builder().rate_limit_rps(1000).build(),
566 )
567 .build(),
568 )
569 .build();
570 let handler = handler.expect("builder with tenant resolver and config should succeed");
571 assert!(handler.tenant_resolver().is_some());
572 assert!(handler.tenant_config().is_some());
573 assert_eq!(
574 handler
575 .tenant_config()
576 .unwrap()
577 .get("premium")
578 .rate_limit_rps,
579 Some(1000)
580 );
581 assert_eq!(
582 handler
583 .tenant_config()
584 .unwrap()
585 .get("unknown")
586 .rate_limit_rps,
587 Some(100)
588 );
589 }
590
591 #[test]
592 fn builder_without_tenant_fields() {
593 let handler = RequestHandlerBuilder::new(TestExecutor).build().unwrap();
594 assert!(handler.tenant_resolver().is_none());
595 assert!(handler.tenant_config().is_none());
596 }
597
598 #[test]
602 fn builder_default_caps_concurrent_streams() {
603 let handler = RequestHandlerBuilder::new(TestExecutor).build().unwrap();
604 let debug = format!("{:?}", handler.event_queue_manager);
605 assert!(
606 debug.contains(&format!(
607 "max_concurrent_queues: Some({DEFAULT_MAX_CONCURRENT_STREAMS})"
608 )),
609 "default builder should cap concurrent streams at \
610 {DEFAULT_MAX_CONCURRENT_STREAMS}, got: {debug}"
611 );
612 }
613
614 #[test]
616 fn builder_max_concurrent_streams_override_wins() {
617 let handler = RequestHandlerBuilder::new(TestExecutor)
618 .with_max_concurrent_streams(7)
619 .build()
620 .unwrap();
621 let debug = format!("{:?}", handler.event_queue_manager);
622 assert!(
623 debug.contains("max_concurrent_queues: Some(7)"),
624 "explicit cap should override the default, got: {debug}"
625 );
626 }
627
628 #[test]
629 fn builder_debug_does_not_panic() {
630 let builder = RequestHandlerBuilder::new(TestExecutor);
631 let debug = format!("{builder:?}");
632 assert!(debug.contains("RequestHandlerBuilder"));
633 }
634
635 #[test]
636 fn builder_with_push_config_store_builds_ok() {
637 use crate::push::InMemoryPushConfigStore;
638 let result = RequestHandlerBuilder::new(TestExecutor)
639 .with_push_config_store(InMemoryPushConfigStore::new())
640 .build();
641 let _h = result.expect("builder with push config store should succeed");
642 }
643
644 #[test]
645 #[allow(deprecated)] fn builder_with_event_queue_write_timeout_builds_ok() {
647 let result = RequestHandlerBuilder::new(TestExecutor)
648 .with_event_queue_write_timeout(Duration::from_secs(10))
649 .build();
650 let _h = result.expect("builder with event queue write timeout should succeed");
651 }
652
653 #[test]
654 fn builder_zero_max_id_length_errors() {
655 let result = RequestHandlerBuilder::new(TestExecutor)
656 .with_handler_limits(HandlerLimits::default().with_max_id_length(0))
657 .build();
658 assert!(result.is_err(), "zero max_id_length should be rejected");
659 }
660
661 #[test]
662 fn builder_zero_max_metadata_size_errors() {
663 let result = RequestHandlerBuilder::new(TestExecutor)
664 .with_handler_limits(HandlerLimits::default().with_max_metadata_size(0))
665 .build();
666 assert!(result.is_err(), "zero max_metadata_size should be rejected");
667 }
668
669 #[test]
670 fn builder_zero_push_delivery_timeout_errors() {
671 let result = RequestHandlerBuilder::new(TestExecutor)
672 .with_handler_limits(
673 HandlerLimits::default().with_push_delivery_timeout(Duration::ZERO),
674 )
675 .build();
676 assert!(
677 result.is_err(),
678 "zero push_delivery_timeout should be rejected"
679 );
680 }
681}