a2a_protocol_server/dispatch/jsonrpc/
mod.rs1mod refusal;
13mod response;
14
15use std::collections::HashMap;
16use std::convert::Infallible;
17use std::sync::Arc;
18
19use bytes::Bytes;
20use http_body_util::combinators::BoxBody;
21use hyper::body::Incoming;
22
23use a2a_protocol_types::error::ErrorCode;
24use a2a_protocol_types::jsonrpc::{JsonRpcError, JsonRpcErrorResponse, JsonRpcId, JsonRpcRequest};
25
26use crate::agent_card::StaticAgentCardHandler;
27use crate::dispatch::cors::CorsConfig;
28use crate::error::ServerError;
29use crate::handler::{RequestHandler, SendMessageResult};
30use crate::serve::Dispatcher;
31use crate::streaming::build_sse_response;
32
33use response::{
34 error_response, error_response_bytes, extract_headers, json_response, parse_params,
35 read_body_limited, success_response, success_response_bytes,
36};
37
38pub struct JsonRpcDispatcher {
46 handler: Arc<RequestHandler>,
47 card_handler: Option<StaticAgentCardHandler>,
48 cors: Option<CorsConfig>,
49 config: super::DispatchConfig,
50}
51
52impl JsonRpcDispatcher {
53 #[must_use]
56 pub fn new(handler: Arc<RequestHandler>) -> Self {
57 Self::with_config(handler, super::DispatchConfig::default())
58 }
59
60 #[must_use]
62 pub fn with_config(handler: Arc<RequestHandler>, config: super::DispatchConfig) -> Self {
63 let card_handler = handler
64 .agent_card
65 .as_ref()
66 .and_then(|card| StaticAgentCardHandler::new(card).ok());
67 Self {
68 handler,
69 card_handler,
70 cors: None,
71 config,
72 }
73 }
74
75 #[must_use]
80 pub fn with_cors(mut self, cors: CorsConfig) -> Self {
81 self.cors = Some(cors);
82 self
83 }
84
85 pub async fn dispatch(
99 &self,
100 req: hyper::Request<Incoming>,
101 ) -> hyper::Response<BoxBody<Bytes, Infallible>> {
102 if req.method() == "OPTIONS" {
104 if let Some(ref cors) = self.cors {
105 return cors.preflight_response();
106 }
107 return json_response(204, Vec::new());
108 }
109
110 if req.method() == "GET" && req.uri().path() == "/.well-known/agent-card.json" {
113 let mut resp = self.card_handler.as_ref().map_or_else(
114 || json_response(404, br#"{"error":"agent card not configured"}"#.to_vec()),
115 |h| h.handle(&req).map(http_body_util::BodyExt::boxed),
116 );
117 if let Some(ref cors) = self.cors {
118 cors.apply_headers(&mut resp);
119 }
120 return resp;
121 }
122
123 let requested_extensions = req
128 .headers()
129 .get(a2a_protocol_types::A2A_EXTENSIONS_HEADER)
130 .and_then(|v| v.to_str().ok())
131 .map(str::to_owned);
132
133 let mut resp = Box::pin(self.dispatch_inner(req, std::time::Instant::now())).await;
138 if let Some(hval) = self
139 .handler
140 .activated_extensions_header_value(requested_extensions.as_deref())
141 && let Ok(v) = hyper::header::HeaderValue::from_str(&hval)
142 {
143 resp.headers_mut()
144 .insert(a2a_protocol_types::A2A_EXTENSIONS_HEADER, v);
145 }
146 if let Some(ref cors) = self.cors {
147 cors.apply_headers(&mut resp);
148 }
149 resp
150 }
151
152 #[allow(clippy::too_many_lines)]
154 async fn dispatch_inner(
155 &self,
156 req: hyper::Request<Incoming>,
157 started: std::time::Instant,
158 ) -> hyper::Response<BoxBody<Bytes, Infallible>> {
159 if let Some(ct) = req.headers().get("content-type") {
161 let ct_str = ct.to_str().unwrap_or("");
162 if !ct_str.starts_with("application/json")
163 && !ct_str.starts_with(a2a_protocol_types::A2A_CONTENT_TYPE)
164 {
165 return self.refuse(
173 started,
174 &ServerError::Protocol(
175 a2a_protocol_types::error::A2aError::content_type_not_supported(format!(
176 "unsupported Content-Type: {ct_str}; expected application/json or application/a2a+json"
177 )),
178 ),
179 );
180 }
181 }
182
183 let version_value = req
187 .headers()
188 .get(a2a_protocol_types::A2A_VERSION_HEADER)
189 .and_then(|v| v.to_str().ok());
190 if let Err(err) =
191 super::validate_version_header(version_value, self.config.require_version_header)
192 {
193 return self.refuse(started, &ServerError::Protocol(err));
194 }
195
196 let headers = extract_headers(req.headers());
198
199 let body_bytes = match read_body_limited(
201 req.into_body(),
202 self.config.max_request_body_size,
203 self.config.body_read_timeout,
204 )
205 .await
206 {
207 Ok(bytes) => bytes,
208 Err(msg) => return self.refuse_unparsed(started, &msg),
209 };
210
211 let raw: serde_json::Value = match serde_json::from_slice(&body_bytes) {
213 Ok(v) => v,
214 Err(e) => return self.refuse_unparsed(started, &e.to_string()),
215 };
216
217 if let serde_json::Value::Array(items) = raw {
219 if items.is_empty() {
220 return self.refuse_invalid(started, "empty batch request");
221 }
222 if items.len() > self.config.max_batch_size {
224 return self.refuse_invalid(
225 started,
226 &format!(
227 "batch too large: {} requests exceeds {} limit",
228 items.len(),
229 self.config.max_batch_size
230 ),
231 );
232 }
233 let mut responses: Vec<serde_json::Value> = Vec::with_capacity(items.len());
234 for item in items {
235 let rpc_req: JsonRpcRequest = match serde_json::from_value(item) {
236 Ok(r) => r,
237 Err(e) => {
238 self.record_unrouted(started, ErrorCode::InvalidRequest);
243 let err_resp = JsonRpcErrorResponse::new(
244 None,
245 JsonRpcError::new(
246 a2a_protocol_types::error::ErrorCode::InvalidRequest.as_i32(),
247 format!("Invalid Request: {e}"),
248 ),
249 );
250 if let Ok(v) = serde_json::to_value(&err_resp) {
251 responses.push(v);
252 }
253 continue;
254 }
255 };
256 let resp_body = self.dispatch_single_request(&rpc_req, &headers).await;
257 if let Ok(v) = serde_json::from_slice::<serde_json::Value>(&resp_body) {
258 responses.push(v);
259 }
260 }
261 let body = serde_json::to_vec(&responses).unwrap_or_default();
262 json_response(200, body)
263 } else {
264 let rpc_req: JsonRpcRequest = match serde_json::from_value(raw) {
266 Ok(r) => r,
267 Err(e) => return self.refuse_invalid(started, &e.to_string()),
270 };
271 self.dispatch_single_request_http(&rpc_req, &headers).await
272 }
273 }
274
275 fn rpc_span(
277 &self,
278 rpc_req: &JsonRpcRequest,
279 headers: &HashMap<String, String>,
280 ) -> crate::rpc_span::ServerSpan {
281 crate::rpc_span::ServerSpan::open(
282 &self.handler,
283 crate::rpc_span::RpcSystem::JsonRpc,
284 &rpc_req.method,
285 Some(headers),
286 )
287 }
288
289 #[allow(clippy::too_many_lines)]
293 async fn dispatch_single_request_http(
294 &self,
295 rpc_req: &JsonRpcRequest,
296 headers: &HashMap<String, String>,
297 ) -> hyper::Response<BoxBody<Bytes, Infallible>> {
298 let id = rpc_req.id.to_response_id();
299 trace_info!(method = %rpc_req.method, "dispatching JSON-RPC request");
300
301 match rpc_req.method.as_str() {
303 "SendStreamingMessage" => {
304 let call = async {
305 let params =
306 parse_params::<a2a_protocol_types::params::MessageSendParams>(rpc_req)?;
307 self.handler
308 .on_send_message(params, true, Some(headers))
309 .await
310 };
311 self.rpc_span(rpc_req, headers)
312 .run_with(call, |out| match out {
313 Ok(SendMessageResult::Response(resp)) => success_response(id, &resp),
314 Ok(SendMessageResult::Stream(reader)) => build_sse_response(
315 reader,
316 Some(self.config.sse_keep_alive_interval),
317 Some(self.config.sse_channel_capacity),
318 Some(id),
320 ),
321 Err(e) => error_response(id, &e),
322 })
323 .await
324 }
325 "SubscribeToTask" => {
326 let call = async {
327 let params = parse_params::<a2a_protocol_types::params::TaskIdParams>(rpc_req)?;
328 self.handler.on_resubscribe(params, Some(headers)).await
329 };
330 self.rpc_span(rpc_req, headers)
331 .run_with(call, |out| match out {
332 Ok(reader) => build_sse_response(
333 reader,
334 Some(self.config.sse_keep_alive_interval),
335 Some(self.config.sse_channel_capacity),
336 Some(id),
339 ),
340 Err(e) => error_response(id, &e),
341 })
342 .await
343 }
344 _ => match self
348 .rpc_span(rpc_req, headers)
349 .run(self.call(id.clone(), rpc_req, headers))
350 .await
351 {
352 Ok(body) => json_response(200, body),
353 Err(e) => error_response(id, &e),
354 },
355 }
356 }
357
358 async fn dispatch_single_request(
363 &self,
364 rpc_req: &JsonRpcRequest,
365 headers: &HashMap<String, String>,
366 ) -> Vec<u8> {
367 let id = rpc_req.id.to_response_id();
368 self.rpc_span(rpc_req, headers)
369 .run(self.call(id.clone(), rpc_req, headers))
370 .await
371 .unwrap_or_else(|e| error_response_bytes(id, &e))
372 }
373
374 #[allow(clippy::too_many_lines)]
376 async fn call(
377 &self,
378 id: JsonRpcId,
379 rpc_req: &JsonRpcRequest,
380 headers: &HashMap<String, String>,
381 ) -> Result<Vec<u8>, ServerError> {
382 let headers = Some(headers);
383 match rpc_req.method.as_str() {
384 "SendMessage" => {
385 let params =
386 parse_params::<a2a_protocol_types::params::MessageSendParams>(rpc_req)?;
387 match self.handler.on_send_message(params, false, headers).await? {
388 SendMessageResult::Response(resp) => Ok(success_response_bytes(id, &resp)),
389 SendMessageResult::Stream(_) => {
391 Err(ServerError::Internal("unexpected stream response".into()))
392 }
393 }
394 }
395 "SendStreamingMessage" => Err(ServerError::InvalidParams(
397 "SendStreamingMessage not supported in batch requests".into(),
398 )),
399 "SubscribeToTask" => Err(ServerError::InvalidParams(
400 "SubscribeToTask not supported in batch requests".into(),
401 )),
402 "GetTask" => {
403 let params = parse_params::<a2a_protocol_types::params::TaskQueryParams>(rpc_req)?;
404 let task = self.handler.on_get_task(params, headers).await?;
405 Ok(success_response_bytes(id, &task))
406 }
407 "ListTasks" => {
408 let params = parse_params::<a2a_protocol_types::params::ListTasksParams>(rpc_req)?;
409 let tasks = self.handler.on_list_tasks(params, headers).await?;
410 Ok(success_response_bytes(id, &tasks))
411 }
412 "CancelTask" => {
413 let params = parse_params::<a2a_protocol_types::params::CancelTaskParams>(rpc_req)?;
414 let task = self.handler.on_cancel_task(params, headers).await?;
415 Ok(success_response_bytes(id, &task))
416 }
417 "CreateTaskPushNotificationConfig" => {
418 let params =
419 parse_params::<a2a_protocol_types::push::TaskPushNotificationConfig>(rpc_req)?;
420 let config = self.handler.on_set_push_config(params, headers).await?;
421 Ok(success_response_bytes(id, &config))
422 }
423 "GetTaskPushNotificationConfig" => {
424 let params =
425 parse_params::<a2a_protocol_types::params::GetPushConfigParams>(rpc_req)?;
426 let config = self.handler.on_get_push_config(params, headers).await?;
427 Ok(success_response_bytes(id, &config))
428 }
429 "ListTaskPushNotificationConfigs" => {
430 let params =
431 parse_params::<a2a_protocol_types::params::ListPushConfigsParams>(rpc_req)?;
432 let configs = self
433 .handler
434 .on_list_push_configs(¶ms.task_id, params.tenant.as_deref(), headers)
435 .await?;
436 let resp = a2a_protocol_types::responses::ListPushConfigsResponse {
437 configs,
438 next_page_token: None,
439 };
440 Ok(success_response_bytes(id, &resp))
441 }
442 "DeleteTaskPushNotificationConfig" => {
443 let params =
444 parse_params::<a2a_protocol_types::params::DeletePushConfigParams>(rpc_req)?;
445 self.handler.on_delete_push_config(params, headers).await?;
446 Ok(success_response_bytes(id, &serde_json::json!({})))
447 }
448 "GetExtendedAgentCard" => {
449 let card = self.handler.on_get_extended_agent_card(headers).await?;
450 Ok(success_response_bytes(id, &card))
451 }
452 other => Err(ServerError::MethodNotFound(other.to_owned())),
453 }
454 }
455}
456
457impl std::fmt::Debug for JsonRpcDispatcher {
458 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
459 f.debug_struct("JsonRpcDispatcher").finish()
460 }
461}
462
463impl Dispatcher for JsonRpcDispatcher {
466 fn dispatch(
467 &self,
468 req: hyper::Request<Incoming>,
469 ) -> std::pin::Pin<
470 Box<dyn std::future::Future<Output = crate::serve::DispatchResponse> + Send + '_>,
471 > {
472 Box::pin(self.dispatch(req))
473 }
474
475 fn request_handler(&self) -> Option<&Arc<RequestHandler>> {
476 Some(&self.handler)
477 }
478}