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 mut headers = extract_headers(req.headers());
198 headers.insert(":path".to_owned(), req.uri().path().to_owned());
201
202 let body_bytes = match read_body_limited(
204 req.into_body(),
205 self.config.max_request_body_size,
206 self.config.body_read_timeout,
207 )
208 .await
209 {
210 Ok(bytes) => bytes,
211 Err(msg) => return self.refuse_unparsed(started, &msg),
212 };
213
214 let raw: serde_json::Value = match serde_json::from_slice(&body_bytes) {
216 Ok(v) => v,
217 Err(e) => return self.refuse_unparsed(started, &e.to_string()),
218 };
219
220 if let serde_json::Value::Array(items) = raw {
222 if items.is_empty() {
223 return self.refuse_invalid(started, "empty batch request");
224 }
225 if items.len() > self.config.max_batch_size {
227 return self.refuse_invalid(
228 started,
229 &format!(
230 "batch too large: {} requests exceeds {} limit",
231 items.len(),
232 self.config.max_batch_size
233 ),
234 );
235 }
236 let mut responses: Vec<serde_json::Value> = Vec::with_capacity(items.len());
237 for item in items {
238 let rpc_req: JsonRpcRequest = match serde_json::from_value(item) {
239 Ok(r) => r,
240 Err(e) => {
241 self.record_unrouted(started, ErrorCode::InvalidRequest);
246 let err_resp = JsonRpcErrorResponse::new(
247 None,
248 JsonRpcError::new(
249 a2a_protocol_types::error::ErrorCode::InvalidRequest.as_i32(),
250 format!("Invalid Request: {e}"),
251 ),
252 );
253 if let Ok(v) = serde_json::to_value(&err_resp) {
254 responses.push(v);
255 }
256 continue;
257 }
258 };
259 let resp_body = self.dispatch_single_request(&rpc_req, &headers).await;
260 if let Ok(v) = serde_json::from_slice::<serde_json::Value>(&resp_body) {
261 responses.push(v);
262 }
263 }
264 let body = serde_json::to_vec(&responses).unwrap_or_default();
265 json_response(200, body)
266 } else {
267 let rpc_req: JsonRpcRequest = match serde_json::from_value(raw) {
269 Ok(r) => r,
270 Err(e) => return self.refuse_invalid(started, &e.to_string()),
273 };
274 self.dispatch_single_request_http(&rpc_req, &headers).await
275 }
276 }
277
278 fn rpc_span(
280 &self,
281 rpc_req: &JsonRpcRequest,
282 headers: &HashMap<String, String>,
283 ) -> crate::rpc_span::ServerSpan {
284 crate::rpc_span::ServerSpan::open(
285 &self.handler,
286 crate::rpc_span::RpcSystem::JsonRpc,
287 &rpc_req.method,
288 Some(headers),
289 )
290 }
291
292 #[allow(clippy::too_many_lines)]
296 async fn dispatch_single_request_http(
297 &self,
298 rpc_req: &JsonRpcRequest,
299 headers: &HashMap<String, String>,
300 ) -> hyper::Response<BoxBody<Bytes, Infallible>> {
301 let id = rpc_req.id.to_response_id();
302 trace_info!(method = %rpc_req.method, "dispatching JSON-RPC request");
303
304 match rpc_req.method.as_str() {
306 "SendStreamingMessage" => {
307 let call = async {
308 let params =
309 parse_params::<a2a_protocol_types::params::MessageSendParams>(rpc_req)?;
310 self.handler
311 .on_send_message(params, true, Some(headers))
312 .await
313 };
314 self.rpc_span(rpc_req, headers)
315 .run_with(call, |out| match out {
316 Ok(SendMessageResult::Response(resp)) => success_response(id, &resp),
317 Ok(SendMessageResult::Stream(reader)) => build_sse_response(
318 reader,
319 Some(self.config.sse_keep_alive_interval),
320 Some(self.config.sse_channel_capacity),
321 Some(id),
323 ),
324 Err(e) => error_response(id, &e),
325 })
326 .await
327 }
328 "SubscribeToTask" => {
329 let call = async {
330 let params = parse_params::<a2a_protocol_types::params::TaskIdParams>(rpc_req)?;
331 self.handler.on_resubscribe(params, Some(headers)).await
332 };
333 self.rpc_span(rpc_req, headers)
334 .run_with(call, |out| match out {
335 Ok(reader) => build_sse_response(
336 reader,
337 Some(self.config.sse_keep_alive_interval),
338 Some(self.config.sse_channel_capacity),
339 Some(id),
342 ),
343 Err(e) => error_response(id, &e),
344 })
345 .await
346 }
347 _ => match self
351 .rpc_span(rpc_req, headers)
352 .run(self.call(id.clone(), rpc_req, headers))
353 .await
354 {
355 Ok(body) => json_response(200, body),
356 Err(e) => error_response(id, &e),
357 },
358 }
359 }
360
361 async fn dispatch_single_request(
366 &self,
367 rpc_req: &JsonRpcRequest,
368 headers: &HashMap<String, String>,
369 ) -> Vec<u8> {
370 let id = rpc_req.id.to_response_id();
371 self.rpc_span(rpc_req, headers)
372 .run(self.call(id.clone(), rpc_req, headers))
373 .await
374 .unwrap_or_else(|e| error_response_bytes(id, &e))
375 }
376
377 #[allow(clippy::too_many_lines)]
379 async fn call(
380 &self,
381 id: JsonRpcId,
382 rpc_req: &JsonRpcRequest,
383 headers: &HashMap<String, String>,
384 ) -> Result<Vec<u8>, ServerError> {
385 let headers = Some(headers);
386 match rpc_req.method.as_str() {
387 "SendMessage" => {
388 let params =
389 parse_params::<a2a_protocol_types::params::MessageSendParams>(rpc_req)?;
390 match self.handler.on_send_message(params, false, headers).await? {
391 SendMessageResult::Response(resp) => Ok(success_response_bytes(id, &resp)),
392 SendMessageResult::Stream(_) => {
394 Err(ServerError::Internal("unexpected stream response".into()))
395 }
396 }
397 }
398 "SendStreamingMessage" => Err(ServerError::InvalidParams(
400 "SendStreamingMessage not supported in batch requests".into(),
401 )),
402 "SubscribeToTask" => Err(ServerError::InvalidParams(
403 "SubscribeToTask not supported in batch requests".into(),
404 )),
405 "GetTask" => {
406 let params = parse_params::<a2a_protocol_types::params::TaskQueryParams>(rpc_req)?;
407 let task = self.handler.on_get_task(params, headers).await?;
408 Ok(success_response_bytes(id, &task))
409 }
410 "ListTasks" => {
411 let params = parse_params::<a2a_protocol_types::params::ListTasksParams>(rpc_req)?;
412 let tasks = self.handler.on_list_tasks(params, headers).await?;
413 Ok(success_response_bytes(id, &tasks))
414 }
415 "CancelTask" => {
416 let params = parse_params::<a2a_protocol_types::params::CancelTaskParams>(rpc_req)?;
417 let task = self.handler.on_cancel_task(params, headers).await?;
418 Ok(success_response_bytes(id, &task))
419 }
420 "CreateTaskPushNotificationConfig" => {
421 let params =
422 parse_params::<a2a_protocol_types::push::TaskPushNotificationConfig>(rpc_req)?;
423 let config = self.handler.on_set_push_config(params, headers).await?;
424 Ok(success_response_bytes(id, &config))
425 }
426 "GetTaskPushNotificationConfig" => {
427 let params =
428 parse_params::<a2a_protocol_types::params::GetPushConfigParams>(rpc_req)?;
429 let config = self.handler.on_get_push_config(params, headers).await?;
430 Ok(success_response_bytes(id, &config))
431 }
432 "ListTaskPushNotificationConfigs" => {
433 let params =
434 parse_params::<a2a_protocol_types::params::ListPushConfigsParams>(rpc_req)?;
435 let configs = self
436 .handler
437 .on_list_push_configs(¶ms.task_id, params.tenant.as_deref(), headers)
438 .await?;
439 let resp = a2a_protocol_types::responses::ListPushConfigsResponse {
440 configs,
441 next_page_token: None,
442 };
443 Ok(success_response_bytes(id, &resp))
444 }
445 "DeleteTaskPushNotificationConfig" => {
446 let params =
447 parse_params::<a2a_protocol_types::params::DeletePushConfigParams>(rpc_req)?;
448 self.handler.on_delete_push_config(params, headers).await?;
449 Ok(success_response_bytes(id, &serde_json::json!({})))
450 }
451 "GetExtendedAgentCard" => {
452 let card = self.handler.on_get_extended_agent_card(headers).await?;
453 Ok(success_response_bytes(id, &card))
454 }
455 other => Err(ServerError::MethodNotFound(other.to_owned())),
456 }
457 }
458}
459
460impl std::fmt::Debug for JsonRpcDispatcher {
461 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
462 f.debug_struct("JsonRpcDispatcher").finish()
463 }
464}
465
466impl Dispatcher for JsonRpcDispatcher {
469 fn dispatch(
470 &self,
471 req: hyper::Request<Incoming>,
472 ) -> std::pin::Pin<
473 Box<dyn std::future::Future<Output = crate::serve::DispatchResponse> + Send + '_>,
474 > {
475 Box::pin(self.dispatch(req))
476 }
477
478 fn request_handler(&self) -> Option<&Arc<RequestHandler>> {
479 Some(&self.handler)
480 }
481}