strands_agents/multiagent/a2a/
types.rs

1//! A2A protocol type definitions.
2
3use serde::{Deserialize, Serialize};
4
5/// State of an A2A task.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "lowercase")]
8pub enum A2ATaskState {
9    Pending,
10    Working,
11    Completed,
12    Failed,
13    Cancelled,
14}
15
16/// An A2A task.
17#[derive(Debug, Clone, Serialize, Deserialize)]
18#[serde(rename_all = "camelCase")]
19pub struct A2ATask {
20    pub id: String,
21    pub context_id: String,
22    pub state: A2ATaskState,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub message: Option<A2AMessage>,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub artifacts: Option<Vec<A2AArtifact>>,
27}
28
29impl A2ATask {
30    pub fn new(id: impl Into<String>, context_id: impl Into<String>) -> Self {
31        Self {
32            id: id.into(),
33            context_id: context_id.into(),
34            state: A2ATaskState::Pending,
35            message: None,
36            artifacts: None,
37        }
38    }
39}
40
41/// An artifact produced by a task.
42#[derive(Debug, Clone, Serialize, Deserialize)]
43#[serde(rename_all = "camelCase")]
44pub struct A2AArtifact {
45    pub name: String,
46    pub parts: Vec<A2APart>,
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub index: Option<usize>,
49}
50
51/// An A2A message.
52#[derive(Debug, Clone, Serialize, Deserialize)]
53#[serde(rename_all = "camelCase")]
54pub struct A2AMessage {
55    pub role: String,
56    pub parts: Vec<A2APart>,
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub context_id: Option<String>,
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub task_id: Option<String>,
61}
62
63impl A2AMessage {
64    pub fn user(parts: Vec<A2APart>) -> Self {
65        Self {
66            role: "user".to_string(),
67            parts,
68            context_id: None,
69            task_id: None,
70        }
71    }
72
73    pub fn agent(parts: Vec<A2APart>, context_id: Option<String>, task_id: Option<String>) -> Self {
74        Self {
75            role: "agent".to_string(),
76            parts,
77            context_id,
78            task_id,
79        }
80    }
81}
82
83/// A part of an A2A message.
84#[derive(Debug, Clone, Serialize, Deserialize)]
85#[serde(tag = "type", rename_all = "lowercase")]
86pub enum A2APart {
87    Text { text: String },
88    File { file: A2AFile },
89    Data { data: serde_json::Value },
90}
91
92impl A2APart {
93    pub fn text(text: impl Into<String>) -> Self {
94        Self::Text { text: text.into() }
95    }
96
97    pub fn data(data: serde_json::Value) -> Self {
98        Self::Data { data }
99    }
100}
101
102/// A file in an A2A message.
103#[derive(Debug, Clone, Serialize, Deserialize)]
104#[serde(rename_all = "camelCase")]
105pub struct A2AFile {
106    pub name: String,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub mime_type: Option<String>,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub bytes: Option<String>,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub uri: Option<String>,
113}
114
115/// A2A request.
116#[derive(Debug, Clone, Serialize, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub struct A2ARequest {
119    pub jsonrpc: String,
120    pub id: serde_json::Value,
121    pub method: String,
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub params: Option<serde_json::Value>,
124}
125
126impl A2ARequest {
127    pub fn new(id: impl Into<serde_json::Value>, method: impl Into<String>) -> Self {
128        Self {
129            jsonrpc: "2.0".to_string(),
130            id: id.into(),
131            method: method.into(),
132            params: None,
133        }
134    }
135
136    pub fn with_params(mut self, params: serde_json::Value) -> Self {
137        self.params = Some(params);
138        self
139    }
140}
141
142/// A2A response.
143#[derive(Debug, Clone, Serialize, Deserialize)]
144#[serde(rename_all = "camelCase")]
145pub struct A2AResponse {
146    pub jsonrpc: String,
147    pub id: serde_json::Value,
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub result: Option<serde_json::Value>,
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub error: Option<A2AError>,
152}
153
154impl A2AResponse {
155    pub fn success(id: serde_json::Value, result: serde_json::Value) -> Self {
156        Self {
157            jsonrpc: "2.0".to_string(),
158            id,
159            result: Some(result),
160            error: None,
161        }
162    }
163
164    pub fn error(id: serde_json::Value, error: A2AError) -> Self {
165        Self {
166            jsonrpc: "2.0".to_string(),
167            id,
168            result: None,
169            error: Some(error),
170        }
171    }
172}
173
174/// A2A error.
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct A2AError {
177    pub code: i32,
178    pub message: String,
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub data: Option<serde_json::Value>,
181}
182
183impl A2AError {
184    pub fn internal(message: impl Into<String>) -> Self {
185        Self {
186            code: -32603,
187            message: message.into(),
188            data: None,
189        }
190    }
191
192    pub fn invalid_request(message: impl Into<String>) -> Self {
193        Self {
194            code: -32600,
195            message: message.into(),
196            data: None,
197        }
198    }
199
200    pub fn method_not_found(message: impl Into<String>) -> Self {
201        Self {
202            code: -32601,
203            message: message.into(),
204            data: None,
205        }
206    }
207}
208
209/// Agent capabilities.
210#[derive(Debug, Clone, Default, Serialize, Deserialize)]
211pub struct A2ACapabilities {
212    #[serde(default)]
213    pub streaming: bool,
214    #[serde(default)]
215    pub push_notifications: bool,
216    #[serde(default)]
217    pub state_transition_history: bool,
218}
219
220/// Agent skill.
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct AgentSkill {
223    pub id: String,
224    pub name: String,
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub description: Option<String>,
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub tags: Option<Vec<String>>,
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub examples: Option<Vec<String>>,
231}
232
233impl AgentSkill {
234    pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
235        Self {
236            id: id.into(),
237            name: name.into(),
238            description: None,
239            tags: None,
240            examples: None,
241        }
242    }
243
244    pub fn with_description(mut self, description: impl Into<String>) -> Self {
245        self.description = Some(description.into());
246        self
247    }
248}
249
250/// Agent card describing the agent.
251#[derive(Debug, Clone, Serialize, Deserialize)]
252#[serde(rename_all = "camelCase")]
253pub struct AgentCard {
254    pub name: String,
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub description: Option<String>,
257    pub url: String,
258    pub version: String,
259    pub capabilities: A2ACapabilities,
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub skills: Option<Vec<AgentSkill>>,
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub default_input_modes: Option<Vec<String>>,
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub default_output_modes: Option<Vec<String>>,
266}
267
268impl AgentCard {
269    pub fn new(
270        name: impl Into<String>,
271        url: impl Into<String>,
272        version: impl Into<String>,
273    ) -> Self {
274        Self {
275            name: name.into(),
276            description: None,
277            url: url.into(),
278            version: version.into(),
279            capabilities: A2ACapabilities::default(),
280            skills: None,
281            default_input_modes: None,
282            default_output_modes: None,
283        }
284    }
285
286    pub fn with_description(mut self, description: impl Into<String>) -> Self {
287        self.description = Some(description.into());
288        self
289    }
290
291    pub fn with_streaming(mut self, streaming: bool) -> Self {
292        self.capabilities.streaming = streaming;
293        self
294    }
295
296    pub fn with_skills(mut self, skills: Vec<AgentSkill>) -> Self {
297        self.skills = Some(skills);
298        self
299    }
300}
301