1use serde::{Deserialize, Serialize};
15use serde_json::Value;
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct AgentCard {
23 pub name: String,
25 pub description: String,
27 pub url: String,
29 pub capabilities: Vec<String>,
31 #[serde(default = "default_version")]
33 pub version: String,
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub provider: Option<String>,
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub documentation_url: Option<String>,
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub authentication: Option<Vec<String>>,
43 #[serde(default = "default_input_modes")]
45 pub default_input_modes: Vec<String>,
46 #[serde(default = "default_output_modes")]
48 pub default_output_modes: Vec<String>,
49}
50
51fn default_version() -> String {
52 env!("CARGO_PKG_VERSION").to_string()
53}
54
55fn default_input_modes() -> Vec<String> {
56 vec!["text".to_string()]
57}
58
59fn default_output_modes() -> Vec<String> {
60 vec!["text".to_string()]
61}
62
63impl AgentCard {
64 pub fn new(
66 name: impl Into<String>,
67 description: impl Into<String>,
68 url: impl Into<String>,
69 ) -> Self {
70 Self {
71 name: name.into(),
72 description: description.into(),
73 url: url.into(),
74 capabilities: Vec::new(),
75 version: default_version(),
76 provider: None,
77 documentation_url: None,
78 authentication: None,
79 default_input_modes: default_input_modes(),
80 default_output_modes: default_output_modes(),
81 }
82 }
83
84 pub fn with_capability(mut self, capability: impl Into<String>) -> Self {
86 self.capabilities.push(capability.into());
87 self
88 }
89
90 pub fn with_version(mut self, version: impl Into<String>) -> Self {
92 self.version = version.into();
93 self
94 }
95
96 pub fn with_provider(mut self, provider: impl Into<String>) -> Self {
98 self.provider = Some(provider.into());
99 self
100 }
101
102 pub fn with_documentation_url(mut self, url: impl Into<String>) -> Self {
104 self.documentation_url = Some(url.into());
105 self
106 }
107
108 pub fn with_authentication(mut self, schemes: Vec<String>) -> Self {
110 self.authentication = Some(schemes);
111 self
112 }
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
117#[serde(rename_all = "lowercase")]
118pub enum TaskStatus {
119 Submitted,
121 Working,
123 InputRequired,
125 Completed,
127 Failed,
129 Cancelled,
131}
132
133impl std::fmt::Display for TaskStatus {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 match self {
136 TaskStatus::Submitted => write!(f, "submitted"),
137 TaskStatus::Working => write!(f, "working"),
138 TaskStatus::InputRequired => write!(f, "input_required"),
139 TaskStatus::Completed => write!(f, "completed"),
140 TaskStatus::Failed => write!(f, "failed"),
141 TaskStatus::Cancelled => write!(f, "cancelled"),
142 }
143 }
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct A2AMessage {
149 pub role: String,
151 pub content: String,
153}
154
155impl A2AMessage {
156 pub fn new(role: impl Into<String>, content: impl Into<String>) -> Self {
158 Self {
159 role: role.into(),
160 content: content.into(),
161 }
162 }
163
164 pub fn user(content: impl Into<String>) -> Self {
166 Self::new("user", content)
167 }
168
169 pub fn agent(content: impl Into<String>) -> Self {
171 Self::new("agent", content)
172 }
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct A2ATask {
178 pub id: String,
180 pub message: A2AMessage,
182 pub status: TaskStatus,
184}
185
186impl A2ATask {
187 pub fn new(id: impl Into<String>, message: A2AMessage) -> Self {
189 Self {
190 id: id.into(),
191 message,
192 status: TaskStatus::Submitted,
193 }
194 }
195
196 pub fn with_status(mut self, status: TaskStatus) -> Self {
198 self.status = status;
199 self
200 }
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct A2ATaskResult {
206 pub output: String,
208}
209
210impl A2ATaskResult {
211 pub fn new(output: impl Into<String>) -> Self {
213 Self {
214 output: output.into(),
215 }
216 }
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct A2ARequest {
222 pub jsonrpc: String,
224 pub id: u64,
226 pub method: String,
228 #[serde(skip_serializing_if = "Option::is_none")]
230 pub params: Option<Value>,
231}
232
233impl A2ARequest {
234 pub fn new(id: u64, method: impl Into<String>, params: Option<Value>) -> Self {
236 Self {
237 jsonrpc: "2.0".to_string(),
238 id,
239 method: method.into(),
240 params,
241 }
242 }
243
244 pub fn send_task(id: u64, message: &A2AMessage) -> Self {
246 let params = serde_json::to_value(message)
247 .ok()
248 .map(|v| serde_json::json!({ "message": v }));
249 Self::new(id, "tasks/send", params)
250 }
251
252 pub fn get_task(id: u64, task_id: &str) -> Self {
254 Self::new(
255 id,
256 "tasks/get",
257 Some(serde_json::json!({ "taskId": task_id })),
258 )
259 }
260
261 pub fn cancel_task(id: u64, task_id: &str) -> Self {
263 Self::new(
264 id,
265 "tasks/cancel",
266 Some(serde_json::json!({ "taskId": task_id })),
267 )
268 }
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize)]
273pub struct A2AResponse {
274 pub jsonrpc: String,
276 pub id: u64,
278 #[serde(skip_serializing_if = "Option::is_none")]
280 pub result: Option<Value>,
281 #[serde(skip_serializing_if = "Option::is_none")]
283 pub error: Option<A2AErrorData>,
284}
285
286impl A2AResponse {
287 pub fn ok(id: u64, result: Value) -> Self {
289 Self {
290 jsonrpc: "2.0".to_string(),
291 id,
292 result: Some(result),
293 error: None,
294 }
295 }
296
297 pub fn error(id: u64, code: i32, message: impl Into<String>) -> Self {
299 Self {
300 jsonrpc: "2.0".to_string(),
301 id,
302 result: None,
303 error: Some(A2AErrorData {
304 code,
305 message: message.into(),
306 }),
307 }
308 }
309
310 pub fn from_error_data(id: u64, error: A2AErrorData) -> Self {
312 Self {
313 jsonrpc: "2.0".to_string(),
314 id,
315 result: None,
316 error: Some(error),
317 }
318 }
319
320 pub fn is_error(&self) -> bool {
322 self.error.is_some()
323 }
324
325 pub fn into_result(self) -> Result<Value, A2AErrorData> {
327 if let Some(err) = self.error {
328 return Err(err);
329 }
330 Ok(self.result.unwrap_or(Value::Null))
331 }
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize)]
336pub struct A2AErrorData {
337 pub code: i32,
339 pub message: String,
341}
342
343impl A2AErrorData {
344 pub fn new(code: i32, message: impl Into<String>) -> Self {
346 Self {
347 code,
348 message: message.into(),
349 }
350 }
351
352 pub fn method_not_found() -> Self {
354 Self::new(-32601, "Method not found")
355 }
356
357 pub fn invalid_params(msg: impl Into<String>) -> Self {
359 Self::new(-32602, msg)
360 }
361
362 pub fn internal_error(msg: impl Into<String>) -> Self {
364 Self::new(-32603, msg)
365 }
366}
367
368impl std::fmt::Display for A2AErrorData {
369 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
370 write!(f, "A2A Error [{}]: {}", self.code, self.message)
371 }
372}
373
374impl std::error::Error for A2AErrorData {}
375
376#[cfg(test)]
377mod tests {
378 use super::*;
379
380 #[test]
381 fn agent_card_new() {
382 let card = AgentCard::new("test-agent", "A test agent", "http://localhost:8080");
383 assert_eq!(card.name, "test-agent");
384 assert_eq!(card.description, "A test agent");
385 assert_eq!(card.url, "http://localhost:8080");
386 assert!(card.capabilities.is_empty());
387 }
388
389 #[test]
390 fn agent_card_with_capabilities() {
391 let card = AgentCard::new("agent", "desc", "http://localhost")
392 .with_capability("text-generation")
393 .with_capability("tool-use");
394 assert_eq!(card.capabilities.len(), 2);
395 assert_eq!(card.capabilities[0], "text-generation");
396 assert_eq!(card.capabilities[1], "tool-use");
397 }
398
399 #[test]
400 fn agent_card_serialization() {
401 let card =
402 AgentCard::new("agent", "desc", "http://localhost").with_capability("text-generation");
403 let json = serde_json::to_string(&card).unwrap();
404 assert!(json.contains("\"name\":\"agent\""));
405 assert!(json.contains("\"capabilities\""));
406 assert!(json.contains("\"text-generation\""));
407 }
408
409 #[test]
410 fn agent_card_deserialization() {
411 let json = r#"{"name":"agent","description":"desc","url":"http://localhost","capabilities":[],"version":"0.1.0"}"#;
412 let card: AgentCard = serde_json::from_str(json).unwrap();
413 assert_eq!(card.name, "agent");
414 assert_eq!(card.version, "0.1.0");
415 }
416
417 #[test]
418 fn task_status_serialization() {
419 let statuses = vec![
420 TaskStatus::Submitted,
421 TaskStatus::Working,
422 TaskStatus::Completed,
423 TaskStatus::Failed,
424 TaskStatus::Cancelled,
425 ];
426 let json = serde_json::to_string(&statuses).unwrap();
427 assert!(json.contains("\"submitted\""));
428 assert!(json.contains("\"working\""));
429 assert!(json.contains("\"completed\""));
430 assert!(json.contains("\"failed\""));
431 assert!(json.contains("\"cancelled\""));
432 }
433
434 #[test]
435 fn task_status_display() {
436 assert_eq!(TaskStatus::Submitted.to_string(), "submitted");
437 assert_eq!(TaskStatus::Working.to_string(), "working");
438 assert_eq!(TaskStatus::Completed.to_string(), "completed");
439 assert_eq!(TaskStatus::Failed.to_string(), "failed");
440 assert_eq!(TaskStatus::Cancelled.to_string(), "cancelled");
441 }
442
443 #[test]
444 fn a2a_message_user() {
445 let msg = A2AMessage::user("hello");
446 assert_eq!(msg.role, "user");
447 assert_eq!(msg.content, "hello");
448 }
449
450 #[test]
451 fn a2a_message_agent() {
452 let msg = A2AMessage::agent("response");
453 assert_eq!(msg.role, "agent");
454 assert_eq!(msg.content, "response");
455 }
456
457 #[test]
458 fn a2a_task_new() {
459 let task = A2ATask::new("task-1", A2AMessage::user("hello"));
460 assert_eq!(task.id, "task-1");
461 assert_eq!(task.status, TaskStatus::Submitted);
462 assert_eq!(task.message.content, "hello");
463 }
464
465 #[test]
466 fn a2a_task_with_status() {
467 let task =
468 A2ATask::new("task-1", A2AMessage::user("hello")).with_status(TaskStatus::Completed);
469 assert_eq!(task.status, TaskStatus::Completed);
470 }
471
472 #[test]
473 fn a2a_task_result() {
474 let result = A2ATaskResult::new("output text");
475 assert_eq!(result.output, "output text");
476 }
477
478 #[test]
479 fn a2a_request_new() {
480 let req = A2ARequest::new(1, "tasks/send", None);
481 assert_eq!(req.jsonrpc, "2.0");
482 assert_eq!(req.id, 1);
483 assert_eq!(req.method, "tasks/send");
484 assert!(req.params.is_none());
485 }
486
487 #[test]
488 fn a2a_request_send_task() {
489 let msg = A2AMessage::user("hello");
490 let req = A2ARequest::send_task(1, &msg);
491 assert_eq!(req.method, "tasks/send");
492 assert!(req.params.is_some());
493 let params = req.params.unwrap();
494 assert!(params.get("message").is_some());
495 }
496
497 #[test]
498 fn a2a_request_get_task() {
499 let req = A2ARequest::get_task(2, "task-123");
500 assert_eq!(req.method, "tasks/get");
501 let params = req.params.unwrap();
502 assert_eq!(params["taskId"], "task-123");
503 }
504
505 #[test]
506 fn a2a_request_cancel_task() {
507 let req = A2ARequest::cancel_task(3, "task-456");
508 assert_eq!(req.method, "tasks/cancel");
509 let params = req.params.unwrap();
510 assert_eq!(params["taskId"], "task-456");
511 }
512
513 #[test]
514 fn a2a_request_serialization_skips_none_params() {
515 let req = A2ARequest::new(1, "tasks/send", None);
516 let json = serde_json::to_string(&req).unwrap();
517 assert!(!json.contains("params"));
518 }
519
520 #[test]
521 fn a2a_response_ok() {
522 let resp = A2AResponse::ok(1, serde_json::json!({"status": "completed"}));
523 assert!(!resp.is_error());
524 assert!(resp.result.is_some());
525 assert!(resp.error.is_none());
526 }
527
528 #[test]
529 fn a2a_response_error() {
530 let resp = A2AResponse::error(1, -32601, "Method not found");
531 assert!(resp.is_error());
532 assert!(resp.result.is_none());
533 let err = resp.error.unwrap();
534 assert_eq!(err.code, -32601);
535 }
536
537 #[test]
538 fn a2a_response_into_result_ok() {
539 let resp = A2AResponse::ok(1, serde_json::json!({"output": "done"}));
540 let result = resp.into_result();
541 assert!(result.is_ok());
542 assert_eq!(result.unwrap()["output"], "done");
543 }
544
545 #[test]
546 fn a2a_response_into_result_err() {
547 let resp = A2AResponse::error(1, -32601, "Method not found");
548 let result = resp.into_result();
549 assert!(result.is_err());
550 assert_eq!(result.unwrap_err().code, -32601);
551 }
552
553 #[test]
554 fn a2a_response_serialization() {
555 let resp = A2AResponse::ok(1, serde_json::json!({"status": "completed"}));
556 let json = serde_json::to_string(&resp).unwrap();
557 assert!(json.contains("\"jsonrpc\":\"2.0\""));
558 assert!(json.contains("\"id\":1"));
559 assert!(json.contains("\"result\""));
560 assert!(!json.contains("\"error\""));
561 }
562
563 #[test]
564 fn a2a_error_data_display() {
565 let err = A2AErrorData::new(-1, "boom");
566 assert_eq!(format!("{}", err), "A2A Error [-1]: boom");
567 }
568
569 #[test]
570 fn a2a_error_data_standard_errors() {
571 let err = A2AErrorData::method_not_found();
572 assert_eq!(err.code, -32601);
573
574 let err = A2AErrorData::invalid_params("bad input");
575 assert_eq!(err.code, -32602);
576 assert!(err.message.contains("bad input"));
577
578 let err = A2AErrorData::internal_error("oops");
579 assert_eq!(err.code, -32603);
580 }
581
582 #[test]
583 fn roundtrip_request_json() {
584 let req = A2ARequest::send_task(42, &A2AMessage::user("test"));
585 let json = serde_json::to_string(&req).unwrap();
586 let parsed: A2ARequest = serde_json::from_str(&json).unwrap();
587 assert_eq!(parsed.id, 42);
588 assert_eq!(parsed.method, "tasks/send");
589 }
590
591 #[test]
592 fn roundtrip_response_json() {
593 let resp = A2AResponse::ok(7, serde_json::json!({"task": {"id": "t1"}}));
594 let json = serde_json::to_string(&resp).unwrap();
595 let parsed: A2AResponse = serde_json::from_str(&json).unwrap();
596 assert_eq!(parsed.id, 7);
597 assert!(!parsed.is_error());
598 }
599}