1mod response;
13
14use std::collections::HashMap;
15use std::convert::Infallible;
16use std::sync::Arc;
17
18use bytes::Bytes;
19use http_body_util::combinators::BoxBody;
20use hyper::body::Incoming;
21
22use a2a_protocol_types::jsonrpc::{
23 JsonRpcError, JsonRpcErrorResponse, JsonRpcId, JsonRpcRequest, JsonRpcSuccessResponse,
24 JsonRpcVersion,
25};
26
27use crate::agent_card::StaticAgentCardHandler;
28use crate::dispatch::cors::CorsConfig;
29use crate::error::ServerError;
30use crate::handler::{RequestHandler, SendMessageResult};
31use crate::serve::Dispatcher;
32use crate::streaming::build_sse_response;
33
34use response::{
35 error_response, error_response_bytes, extract_headers, json_response, parse_error_response,
36 parse_params, read_body_limited, success_response, success_response_bytes,
37};
38
39pub struct JsonRpcDispatcher {
47 handler: Arc<RequestHandler>,
48 card_handler: Option<StaticAgentCardHandler>,
49 cors: Option<CorsConfig>,
50 config: super::DispatchConfig,
51}
52
53impl JsonRpcDispatcher {
54 #[must_use]
57 pub fn new(handler: Arc<RequestHandler>) -> Self {
58 Self::with_config(handler, super::DispatchConfig::default())
59 }
60
61 #[must_use]
63 pub fn with_config(handler: Arc<RequestHandler>, config: super::DispatchConfig) -> Self {
64 let card_handler = handler
65 .agent_card
66 .as_ref()
67 .and_then(|card| StaticAgentCardHandler::new(card).ok());
68 Self {
69 handler,
70 card_handler,
71 cors: None,
72 config,
73 }
74 }
75
76 #[must_use]
81 pub fn with_cors(mut self, cors: CorsConfig) -> Self {
82 self.cors = Some(cors);
83 self
84 }
85
86 pub async fn dispatch(
93 &self,
94 req: hyper::Request<Incoming>,
95 ) -> hyper::Response<BoxBody<Bytes, Infallible>> {
96 if req.method() == "OPTIONS" {
98 if let Some(ref cors) = self.cors {
99 return cors.preflight_response();
100 }
101 return json_response(204, Vec::new());
102 }
103
104 if req.method() == "GET" && req.uri().path() == "/.well-known/agent-card.json" {
107 let mut resp = self.card_handler.as_ref().map_or_else(
108 || json_response(404, br#"{"error":"agent card not configured"}"#.to_vec()),
109 |h| h.handle(&req).map(http_body_util::BodyExt::boxed),
110 );
111 if let Some(ref cors) = self.cors {
112 cors.apply_headers(&mut resp);
113 }
114 return resp;
115 }
116
117 let requested_extensions = req
122 .headers()
123 .get(a2a_protocol_types::A2A_EXTENSIONS_HEADER)
124 .and_then(|v| v.to_str().ok())
125 .map(str::to_owned);
126
127 let mut resp = Box::pin(self.dispatch_inner(req)).await;
132 if let Some(hval) = self
133 .handler
134 .activated_extensions_header_value(requested_extensions.as_deref())
135 {
136 if let Ok(v) = hyper::header::HeaderValue::from_str(&hval) {
137 resp.headers_mut()
138 .insert(a2a_protocol_types::A2A_EXTENSIONS_HEADER, v);
139 }
140 }
141 if let Some(ref cors) = self.cors {
142 cors.apply_headers(&mut resp);
143 }
144 resp
145 }
146
147 #[allow(clippy::too_many_lines)]
149 async fn dispatch_inner(
150 &self,
151 req: hyper::Request<Incoming>,
152 ) -> hyper::Response<BoxBody<Bytes, Infallible>> {
153 if let Some(ct) = req.headers().get("content-type") {
155 let ct_str = ct.to_str().unwrap_or("");
156 if !ct_str.starts_with("application/json")
157 && !ct_str.starts_with(a2a_protocol_types::A2A_CONTENT_TYPE)
158 {
159 return error_response(
167 None,
168 &ServerError::Protocol(a2a_protocol_types::error::A2aError::content_type_not_supported(
169 format!("unsupported Content-Type: {ct_str}; expected application/json or application/a2a+json"),
170 )),
171 );
172 }
173 }
174
175 let version_value = req
179 .headers()
180 .get(a2a_protocol_types::A2A_VERSION_HEADER)
181 .and_then(|v| v.to_str().ok());
182 if let Err(err) =
183 super::validate_version_header(version_value, self.config.require_version_header)
184 {
185 return error_response(None, &ServerError::Protocol(err));
186 }
187
188 let headers = extract_headers(req.headers());
190
191 let body_bytes = match read_body_limited(
193 req.into_body(),
194 self.config.max_request_body_size,
195 self.config.body_read_timeout,
196 )
197 .await
198 {
199 Ok(bytes) => bytes,
200 Err(msg) => return parse_error_response(None, &msg),
201 };
202
203 let raw: serde_json::Value = match serde_json::from_slice(&body_bytes) {
205 Ok(v) => v,
206 Err(e) => return parse_error_response(None, &e.to_string()),
207 };
208
209 if raw.is_array() {
210 let serde_json::Value::Array(items) = raw else {
212 unreachable!()
213 };
214 if items.is_empty() {
215 return parse_error_response(None, "empty batch request");
216 }
217 if items.len() > self.config.max_batch_size {
219 return parse_error_response(
220 None,
221 &format!(
222 "batch too large: {} requests exceeds {} limit",
223 items.len(),
224 self.config.max_batch_size
225 ),
226 );
227 }
228 let mut responses: Vec<serde_json::Value> = Vec::with_capacity(items.len());
229 for item in items {
230 let rpc_req: JsonRpcRequest = match serde_json::from_value(item) {
231 Ok(r) => r,
232 Err(e) => {
233 let err_resp = JsonRpcErrorResponse::new(
235 None,
236 JsonRpcError::new(
237 a2a_protocol_types::error::ErrorCode::ParseError.as_i32(),
238 format!("Parse error: {e}"),
239 ),
240 );
241 if let Ok(v) = serde_json::to_value(&err_resp) {
242 responses.push(v);
243 }
244 continue;
245 }
246 };
247 let resp_body = self.dispatch_single_request(&rpc_req, &headers).await;
248 if let Ok(v) = serde_json::from_slice::<serde_json::Value>(&resp_body) {
249 responses.push(v);
250 }
251 }
252 let body = serde_json::to_vec(&responses).unwrap_or_default();
253 json_response(200, body)
254 } else {
255 let rpc_req: JsonRpcRequest = match serde_json::from_value(raw) {
257 Ok(r) => r,
258 Err(e) => return parse_error_response(None, &e.to_string()),
259 };
260 self.dispatch_single_request_http(&rpc_req, &headers).await
261 }
262 }
263
264 #[allow(clippy::too_many_lines)]
268 async fn dispatch_single_request_http(
269 &self,
270 rpc_req: &JsonRpcRequest,
271 headers: &HashMap<String, String>,
272 ) -> hyper::Response<BoxBody<Bytes, Infallible>> {
273 let id = rpc_req.id.to_response_id();
274 trace_info!(method = %rpc_req.method, "dispatching JSON-RPC request");
275
276 match rpc_req.method.as_str() {
278 "SendStreamingMessage" => {
279 return self.dispatch_send_message(id, rpc_req, true, headers).await;
280 }
281 "SubscribeToTask" => {
282 return match parse_params::<a2a_protocol_types::params::TaskIdParams>(rpc_req) {
283 Ok(p) => match self.handler.on_resubscribe(p, Some(headers)).await {
284 Ok(reader) => build_sse_response(
285 reader,
286 Some(self.config.sse_keep_alive_interval),
287 Some(self.config.sse_channel_capacity),
288 Some(id.clone()),
291 ),
292 Err(e) => error_response(id, &e),
293 },
294 Err(e) => error_response(id, &e),
295 };
296 }
297 _ => {}
298 }
299
300 let body = self.dispatch_single_request(rpc_req, headers).await;
301 json_response(200, body)
302 }
303
304 #[allow(clippy::too_many_lines)]
308 async fn dispatch_single_request(
309 &self,
310 rpc_req: &JsonRpcRequest,
311 headers: &HashMap<String, String>,
312 ) -> Vec<u8> {
313 let id = rpc_req.id.to_response_id();
314
315 match rpc_req.method.as_str() {
316 "SendMessage" => {
317 match self
318 .dispatch_send_message_inner(id.clone(), rpc_req, false, headers)
319 .await
320 {
321 Ok(resp) => serde_json::to_vec(&resp).unwrap_or_default(),
322 Err(body) => body,
323 }
324 }
325 "SendStreamingMessage" => {
326 let err = ServerError::InvalidParams(
328 "SendStreamingMessage not supported in batch requests".into(),
329 );
330 let a2a_err = err.to_a2a_error();
331 let resp = JsonRpcErrorResponse::new(
332 id,
333 JsonRpcError::new(a2a_err.code.as_i32(), a2a_err.message),
334 );
335 serde_json::to_vec(&resp).unwrap_or_default()
336 }
337 "GetTask" => {
338 match parse_params::<a2a_protocol_types::params::TaskQueryParams>(rpc_req) {
339 Ok(p) => match self.handler.on_get_task(p, Some(headers)).await {
340 Ok(r) => success_response_bytes(id, &r),
341 Err(e) => error_response_bytes(id, &e),
342 },
343 Err(e) => error_response_bytes(id, &e),
344 }
345 }
346 "ListTasks" => {
347 match parse_params::<a2a_protocol_types::params::ListTasksParams>(rpc_req) {
348 Ok(p) => match self.handler.on_list_tasks(p, Some(headers)).await {
349 Ok(r) => success_response_bytes(id, &r),
350 Err(e) => error_response_bytes(id, &e),
351 },
352 Err(e) => error_response_bytes(id, &e),
353 }
354 }
355 "CancelTask" => {
356 match parse_params::<a2a_protocol_types::params::CancelTaskParams>(rpc_req) {
357 Ok(p) => match self.handler.on_cancel_task(p, Some(headers)).await {
358 Ok(r) => success_response_bytes(id, &r),
359 Err(e) => error_response_bytes(id, &e),
360 },
361 Err(e) => error_response_bytes(id, &e),
362 }
363 }
364 "SubscribeToTask" => {
365 let err = ServerError::InvalidParams(
366 "SubscribeToTask not supported in batch requests".into(),
367 );
368 error_response_bytes(id, &err)
369 }
370 "CreateTaskPushNotificationConfig" => {
371 match parse_params::<a2a_protocol_types::push::TaskPushNotificationConfig>(rpc_req)
372 {
373 Ok(p) => match self.handler.on_set_push_config(p, Some(headers)).await {
374 Ok(r) => success_response_bytes(id, &r),
375 Err(e) => error_response_bytes(id, &e),
376 },
377 Err(e) => error_response_bytes(id, &e),
378 }
379 }
380 "GetTaskPushNotificationConfig" => {
381 match parse_params::<a2a_protocol_types::params::GetPushConfigParams>(rpc_req) {
382 Ok(p) => match self.handler.on_get_push_config(p, Some(headers)).await {
383 Ok(r) => success_response_bytes(id, &r),
384 Err(e) => error_response_bytes(id, &e),
385 },
386 Err(e) => error_response_bytes(id, &e),
387 }
388 }
389 "ListTaskPushNotificationConfigs" => {
390 match parse_params::<a2a_protocol_types::params::ListPushConfigsParams>(rpc_req) {
391 Ok(p) => match self
392 .handler
393 .on_list_push_configs(&p.task_id, p.tenant.as_deref(), Some(headers))
394 .await
395 {
396 Ok(configs) => {
397 let resp = a2a_protocol_types::responses::ListPushConfigsResponse {
398 configs,
399 next_page_token: None,
400 };
401 success_response_bytes(id, &resp)
402 }
403 Err(e) => error_response_bytes(id, &e),
404 },
405 Err(e) => error_response_bytes(id, &e),
406 }
407 }
408 "DeleteTaskPushNotificationConfig" => {
409 match parse_params::<a2a_protocol_types::params::DeletePushConfigParams>(rpc_req) {
410 Ok(p) => match self.handler.on_delete_push_config(p, Some(headers)).await {
411 Ok(()) => success_response_bytes(id, &serde_json::json!({})),
412 Err(e) => error_response_bytes(id, &e),
413 },
414 Err(e) => error_response_bytes(id, &e),
415 }
416 }
417 "GetExtendedAgentCard" => {
418 match self.handler.on_get_extended_agent_card(Some(headers)).await {
419 Ok(r) => success_response_bytes(id, &r),
420 Err(e) => error_response_bytes(id, &e),
421 }
422 }
423 other => {
424 let err = ServerError::MethodNotFound(other.to_owned());
425 error_response_bytes(id, &err)
426 }
427 }
428 }
429
430 async fn dispatch_send_message_inner(
433 &self,
434 id: JsonRpcId,
435 rpc_req: &JsonRpcRequest,
436 streaming: bool,
437 headers: &HashMap<String, String>,
438 ) -> Result<JsonRpcSuccessResponse<serde_json::Value>, Vec<u8>> {
439 let params = match parse_params::<a2a_protocol_types::params::MessageSendParams>(rpc_req) {
440 Ok(p) => p,
441 Err(e) => return Err(error_response_bytes(id, &e)),
442 };
443 match self
444 .handler
445 .on_send_message(params, streaming, Some(headers))
446 .await
447 {
448 Ok(SendMessageResult::Response(resp)) => {
449 let result = serde_json::to_value(&resp).unwrap_or(serde_json::Value::Null);
450 Ok(JsonRpcSuccessResponse {
451 jsonrpc: JsonRpcVersion,
452 id,
453 result,
454 })
455 }
456 Ok(SendMessageResult::Stream(_)) => {
457 let err = ServerError::Internal("unexpected stream response".into());
459 Err(error_response_bytes(id, &err))
460 }
461 Err(e) => Err(error_response_bytes(id, &e)),
462 }
463 }
464
465 async fn dispatch_send_message(
466 &self,
467 id: JsonRpcId,
468 rpc_req: &JsonRpcRequest,
469 streaming: bool,
470 headers: &HashMap<String, String>,
471 ) -> hyper::Response<BoxBody<Bytes, Infallible>> {
472 let params = match parse_params::<a2a_protocol_types::params::MessageSendParams>(rpc_req) {
473 Ok(p) => p,
474 Err(e) => return error_response(id, &e),
475 };
476 match self
477 .handler
478 .on_send_message(params, streaming, Some(headers))
479 .await
480 {
481 Ok(SendMessageResult::Response(resp)) => success_response(id, &resp),
482 Ok(SendMessageResult::Stream(reader)) => build_sse_response(
483 reader,
484 Some(self.config.sse_keep_alive_interval),
485 Some(self.config.sse_channel_capacity),
486 Some(id.clone()),
488 ),
489 Err(e) => error_response(id, &e),
490 }
491 }
492}
493
494impl std::fmt::Debug for JsonRpcDispatcher {
495 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
496 f.debug_struct("JsonRpcDispatcher").finish()
497 }
498}
499
500impl Dispatcher for JsonRpcDispatcher {
503 fn dispatch(
504 &self,
505 req: hyper::Request<Incoming>,
506 ) -> std::pin::Pin<
507 Box<dyn std::future::Future<Output = crate::serve::DispatchResponse> + Send + '_>,
508 > {
509 Box::pin(self.dispatch(req))
510 }
511}