Skip to main content

codetether_agent/a2a/
types.rs

1//! A2A Protocol Types
2//!
3//! Types aligned with the A2A specification (a2a.json schema)
4
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8/// A2A Task States
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "kebab-case")]
11pub enum TaskState {
12    Submitted,
13    Working,
14    Completed,
15    Failed,
16    Cancelled,
17    InputRequired,
18    Rejected,
19    AuthRequired,
20}
21
22impl TaskState {
23    /// Check if this is a terminal state
24    pub fn is_terminal(&self) -> bool {
25        matches!(
26            self,
27            TaskState::Completed | TaskState::Failed | TaskState::Cancelled | TaskState::Rejected
28        )
29    }
30
31    /// Check if this is an active state
32    pub fn is_active(&self) -> bool {
33        !self.is_terminal()
34    }
35}
36
37/// An A2A Task
38#[derive(Debug, Clone, Serialize, Deserialize)]
39#[serde(rename_all = "camelCase")]
40pub struct Task {
41    pub id: String,
42    pub context_id: Option<String>,
43    pub status: TaskStatus,
44    #[serde(default)]
45    pub artifacts: Vec<Artifact>,
46    #[serde(default)]
47    pub history: Vec<Message>,
48    #[serde(default)]
49    pub metadata: HashMap<String, serde_json::Value>,
50}
51
52/// Task status information
53#[derive(Debug, Clone, Serialize, Deserialize)]
54#[serde(rename_all = "camelCase")]
55pub struct TaskStatus {
56    pub state: TaskState,
57    #[serde(default)]
58    pub message: Option<Message>,
59    pub timestamp: Option<String>,
60}
61
62/// An A2A Message
63#[derive(Debug, Clone, Serialize, Deserialize)]
64#[serde(rename_all = "camelCase")]
65pub struct Message {
66    pub message_id: String,
67    pub role: MessageRole,
68    pub parts: Vec<Part>,
69    #[serde(default)]
70    pub context_id: Option<String>,
71    #[serde(default)]
72    pub task_id: Option<String>,
73    #[serde(default)]
74    pub metadata: HashMap<String, serde_json::Value>,
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
78#[serde(rename_all = "lowercase")]
79pub enum MessageRole {
80    User,
81    Agent,
82}
83
84/// A part of a message
85#[derive(Debug, Clone, Serialize, Deserialize)]
86#[serde(tag = "kind", rename_all = "camelCase")]
87pub enum Part {
88    #[serde(rename = "text")]
89    Text { text: String },
90    #[serde(rename = "file")]
91    File { file: FileContent },
92    #[serde(rename = "data")]
93    Data { data: serde_json::Value },
94}
95
96/// File content (bytes or URI)
97#[derive(Debug, Clone, Serialize, Deserialize)]
98#[serde(rename_all = "camelCase")]
99pub struct FileContent {
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub bytes: Option<String>, // Base64 encoded
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub uri: Option<String>,
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub mime_type: Option<String>,
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub name: Option<String>,
108}
109
110/// An artifact produced by a task
111#[derive(Debug, Clone, Serialize, Deserialize)]
112#[serde(rename_all = "camelCase")]
113pub struct Artifact {
114    pub artifact_id: String,
115    pub parts: Vec<Part>,
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub name: Option<String>,
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub description: Option<String>,
120    #[serde(default)]
121    pub metadata: HashMap<String, serde_json::Value>,
122}
123
124/// Agent Card - self-describing manifest
125#[derive(Debug, Clone, Serialize, Deserialize)]
126#[serde(rename_all = "camelCase")]
127pub struct AgentCard {
128    pub name: String,
129    pub description: String,
130    pub url: String,
131    pub version: String,
132    #[serde(default = "default_protocol_version")]
133    pub protocol_version: String,
134    pub capabilities: AgentCapabilities,
135    pub skills: Vec<AgentSkill>,
136    #[serde(default)]
137    pub default_input_modes: Vec<String>,
138    #[serde(default)]
139    pub default_output_modes: Vec<String>,
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub provider: Option<AgentProvider>,
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub icon_url: Option<String>,
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub documentation_url: Option<String>,
146}
147
148fn default_protocol_version() -> String {
149    "0.3.0".to_string()
150}
151
152#[derive(Debug, Clone, Default, Serialize, Deserialize)]
153#[serde(rename_all = "camelCase")]
154pub struct AgentCapabilities {
155    #[serde(default)]
156    pub streaming: bool,
157    #[serde(default)]
158    pub push_notifications: bool,
159    #[serde(default)]
160    pub state_transition_history: bool,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
164#[serde(rename_all = "camelCase")]
165pub struct AgentSkill {
166    pub id: String,
167    pub name: String,
168    pub description: String,
169    #[serde(default)]
170    pub tags: Vec<String>,
171    #[serde(default)]
172    pub examples: Vec<String>,
173    #[serde(default)]
174    pub input_modes: Vec<String>,
175    #[serde(default)]
176    pub output_modes: Vec<String>,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct AgentProvider {
181    pub organization: String,
182    pub url: String,
183}
184
185/// JSON-RPC 2.0 Request
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct JsonRpcRequest {
188    pub jsonrpc: String,
189    pub id: serde_json::Value,
190    pub method: String,
191    #[serde(default)]
192    pub params: serde_json::Value,
193}
194
195/// JSON-RPC 2.0 Response
196#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct JsonRpcResponse {
198    pub jsonrpc: String,
199    pub id: serde_json::Value,
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub result: Option<serde_json::Value>,
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub error: Option<JsonRpcError>,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct JsonRpcError {
208    pub code: i32,
209    pub message: String,
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub data: Option<serde_json::Value>,
212}
213
214#[allow(dead_code)]
215impl JsonRpcError {
216    /// Create a parse error (-32700)
217    pub fn parse_error(msg: impl Into<String>) -> Self {
218        Self {
219            code: PARSE_ERROR,
220            message: msg.into(),
221            data: None,
222        }
223    }
224
225    /// Create an invalid request error (-32600)
226    pub fn invalid_request(msg: impl Into<String>) -> Self {
227        Self {
228            code: INVALID_REQUEST,
229            message: msg.into(),
230            data: None,
231        }
232    }
233
234    /// Create a method not found error (-32601)
235    pub fn method_not_found(method: &str) -> Self {
236        Self {
237            code: METHOD_NOT_FOUND,
238            message: format!("Method not found: {}", method),
239            data: None,
240        }
241    }
242
243    /// Create an invalid params error (-32602)
244    pub fn invalid_params(msg: impl Into<String>) -> Self {
245        Self {
246            code: INVALID_PARAMS,
247            message: msg.into(),
248            data: None,
249        }
250    }
251
252    /// Create an internal error (-32603)
253    pub fn internal_error(msg: impl Into<String>) -> Self {
254        Self {
255            code: INTERNAL_ERROR,
256            message: msg.into(),
257            data: None,
258        }
259    }
260}
261
262// Standard JSON-RPC error codes
263#[allow(dead_code)]
264pub const PARSE_ERROR: i32 = -32700;
265#[allow(dead_code)]
266pub const INVALID_REQUEST: i32 = -32600;
267pub const METHOD_NOT_FOUND: i32 = -32601;
268pub const INVALID_PARAMS: i32 = -32602;
269pub const INTERNAL_ERROR: i32 = -32603;
270
271// A2A-specific error codes
272pub const TASK_NOT_FOUND: i32 = -32001;
273pub const TASK_NOT_CANCELABLE: i32 = -32002;
274#[allow(dead_code)]
275pub const PUSH_NOT_SUPPORTED: i32 = -32003;
276pub const UNSUPPORTED_OPERATION: i32 = -32004;
277#[allow(dead_code)]
278pub const CONTENT_TYPE_NOT_SUPPORTED: i32 = -32005;
279
280/// Message send parameters
281#[derive(Debug, Clone, Serialize, Deserialize)]
282#[serde(rename_all = "camelCase")]
283pub struct MessageSendParams {
284    pub message: Message,
285    #[serde(skip_serializing_if = "Option::is_none")]
286    pub configuration: Option<MessageSendConfiguration>,
287}
288
289#[derive(Debug, Clone, Serialize, Deserialize)]
290#[serde(rename_all = "camelCase")]
291pub struct MessageSendConfiguration {
292    #[serde(default)]
293    pub accepted_output_modes: Vec<String>,
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub blocking: Option<bool>,
296    #[serde(skip_serializing_if = "Option::is_none")]
297    pub history_length: Option<usize>,
298    #[serde(skip_serializing_if = "Option::is_none")]
299    pub push_notification_config: Option<PushNotificationConfig>,
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize)]
303#[serde(rename_all = "camelCase")]
304pub struct PushNotificationConfig {
305    pub url: String,
306    #[serde(skip_serializing_if = "Option::is_none")]
307    pub token: Option<String>,
308    #[serde(skip_serializing_if = "Option::is_none")]
309    pub id: Option<String>,
310}
311
312/// Task query parameters
313#[derive(Debug, Clone, Serialize, Deserialize)]
314#[serde(rename_all = "camelCase")]
315pub struct TaskQueryParams {
316    pub id: String,
317    #[serde(skip_serializing_if = "Option::is_none")]
318    pub history_length: Option<usize>,
319}