1mod model;
26mod tasks;
27
28pub use model::{Artifact, Message, Part, PartContent, Role, Task, TaskState, TaskStatus};
29pub use tasks::TaskHandle;
30
31use std::sync::Arc;
32use std::time::Duration;
33
34use tasks::Shared;
35
36use model::{quote, to_json, Obj};
37
38use crate::application::Application;
39use crate::request::Request;
40use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
41use crate::server::ConnectionInfo;
42use crate::service_discovery::json_lite::{self, JsonValue};
43
44pub const PROTOCOL_VERSION: &str = "1.0";
46pub const AGENT_CARD_PATH: &str = "/.well-known/agent-card.json";
48pub const MAX_BODY: usize = 4 * 1024 * 1024;
50pub const MAX_TASKS: usize = 10_000;
52
53pub(crate) fn new_id() -> String {
55 use std::hash::{BuildHasher, Hasher};
56 let half = || {
57 let mut h = std::collections::hash_map::RandomState::new().build_hasher();
58 h.write_u128(std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_nanos());
59 h.finish()
60 };
61 let (a, b) = (half(), half());
62 let bytes: Vec<u8> = a.to_be_bytes().into_iter().chain(b.to_be_bytes()).collect();
63 let hex: String = bytes
64 .iter()
65 .enumerate()
66 .map(|(i, byte)| match i {
67 6 => format!("{:02x}", (byte & 0x0f) | 0x40),
68 8 => format!("{:02x}", (byte & 0x3f) | 0x80),
69 _ => format!("{byte:02x}"),
70 })
71 .collect();
72 format!("{}-{}-{}-{}-{}", &hex[0..8], &hex[8..12], &hex[12..16], &hex[16..20], &hex[20..32])
73}
74
75#[derive(Debug, Clone, PartialEq)]
79pub struct Skill {
80 pub id: String,
81 pub name: String,
82 pub description: String,
83 pub tags: Vec<String>,
84 pub examples: Vec<String>,
85 pub input_modes: Vec<String>,
86 pub output_modes: Vec<String>,
87}
88
89impl Skill {
90 pub fn new(id: &str, name: &str, description: &str) -> Skill {
91 Skill { id: id.into(), name: name.into(), description: description.into(), tags: Vec::new(), examples: Vec::new(), input_modes: Vec::new(), output_modes: Vec::new() }
92 }
93
94 pub fn tag(mut self, tag: &str) -> Skill {
95 self.tags.push(tag.into());
96 self
97 }
98
99 pub fn example(mut self, example: &str) -> Skill {
100 self.examples.push(example.into());
101 self
102 }
103
104 fn to_json(&self) -> String {
105 let mut o = Obj::new().str("id", &self.id).str("name", &self.name).str("description", &self.description).strs("tags", &self.tags);
106 if !self.examples.is_empty() {
107 o = o.strs("examples", &self.examples);
108 }
109 if !self.input_modes.is_empty() {
110 o = o.strs("inputModes", &self.input_modes);
111 }
112 if !self.output_modes.is_empty() {
113 o = o.strs("outputModes", &self.output_modes);
114 }
115 o.done()
116 }
117}
118
119#[derive(Debug, Clone, PartialEq)]
121pub struct AgentCard {
122 pub name: String,
123 pub description: String,
124 pub version: String,
125 pub url: Option<String>,
128 pub skills: Vec<Skill>,
129 pub default_input_modes: Vec<String>,
130 pub default_output_modes: Vec<String>,
131 pub provider: Option<(String, String)>,
132 pub documentation_url: Option<String>,
133 pub icon_url: Option<String>,
134}
135
136impl AgentCard {
137 pub fn new(name: &str, description: &str, version: &str) -> AgentCard {
138 AgentCard {
139 name: name.into(),
140 description: description.into(),
141 version: version.into(),
142 url: None,
143 skills: Vec::new(),
144 default_input_modes: vec!["text/plain".into()],
145 default_output_modes: vec!["text/plain".into()],
146 provider: None,
147 documentation_url: None,
148 icon_url: None,
149 }
150 }
151
152 pub fn skill(mut self, skill: Skill) -> AgentCard {
153 self.skills.push(skill);
154 self
155 }
156
157 pub fn url(mut self, url: &str) -> AgentCard {
159 self.url = Some(url.into());
160 self
161 }
162
163 pub fn modes(mut self, input: &[&str], output: &[&str]) -> AgentCard {
165 self.default_input_modes = input.iter().map(|s| s.to_string()).collect();
166 self.default_output_modes = output.iter().map(|s| s.to_string()).collect();
167 self
168 }
169
170 pub fn provider(mut self, organization: &str, url: &str) -> AgentCard {
171 self.provider = Some((organization.into(), url.into()));
172 self
173 }
174
175 pub fn documentation_url(mut self, url: &str) -> AgentCard {
176 self.documentation_url = Some(url.into());
177 self
178 }
179
180 pub(crate) fn to_json(&self, url: &str, bearer: bool) -> String {
181 let interface = Obj::new().str("url", url).str("protocolBinding", "JSONRPC").str("protocolVersion", PROTOCOL_VERSION).done();
182 let capabilities = r#"{"streaming":true,"pushNotifications":false,"extendedAgentCard":false}"#;
183 let skills: Vec<String> = self.skills.iter().map(Skill::to_json).collect();
184 let provider = self.provider.as_ref().map(|(org, url)| Obj::new().str("organization", org).str("url", url).done());
185 Obj::new()
186 .str("name", &self.name)
187 .str("description", &self.description)
188 .str("version", &self.version)
189 .list("supportedInterfaces", &[interface])
190 .raw("capabilities", capabilities)
191 .strs("defaultInputModes", &self.default_input_modes)
192 .strs("defaultOutputModes", &self.default_output_modes)
193 .list("skills", &skills)
194 .opt_raw("provider", provider.as_deref())
195 .opt_str("documentationUrl", self.documentation_url.as_deref())
196 .opt_str("iconUrl", self.icon_url.as_deref())
197 .opt_raw("securitySchemes", bearer.then_some(r#"{"bearer":{"httpAuthSecurityScheme":{"scheme":"Bearer"}}}"#))
198 .opt_raw("securityRequirements", bearer.then_some(r#"[{"schemes":{"bearer":{"list":[]}}}]"#))
199 .done()
200 }
201}
202
203pub struct A2aContext {
207 task_id: String,
208 context_id: String,
209 user_message: Message,
210 shared: Arc<Shared>,
211 owner: String,
212 pub task: Option<Task>,
214 pub accepted_output_modes: Vec<String>,
216}
217
218impl A2aContext {
219 pub fn task_id(&self) -> &str {
221 &self.task_id
222 }
223
224 pub fn context_id(&self) -> &str {
226 &self.context_id
227 }
228
229 pub fn task(&self, state: TaskState, message: Option<Message>, artifacts: Vec<Artifact>) -> Task {
232 let mut history = self.task.as_ref().map(|t| t.history.clone()).unwrap_or_default();
233 history.push(self.user_message.clone());
234 let message = message.map(|mut m| {
235 m.task_id = Some(self.task_id.clone());
236 m.context_id = Some(self.context_id.clone());
237 m
238 });
239 if let Some(m) = &message {
240 history.push(m.clone());
241 }
242 let mut all = self.task.as_ref().map(|t| t.artifacts.clone()).unwrap_or_default();
243 all.extend(artifacts);
244 Task {
245 id: self.task_id.clone(),
246 context_id: self.context_id.clone(),
247 status: TaskStatus { state, message, timestamp: model::timestamp() },
248 artifacts: all,
249 history,
250 metadata: None,
251 }
252 }
253
254 pub fn complete(&self, artifacts: Vec<Artifact>) -> Task {
256 self.task(TaskState::Completed, None, artifacts)
257 }
258
259 pub fn fail(&self, reason: &str) -> Task {
261 self.task(TaskState::Failed, Some(Message::agent_text(reason)), Vec::new())
262 }
263
264 pub fn reject(&self, reason: &str) -> Task {
266 self.task(TaskState::Rejected, Some(Message::agent_text(reason)), Vec::new())
267 }
268
269 pub fn input_required(&self, question: &str) -> Task {
271 self.task(TaskState::InputRequired, Some(Message::agent_text(question)), Vec::new())
272 }
273
274 pub fn start(&self) -> TaskHandle {
279 let task = self.task(TaskState::Working, None, Vec::new());
280 let mut store = self.shared.lock();
281 store.put(&self.owner, task);
282 let canceled = store.get_mut(&self.owner, &self.task_id).map(|s| Arc::clone(&s.canceled)).unwrap_or_default();
283 drop(store);
284 TaskHandle { shared: Arc::clone(&self.shared), owner: self.owner.clone(), id: self.task_id.clone(), canceled }
285 }
286}
287
288impl std::fmt::Debug for TaskHandle {
289 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290 f.debug_struct("TaskHandle").field("id", &self.id).finish()
291 }
292}
293
294#[derive(Debug, Clone)]
296pub enum A2aReply {
297 Message(Message),
299 Task(Task),
301 Running(TaskHandle),
303}
304
305type Handler = Arc<dyn Fn(&A2aContext, Message) -> A2aReply + Send + Sync>;
306
307#[derive(Clone)]
310pub struct A2aServer {
311 card: Arc<AgentCard>,
312 path: String,
313 handler: Handler,
314 tasks: Arc<Shared>,
315 fallback: Option<Arc<dyn Application + Send + Sync>>,
316 tokens: Arc<Vec<(String, String)>>,
318 blocking_timeout: Duration,
319}
320
321impl A2aServer {
322 pub fn new(card: AgentCard) -> A2aServer {
325 A2aServer {
326 card: Arc::new(card),
327 path: "/a2a".to_string(),
328 handler: Arc::new(|ctx: &A2aContext, _| A2aReply::Task(ctx.reject("this agent has no message handler"))),
329 tasks: Arc::new(Shared::default()),
330 fallback: None,
331 tokens: Arc::new(Vec::new()),
332 blocking_timeout: Duration::from_secs(300),
333 }
334 }
335
336 pub fn at(mut self, path: &str) -> A2aServer {
338 self.path = path.to_string();
339 self
340 }
341
342 pub fn on_message<F>(mut self, handler: F) -> A2aServer
344 where
345 F: Fn(&A2aContext, Message) -> A2aReply + Send + Sync + 'static,
346 {
347 self.handler = Arc::new(handler);
348 self
349 }
350
351 pub fn require_bearer(self, token: &str) -> A2aServer {
354 self.require_bearer_tokens([(token, "default")])
355 }
356
357 pub fn require_bearer_tokens<'a>(mut self, tokens: impl IntoIterator<Item = (&'a str, &'a str)>) -> A2aServer {
360 self.tokens = Arc::new(tokens.into_iter().filter(|(t, _)| !t.is_empty()).map(|(t, i)| (t.to_string(), i.to_string())).collect());
361 self
362 }
363
364 pub fn blocking_timeout(mut self, timeout: Duration) -> A2aServer {
367 self.blocking_timeout = timeout;
368 self
369 }
370
371 pub fn wrap(mut self, app: impl Application + Send + Sync + 'static) -> A2aServer {
373 self.fallback = Some(Arc::new(app));
374 self
375 }
376
377 fn caller(&self, request: &Request) -> Option<String> {
380 if self.tokens.is_empty() {
381 return Some(String::new());
382 }
383 let sent = request.header(crate::header::Header::_AUTHORIZATION)?.value.strip_prefix("Bearer ")?.trim().to_string();
384 self.tokens.iter().find(|(token, _)| crate::core::constant_time_eq(token.as_bytes(), sent.as_bytes())).map(|(_, identity)| identity.clone())
385 }
386
387 fn endpoint_url(&self, request: &Request, connection: &ConnectionInfo) -> String {
388 if let Some(url) = &self.card.url {
389 return url.clone();
390 }
391 let host = request.header(crate::header::Header::_HOST).map(|h| h.value.to_string()).unwrap_or_else(|| format!("{}:{}", connection.server.ip, connection.server.port));
392 format!("{}://{host}{}", if connection.tls { "https" } else { "http" }, self.path)
393 }
394
395 fn card_response(&self, request: &Request, connection: &ConnectionInfo) -> Response {
396 let mut response = Response::json(STATUS_CODE_REASON_PHRASE.n200_ok, self.card.to_json(&self.endpoint_url(request, connection), !self.tokens.is_empty()).into_bytes());
397 response.headers.push(crate::header::Header::new("Cache-Control", "public, max-age=300".to_string()));
398 response
399 }
400
401 fn rpc(&self, request: &Request, owner: &str) -> Response {
402 let reply = |body: String| Response::json(STATUS_CODE_REASON_PHRASE.n200_ok, body.into_bytes());
403 if request.body.len() > MAX_BODY {
404 return crate::error::problem(STATUS_CODE_REASON_PHRASE.n413_payload_too_large, "A2A request too large");
405 }
406 let Ok(text) = std::str::from_utf8(&request.body) else {
407 return reply(error_json("null", RpcError::Parse("the body isn't UTF-8".into())));
408 };
409 let call = match json_lite::parse(text) {
410 Ok(call) => call,
411 Err(e) => return reply(error_json("null", RpcError::Parse(e))),
412 };
413 let id = call.get("id").map(to_json).unwrap_or_else(|| "null".to_string());
414 let method = match (call.get("jsonrpc").and_then(JsonValue::as_str), call.get("method").and_then(JsonValue::as_str)) {
415 (Some("2.0"), Some(method)) => method,
416 _ => return reply(error_json(&id, RpcError::InvalidRequest)),
417 };
418 if let Err(e) = check_version(request) {
419 return reply(error_json(&id, e));
420 }
421 let params = call.get("params").cloned().unwrap_or(JsonValue::Object(Vec::new()));
422 if method == "SendStreamingMessage" || method == "SubscribeToTask" {
423 return match self.stream(method, ¶ms, owner, &id) {
424 Ok(response) => response,
425 Err(e) => reply(error_json(&id, e)),
426 };
427 }
428 match self.dispatch(method, ¶ms, owner) {
429 Ok(result) => reply(format!(r#"{{"jsonrpc":"2.0","id":{id},"result":{result}}}"#)),
430 Err(e) => reply(error_json(&id, e)),
431 }
432 }
433
434 fn dispatch(&self, method: &str, params: &JsonValue, owner: &str) -> Result<String, RpcError> {
435 match method {
436 "SendMessage" => self.send_message(params, owner),
437 "GetTask" => {
438 let id = params.get("id").and_then(JsonValue::as_str).ok_or_else(|| RpcError::InvalidParams("id is required".into()))?;
439 let tasks = self.tasks.lock();
440 let task = tasks.get(owner, id).ok_or(RpcError::TaskNotFound)?;
441 Ok(task.to_json(history_length(params)?))
442 }
443 "ListTasks" => self.list_tasks(params, owner),
444 "CancelTask" => {
445 let id = params.get("id").and_then(JsonValue::as_str).ok_or_else(|| RpcError::InvalidParams("id is required".into()))?;
446 if !tasks::set_state(&self.tasks, owner, id, TaskState::Canceled, None) {
447 return Err(match self.tasks.lock().get(owner, id) {
448 Some(_) => RpcError::TaskNotCancelable,
449 None => RpcError::TaskNotFound,
450 });
451 }
452 Ok(self.tasks.lock().get(owner, id).map(|t| t.to_json(None)).unwrap_or_default())
453 }
454 "GetExtendedAgentCard" => Err(RpcError::UnsupportedOperation("this agent has no extended card".into())),
455 "CreateTaskPushNotificationConfig" | "GetTaskPushNotificationConfig" | "ListTaskPushNotificationConfigs" | "DeleteTaskPushNotificationConfig" => {
456 Err(RpcError::PushNotificationNotSupported)
457 }
458 _ => Err(RpcError::MethodNotFound(method.to_string())),
459 }
460 }
461
462 fn handle_message(&self, params: &JsonValue, owner: &str) -> Result<Outcome, RpcError> {
464 let message = Message::from_json(params.get("message").ok_or_else(|| RpcError::InvalidParams("message is required".into()))?).map_err(RpcError::InvalidParams)?;
465 let configuration = params.get("configuration");
466 let accepted_output_modes = configuration
467 .and_then(|c| c.get("acceptedOutputModes"))
468 .and_then(JsonValue::as_array)
469 .map(|a| a.iter().filter_map(|m| m.as_str().map(str::to_string)).collect())
470 .unwrap_or_default();
471 let existing = match &message.task_id {
472 Some(id) => {
473 let task = self.tasks.lock().get(owner, id).cloned().ok_or(RpcError::TaskNotFound)?;
474 if task.status.state.is_terminal() {
475 return Err(RpcError::UnsupportedOperation(format!("task {id} is {} and takes no more messages", task.status.state.as_str())));
476 }
477 Some(task)
478 }
479 None => None,
480 };
481 let task_id = existing.as_ref().map(|t| t.id.clone()).unwrap_or_else(new_id);
482 let context_id = existing.as_ref().map(|t| t.context_id.clone()).or_else(|| message.context_id.clone()).unwrap_or_else(new_id);
483 let mut user_message = message.clone();
484 user_message.context_id = Some(context_id.clone());
485 let ctx = A2aContext {
486 task_id,
487 context_id: context_id.clone(),
488 user_message,
489 shared: Arc::clone(&self.tasks),
490 owner: owner.to_string(),
491 task: existing,
492 accepted_output_modes,
493 };
494 let handler = Arc::clone(&self.handler);
495 let reply = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| handler(&ctx, message))).unwrap_or_else(|_| A2aReply::Task(ctx.fail("the agent failed")));
496 Ok(match reply {
497 A2aReply::Message(mut m) => {
498 m.context_id.get_or_insert(context_id);
499 Outcome::Message(m)
500 }
501 A2aReply::Task(task) => {
502 let id = task.id.clone();
503 self.tasks.lock().put(owner, task);
504 Outcome::Task(id, false)
505 }
506 A2aReply::Running(handle) => Outcome::Task(handle.id, true),
507 })
508 }
509
510 fn send_message(&self, params: &JsonValue, owner: &str) -> Result<String, RpcError> {
511 let configuration = params.get("configuration");
512 let history = match configuration {
513 Some(c) => history_length(c)?,
514 None => None,
515 };
516 let return_immediately = configuration.and_then(|c| c.get("returnImmediately")) == Some(&JsonValue::Bool(true));
517 match self.handle_message(params, owner)? {
518 Outcome::Message(m) => Ok(format!(r#"{{"message":{}}}"#, m.to_json())),
519 Outcome::Task(id, running) => {
520 let deadline = std::time::Instant::now() + self.blocking_timeout;
523 let mut store = self.tasks.lock();
524 loop {
525 let Some(task) = store.get(owner, &id) else { return Err(RpcError::TaskNotFound) };
526 let state = task.status.state;
527 let now = std::time::Instant::now();
528 if !running || return_immediately || state.is_terminal() || state.is_interrupted() || now >= deadline {
529 return Ok(format!(r#"{{"task":{}}}"#, task.to_json(history)));
530 }
531 store = self.tasks.changed.wait_timeout(store, deadline - now).map(|(g, _)| g).unwrap_or_else(|e| e.into_inner().0);
532 }
533 }
534 }
535 }
536
537 fn stream(&self, method: &str, params: &JsonValue, owner: &str, rpc_id: &str) -> Result<Response, RpcError> {
541 let id = if method == "SubscribeToTask" {
542 let id = params.get("id").and_then(JsonValue::as_str).ok_or_else(|| RpcError::InvalidParams("id is required".into()))?.to_string();
543 let store = self.tasks.lock();
544 let task = store.get(owner, &id).ok_or(RpcError::TaskNotFound)?;
545 if task.status.state.is_terminal() {
546 return Err(RpcError::UnsupportedOperation(format!("task {id} is {}", task.status.state.as_str())));
547 }
548 id
549 } else {
550 match self.handle_message(params, owner)? {
551 Outcome::Message(m) => {
552 let (sender, response) = crate::sse::SseStream::new().start();
553 let _ = sender.try_send(tasks::event(rpc_id, "message", &m.to_json()));
554 return Ok(response);
555 }
556 Outcome::Task(id, _) => id,
557 }
558 };
559 let (sender, response) = crate::sse::SseStream::new().buffer(64).start();
560 let mut store = self.tasks.lock();
561 let stored = store.get_mut(owner, &id).ok_or(RpcError::TaskNotFound)?;
562 let _ = sender.try_send(tasks::event(rpc_id, "task", &stored.task.to_json(None)));
563 let state = stored.task.status.state;
564 let follow = !state.is_terminal() && (method == "SubscribeToTask" || !state.is_interrupted());
565 if follow {
566 if stored.subscribers.len() >= tasks::MAX_SUBSCRIBERS {
567 return Err(RpcError::UnsupportedOperation(format!("task {id} has {} streams already", tasks::MAX_SUBSCRIBERS)));
568 }
569 stored.subscribers.push(tasks::Subscriber { sender, rpc_id: rpc_id.to_string() });
570 }
571 Ok(response)
572 }
573
574 fn list_tasks(&self, params: &JsonValue, owner: &str) -> Result<String, RpcError> {
575 let page_size = match params.get("pageSize").and_then(JsonValue::as_f64) {
576 Some(n) if (1.0..=100.0).contains(&n) && n.fract() == 0.0 => n as usize,
577 Some(_) => return Err(RpcError::InvalidParams("pageSize must be 1 to 100".into())),
578 None => 50,
579 };
580 let state = match params.get("status").and_then(JsonValue::as_str) {
581 Some(s) => Some(TaskState::parse(s).ok_or_else(|| RpcError::InvalidParams(format!("unknown status {s}")))?),
582 None => None,
583 };
584 let context = params.get("contextId").and_then(JsonValue::as_str);
585 let offset: usize = match params.get("pageToken").and_then(JsonValue::as_str).filter(|t| !t.is_empty()) {
586 Some(t) => t.parse().map_err(|_| RpcError::InvalidParams("bad pageToken".into()))?,
587 None => 0,
588 };
589 let history = history_length(params)?.or(Some(0));
590 let tasks = self.tasks.lock();
591 let matching: Vec<&Task> = tasks
593 .order
594 .iter()
595 .rev()
596 .filter_map(|id| tasks.get(owner, id))
597 .filter(|t| state.is_none_or(|s| t.status.state == s) && context.is_none_or(|c| t.context_id == c))
598 .collect();
599 let page: Vec<String> = matching.iter().skip(offset).take(page_size).map(|t| t.to_json(history)).collect();
600 let next = if offset + page_size < matching.len() { (offset + page_size).to_string() } else { String::new() };
601 Ok(Obj::new()
602 .list("tasks", &page)
603 .raw("totalSize", &matching.len().to_string())
604 .raw("pageSize", &page_size.to_string())
605 .str("nextPageToken", &next)
606 .done())
607 }
608}
609
610enum Outcome {
612 Message(Message),
613 Task(String, bool),
615}
616
617fn history_length(params: &JsonValue) -> Result<Option<usize>, RpcError> {
618 match params.get("historyLength") {
619 None | Some(JsonValue::Null) => Ok(None),
620 Some(JsonValue::Number(n)) if *n >= 0.0 && n.fract() == 0.0 => Ok(Some(*n as usize)),
621 Some(_) => Err(RpcError::InvalidParams("historyLength must be a non-negative integer".into())),
622 }
623}
624
625fn check_version(request: &Request) -> Result<(), RpcError> {
627 let from_query = || {
628 let query = request.request_uri.split_once('?')?.1;
629 query.split('&').find_map(|p| p.strip_prefix("A2A-Version=")).map(str::to_string)
630 };
631 let version = request.header("A2A-Version").map(|h| h.value.trim().to_string()).or_else(from_query).unwrap_or_default();
632 let major_minor: String = version.split('.').take(2).collect::<Vec<_>>().join(".");
633 if major_minor == PROTOCOL_VERSION {
634 Ok(())
635 } else {
636 Err(RpcError::VersionNotSupported(if version.is_empty() { "0.3 (no A2A-Version)".to_string() } else { version }))
637 }
638}
639
640#[derive(Debug, Clone, PartialEq)]
642pub(crate) enum RpcError {
643 Parse(String),
644 InvalidRequest,
645 MethodNotFound(String),
646 InvalidParams(String),
647 TaskNotFound,
648 TaskNotCancelable,
649 PushNotificationNotSupported,
650 UnsupportedOperation(String),
651 VersionNotSupported(String),
652}
653
654impl RpcError {
655 fn parts(&self) -> (i32, &'static str, String) {
656 match self {
657 RpcError::Parse(e) => (-32700, "PARSE_ERROR", format!("invalid JSON: {e}")),
658 RpcError::InvalidRequest => (-32600, "INVALID_REQUEST", "not a JSON-RPC 2.0 request".to_string()),
659 RpcError::MethodNotFound(m) => (-32601, "METHOD_NOT_FOUND", format!("unknown method {m}")),
660 RpcError::InvalidParams(e) => (-32602, "INVALID_PARAMS", e.clone()),
661 RpcError::TaskNotFound => (-32001, "TASK_NOT_FOUND", "task not found".to_string()),
662 RpcError::TaskNotCancelable => (-32002, "TASK_NOT_CANCELABLE", "the task is finished and can't be canceled".to_string()),
663 RpcError::PushNotificationNotSupported => (-32003, "PUSH_NOTIFICATION_NOT_SUPPORTED", "push notifications aren't supported by this agent".to_string()),
664 RpcError::UnsupportedOperation(e) => (-32004, "UNSUPPORTED_OPERATION", e.clone()),
665 RpcError::VersionNotSupported(v) => (-32009, "VERSION_NOT_SUPPORTED", format!("A2A version {v} isn't supported; this agent speaks {PROTOCOL_VERSION}")),
666 }
667 }
668}
669
670fn error_json(id: &str, error: RpcError) -> String {
671 let (code, reason, message) = error.parts();
672 let data = format!(r#"[{{"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":{},"domain":"a2a-protocol.org"}}]"#, quote(reason));
673 format!(r#"{{"jsonrpc":"2.0","id":{id},"error":{{"code":{code},"message":{},"data":{data}}}}}"#, quote(&message))
674}
675
676impl Application for A2aServer {
677 fn execute(&self, request: &Request, connection: &ConnectionInfo) -> Result<Response, String> {
678 let path = crate::url::URL::request_target_path(&request.request_uri).unwrap_or("");
679 if path == AGENT_CARD_PATH && (request.method == "GET" || request.method == "HEAD") {
680 return Ok(self.card_response(request, connection));
681 }
682 if path == self.path {
683 if request.method == "POST" {
684 let owner = match self.caller(request) {
685 Some(owner) => owner,
686 None => {
687 let mut response = crate::error::problem(STATUS_CODE_REASON_PHRASE.n401_unauthorized, "A2A requests need a bearer token");
688 response.headers.push(crate::header::Header::new("WWW-Authenticate", r#"Bearer realm="a2a""#.to_string()));
689 return Ok(response);
690 }
691 };
692 return Ok(self.rpc(request, &owner));
693 }
694 let mut response = crate::error::problem(STATUS_CODE_REASON_PHRASE.n405_method_not_allowed, "A2A requests are JSON-RPC POSTs");
695 response.headers.push(crate::header::Header::new("Allow", "POST".to_string()));
696 return Ok(response);
697 }
698 match &self.fallback {
699 Some(app) => app.execute(request, connection),
700 None => Ok(crate::error::problem(STATUS_CODE_REASON_PHRASE.n404_not_found, "")),
701 }
702 }
703}
704
705#[cfg(test)]
706mod tests;