1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use super::message::A2AMessage;
7use super::task::{A2ATask, TaskStatus};
8
9pub(crate) fn default_protocol_version() -> String {
10 "0.3.0".to_string()
11}
12
13pub(crate) fn default_input_modes() -> Vec<String> {
14 vec!["text".to_string()]
15}
16
17pub(crate) fn default_output_modes() -> Vec<String> {
18 vec!["text".to_string()]
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct A2ATaskResult {
24 pub output: String,
26}
27
28impl A2ATaskResult {
29 pub fn new(output: impl Into<String>) -> Self {
31 Self {
32 output: output.into(),
33 }
34 }
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct A2ATaskDetails {
40 pub task: A2ATask,
42 pub result: Option<A2ATaskResult>,
44 pub error: Option<String>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct A2AWorkflow {
55 #[serde(skip_serializing_if = "Option::is_none")]
57 pub workflow_id: Option<String>,
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub name: Option<String>,
61 pub steps: Vec<WorkflowStep>,
63}
64
65impl A2AWorkflow {
66 pub fn new(steps: Vec<WorkflowStep>) -> Self {
68 Self {
69 workflow_id: None,
70 name: None,
71 steps,
72 }
73 }
74
75 pub fn with_workflow_id(mut self, id: impl Into<String>) -> Self {
77 self.workflow_id = Some(id.into());
78 self
79 }
80
81 pub fn with_name(mut self, name: impl Into<String>) -> Self {
83 self.name = Some(name.into());
84 self
85 }
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct WorkflowStep {
91 pub id: String,
93 pub message: A2AMessage,
95 #[serde(skip_serializing_if = "Option::is_none")]
97 pub skill_id: Option<String>,
98}
99
100impl WorkflowStep {
101 pub fn new(id: impl Into<String>, content: impl Into<String>) -> Self {
103 Self {
104 id: id.into(),
105 message: A2AMessage::user(content),
106 skill_id: None,
107 }
108 }
109
110 pub fn with_skill(
112 id: impl Into<String>,
113 content: impl Into<String>,
114 skill_id: impl Into<String>,
115 ) -> Self {
116 Self {
117 id: id.into(),
118 message: A2AMessage::user(content),
119 skill_id: Some(skill_id.into()),
120 }
121 }
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct A2ARequest {
127 pub jsonrpc: String,
129 pub id: u64,
131 pub method: String,
133 #[serde(skip_serializing_if = "Option::is_none")]
135 pub params: Option<Value>,
136 #[serde(skip_serializing_if = "Option::is_none")]
145 pub metadata: Option<Value>,
146}
147
148pub mod metadata_keys {
150 pub const TRACE_ID: &str = "trace_id";
152 pub const OWNER: &str = "owner";
154 pub const MESSAGE_ID: &str = "message_id";
156}
157
158impl A2ARequest {
159 pub fn new(id: u64, method: impl Into<String>, params: Option<Value>) -> Self {
161 Self {
162 jsonrpc: "2.0".to_string(),
163 id,
164 method: method.into(),
165 params,
166 metadata: None,
167 }
168 }
169
170 pub fn send_task(id: u64, message: &A2AMessage) -> Self {
172 let params = serde_json::to_value(message)
173 .ok()
174 .map(|v| serde_json::json!({ "message": v }));
175 Self::new(id, "tasks/send", params)
176 }
177
178 pub fn send_task_with_message_id(id: u64, message: &A2AMessage, message_id: &str) -> Self {
183 Self::send_task(id, message).with_message_id(message_id)
184 }
185
186 pub fn send_envelope(id: u64, envelope: &MessageEnvelope) -> Self {
192 let mut req = Self::send_task(id, &envelope.message);
193 if let Some(owner) = &envelope.owner {
194 req = req.with_owner(owner);
195 }
196 if let Some(trace) = &envelope.trace {
197 req = req.with_trace_id(trace.trace_id.as_str());
198 }
199 req
200 }
201
202 pub fn continue_task(id: u64, task_id: &str, message: &A2AMessage) -> Self {
208 let params = serde_json::to_value(message)
209 .ok()
210 .map(|v| serde_json::json!({ "taskId": task_id, "message": v }));
211 Self::new(id, "tasks/send", params)
212 }
213
214 pub fn get_task(id: u64, task_id: &str) -> Self {
216 Self::new(
217 id,
218 "tasks/get",
219 Some(serde_json::json!({ "taskId": task_id })),
220 )
221 }
222
223 pub fn cancel_task(id: u64, task_id: &str) -> Self {
225 Self::new(
226 id,
227 "tasks/cancel",
228 Some(serde_json::json!({ "taskId": task_id })),
229 )
230 }
231
232 pub fn run_workflow(id: u64, workflow: &A2AWorkflow) -> Self {
237 let params = serde_json::to_value(workflow)
238 .ok()
239 .map(|v| serde_json::json!({ "workflow": v }));
240 Self::new(id, "tasks/runWorkflow", params)
241 }
242
243 pub fn with_metadata(mut self, metadata: Value) -> Self {
245 self.metadata = Some(metadata);
246 self
247 }
248
249 pub fn with_trace_id(mut self, trace_id: impl Into<String>) -> Self {
251 let meta = self.metadata.get_or_insert_with(|| serde_json::json!({}));
252 meta[metadata_keys::TRACE_ID] = serde_json::Value::String(trace_id.into());
253 self
254 }
255
256 pub fn with_owner(mut self, owner: impl Into<String>) -> Self {
258 let meta = self.metadata.get_or_insert_with(|| serde_json::json!({}));
259 meta[metadata_keys::OWNER] = serde_json::Value::String(owner.into());
260 self
261 }
262
263 pub fn with_message_id(mut self, message_id: impl Into<String>) -> Self {
265 let meta = self.metadata.get_or_insert_with(|| serde_json::json!({}));
266 meta[metadata_keys::MESSAGE_ID] = serde_json::Value::String(message_id.into());
267 self
268 }
269
270 pub fn trace_id(&self) -> Option<&str> {
272 self.metadata
273 .as_ref()
274 .and_then(|m| m.get(metadata_keys::TRACE_ID))
275 .and_then(serde_json::Value::as_str)
276 }
277
278 pub fn owner(&self) -> Option<&str> {
280 self.metadata
281 .as_ref()
282 .and_then(|m| m.get(metadata_keys::OWNER))
283 .and_then(serde_json::Value::as_str)
284 }
285
286 pub fn message_id(&self) -> Option<&str> {
291 if let Some(id) = self
292 .metadata
293 .as_ref()
294 .and_then(|m| m.get(metadata_keys::MESSAGE_ID))
295 .and_then(serde_json::Value::as_str)
296 {
297 return Some(id);
298 }
299 self.params
300 .as_ref()
301 .and_then(|p| p.get("messageId"))
302 .and_then(serde_json::Value::as_str)
303 }
304
305 pub fn task_id(&self) -> Option<&str> {
307 self.params
308 .as_ref()
309 .and_then(|p| p.get("taskId"))
310 .and_then(serde_json::Value::as_str)
311 }
312}
313
314#[derive(Debug, Clone, Serialize, Deserialize)]
316pub struct A2AResponse {
317 pub jsonrpc: String,
319 pub id: u64,
321 #[serde(skip_serializing_if = "Option::is_none")]
323 pub result: Option<Value>,
324 #[serde(skip_serializing_if = "Option::is_none")]
326 pub error: Option<A2AErrorData>,
327}
328
329impl A2AResponse {
330 pub fn ok(id: u64, result: Value) -> Self {
332 Self {
333 jsonrpc: "2.0".to_string(),
334 id,
335 result: Some(result),
336 error: None,
337 }
338 }
339
340 pub fn error(id: u64, code: i32, message: impl Into<String>) -> Self {
342 Self {
343 jsonrpc: "2.0".to_string(),
344 id,
345 result: None,
346 error: Some(A2AErrorData {
347 code,
348 message: message.into(),
349 }),
350 }
351 }
352
353 pub fn from_error_data(id: u64, error: A2AErrorData) -> Self {
355 Self {
356 jsonrpc: "2.0".to_string(),
357 id,
358 result: None,
359 error: Some(error),
360 }
361 }
362
363 pub fn is_error(&self) -> bool {
365 self.error.is_some()
366 }
367
368 pub fn into_result(self) -> Result<Value, A2AErrorData> {
370 if let Some(err) = self.error {
371 return Err(err);
372 }
373 Ok(self.result.unwrap_or(Value::Null))
374 }
375}
376
377#[derive(Debug, Clone, Serialize, Deserialize)]
379pub struct A2AErrorData {
380 pub code: i32,
382 pub message: String,
384}
385
386impl A2AErrorData {
387 pub fn new(code: i32, message: impl Into<String>) -> Self {
389 Self {
390 code,
391 message: message.into(),
392 }
393 }
394
395 pub fn method_not_found() -> Self {
397 Self::new(-32601, "Method not found")
398 }
399
400 pub fn invalid_params(msg: impl Into<String>) -> Self {
402 Self::new(-32602, msg)
403 }
404
405 pub fn internal_error(msg: impl Into<String>) -> Self {
407 Self::new(-32603, msg)
408 }
409}
410
411impl std::fmt::Display for A2AErrorData {
412 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
413 write!(f, "A2A Error [{}]: {}", self.code, self.message)
414 }
415}
416
417impl std::error::Error for A2AErrorData {}
418
419#[derive(Debug, Clone, Serialize, Deserialize)]
425#[serde(tag = "kind", rename_all = "kebab-case")]
426pub enum TaskPushNotification {
427 #[serde(rename_all = "camelCase")]
430 StatusUpdate {
431 id: String,
433 status: TaskStatus,
435 #[serde(skip_serializing_if = "Option::is_none")]
437 error: Option<String>,
438 },
439 #[serde(rename_all = "camelCase")]
441 ArtifactUpdate {
442 id: String,
444 artifact: A2ATaskResult,
446 },
447}
448
449impl TaskPushNotification {
450 pub fn status(id: impl Into<String>, status: TaskStatus) -> Self {
452 TaskPushNotification::StatusUpdate {
453 id: id.into(),
454 status,
455 error: None,
456 }
457 }
458
459 pub fn status_with_error(
461 id: impl Into<String>,
462 status: TaskStatus,
463 error: impl Into<String>,
464 ) -> Self {
465 TaskPushNotification::StatusUpdate {
466 id: id.into(),
467 status,
468 error: Some(error.into()),
469 }
470 }
471
472 pub fn artifact(id: impl Into<String>, artifact: A2ATaskResult) -> Self {
474 TaskPushNotification::ArtifactUpdate {
475 id: id.into(),
476 artifact,
477 }
478 }
479
480 pub fn id(&self) -> &str {
482 match self {
483 TaskPushNotification::StatusUpdate { id, .. }
484 | TaskPushNotification::ArtifactUpdate { id, .. } => id,
485 }
486 }
487
488 pub fn status_value(&self) -> Option<TaskStatus> {
490 match self {
491 TaskPushNotification::StatusUpdate { status, .. } => Some(*status),
492 TaskPushNotification::ArtifactUpdate { .. } => None,
493 }
494 }
495}
496
497#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
504pub struct TraceContext {
505 pub version: u8,
507 pub trace_id: String,
509 pub parent_id: String,
511 pub flags: u8,
513}
514
515impl TraceContext {
516 pub fn new(trace_id: impl Into<String>, parent_id: impl Into<String>) -> Self {
518 Self {
519 version: 0,
520 trace_id: trace_id.into(),
521 parent_id: parent_id.into(),
522 flags: 0,
523 }
524 }
525
526 pub fn sampled(mut self) -> Self {
528 self.flags |= 0b0000_0001;
529 self
530 }
531
532 pub fn is_sampled(&self) -> bool {
534 self.flags & 0b0000_0001 != 0
535 }
536
537 pub fn parse(s: &str) -> Option<Self> {
539 let mut parts = s.trim().split('-');
540 let version = parts.next()?;
541 let trace_id = parts.next()?;
542 let parent_id = parts.next()?;
543 let flags = parts.next()?;
544 if parts.next().is_some() {
545 return None;
546 }
547 let version = u8::from_str_radix(version, 16).ok()?;
548 if trace_id.len() != 32 || !trace_id.bytes().all(|b| b.is_ascii_hexdigit()) {
549 return None;
550 }
551 if parent_id.len() != 16 || !parent_id.bytes().all(|b| b.is_ascii_hexdigit()) {
552 return None;
553 }
554 let flags = u8::from_str_radix(flags, 16).ok()?;
555 Some(Self {
556 version,
557 trace_id: trace_id.to_string(),
558 parent_id: parent_id.to_string(),
559 flags,
560 })
561 }
562
563 pub fn to_traceparent(&self) -> String {
565 format!(
566 "{:02x}-{}-{}-{:02x}",
567 self.version, self.trace_id, self.parent_id, self.flags
568 )
569 }
570}
571
572#[derive(Debug, Clone, Serialize, Deserialize)]
581pub struct MessageEnvelope {
582 pub protocol_version: String,
584 pub message: A2AMessage,
586 #[serde(skip_serializing_if = "Option::is_none")]
588 pub trace: Option<TraceContext>,
589 #[serde(skip_serializing_if = "Option::is_none")]
591 pub owner: Option<String>,
592 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
594 pub headers: HashMap<String, String>,
595}
596
597impl MessageEnvelope {
598 pub fn new(message: A2AMessage) -> Self {
600 Self {
601 protocol_version: "0.3.0".to_string(),
602 message,
603 trace: None,
604 owner: None,
605 headers: HashMap::new(),
606 }
607 }
608
609 pub fn with_trace(mut self, trace: TraceContext) -> Self {
611 self.trace = Some(trace);
612 self
613 }
614
615 pub fn with_owner(mut self, owner: impl Into<String>) -> Self {
617 self.owner = Some(owner.into());
618 self
619 }
620
621 pub fn with_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
623 self.headers.insert(key.into(), value.into());
624 self
625 }
626
627 pub fn into_message(self) -> A2AMessage {
629 self.message
630 }
631}
632
633#[derive(Debug, Clone, Default)]
638pub struct TaskFilter {
639 pub owner: Option<String>,
641 pub statuses: Option<Vec<TaskStatus>>,
643}
644
645impl TaskFilter {
646 pub fn new() -> Self {
648 Self::default()
649 }
650
651 pub fn with_owner(mut self, owner: impl Into<String>) -> Self {
653 self.owner = Some(owner.into());
654 self
655 }
656
657 pub fn with_statuses(mut self, statuses: Vec<TaskStatus>) -> Self {
659 self.statuses = Some(statuses);
660 self
661 }
662
663 pub fn matches(&self, task: &A2ATask) -> bool {
665 if let Some(owner) = &self.owner {
666 if task.owner.as_deref() != Some(owner.as_str()) {
667 return false;
668 }
669 }
670 if let Some(statuses) = &self.statuses {
671 if !statuses.contains(&task.status) {
672 return false;
673 }
674 }
675 true
676 }
677}