1use serde::{Deserialize, Serialize};
4
5pub const A2A_PROTOCOL_NAME: &str = "oris.a2a";
6pub const A2A_PROTOCOL_VERSION: &str = "0.1.0-experimental";
7pub const A2A_PROTOCOL_VERSION_V1: &str = "1.0.0";
8pub const A2A_SUPPORTED_PROTOCOL_VERSIONS: [&str; 2] =
9 [A2A_PROTOCOL_VERSION_V1, A2A_PROTOCOL_VERSION];
10
11#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
12pub struct A2aProtocol {
13 pub name: String,
14 pub version: String,
15}
16
17impl A2aProtocol {
18 pub fn current() -> Self {
19 Self {
20 name: A2A_PROTOCOL_NAME.to_string(),
21 version: A2A_PROTOCOL_VERSION.to_string(),
22 }
23 }
24}
25
26#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
27pub enum A2aCapability {
28 Coordination,
29 MutationProposal,
30 ReplayFeedback,
31 SupervisedDevloop,
32 EvolutionPublish,
33 EvolutionFetch,
34 EvolutionRevoke,
35}
36
37#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
38pub struct A2aHandshakeRequest {
39 pub agent_id: String,
40 pub role: AgentRole,
41 pub capability_level: AgentCapabilityLevel,
42 pub supported_protocols: Vec<A2aProtocol>,
43 pub advertised_capabilities: Vec<A2aCapability>,
44}
45
46impl A2aHandshakeRequest {
47 pub fn supports_protocol_version(&self, version: &str) -> bool {
48 self.supported_protocols
49 .iter()
50 .any(|protocol| protocol.name == A2A_PROTOCOL_NAME && protocol.version == version)
51 }
52
53 pub fn supports_current_protocol(&self) -> bool {
54 self.supports_protocol_version(A2A_PROTOCOL_VERSION)
55 }
56
57 pub fn negotiate_supported_protocol(&self) -> Option<A2aProtocol> {
58 for version in A2A_SUPPORTED_PROTOCOL_VERSIONS {
59 if self.supports_protocol_version(version) {
60 return Some(A2aProtocol {
61 name: A2A_PROTOCOL_NAME.to_string(),
62 version: version.to_string(),
63 });
64 }
65 }
66 None
67 }
68}
69
70#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
71pub struct A2aHandshakeResponse {
72 pub accepted: bool,
73 pub negotiated_protocol: Option<A2aProtocol>,
74 pub enabled_capabilities: Vec<A2aCapability>,
75 pub message: Option<String>,
76 pub error: Option<A2aErrorEnvelope>,
77}
78
79impl A2aHandshakeResponse {
80 pub fn accept(enabled_capabilities: Vec<A2aCapability>) -> Self {
81 Self {
82 accepted: true,
83 negotiated_protocol: Some(A2aProtocol::current()),
84 enabled_capabilities,
85 message: Some("handshake accepted".to_string()),
86 error: None,
87 }
88 }
89
90 pub fn reject(code: A2aErrorCode, message: impl Into<String>, details: Option<String>) -> Self {
91 Self {
92 accepted: false,
93 negotiated_protocol: None,
94 enabled_capabilities: Vec::new(),
95 message: Some("handshake rejected".to_string()),
96 error: Some(A2aErrorEnvelope {
97 code,
98 message: message.into(),
99 retriable: true,
100 details,
101 }),
102 }
103 }
104}
105
106#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
107pub enum A2aTaskLifecycleState {
108 Queued,
109 Running,
110 Succeeded,
111 Failed,
112 Cancelled,
113}
114
115#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
116pub struct A2aTaskLifecycleEvent {
117 pub task_id: String,
118 pub state: A2aTaskLifecycleState,
119 pub summary: String,
120 pub updated_at_ms: u64,
121 pub error: Option<A2aErrorEnvelope>,
122}
123
124pub const A2A_TASK_SESSION_PROTOCOL_VERSION: &str = A2A_PROTOCOL_VERSION;
125
126#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
127pub enum A2aTaskSessionState {
128 Started,
129 Dispatched,
130 InProgress,
131 Completed,
132 Failed,
133 Cancelled,
134}
135
136#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
137pub struct A2aTaskSessionStartRequest {
138 pub sender_id: String,
139 pub protocol_version: String,
140 pub task_id: String,
141 pub task_summary: String,
142}
143
144#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
145pub struct A2aTaskSessionDispatchRequest {
146 pub sender_id: String,
147 pub protocol_version: String,
148 pub dispatch_id: String,
149 pub summary: String,
150}
151
152#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
153pub struct A2aTaskSessionProgressRequest {
154 pub sender_id: String,
155 pub protocol_version: String,
156 pub progress_pct: u8,
157 pub summary: String,
158 pub retryable: bool,
159 pub retry_after_ms: Option<u64>,
160}
161
162#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
163pub struct A2aTaskSessionCompletionRequest {
164 pub sender_id: String,
165 pub protocol_version: String,
166 pub terminal_state: A2aTaskLifecycleState,
167 pub summary: String,
168 pub retryable: bool,
169 pub retry_after_ms: Option<u64>,
170 pub failure_code: Option<A2aErrorCode>,
171 pub failure_details: Option<String>,
172 pub used_capsule: bool,
173 pub capsule_id: Option<String>,
174 pub reasoning_steps_avoided: u64,
175 pub fallback_reason: Option<String>,
176 pub task_class_id: String,
177 pub task_label: String,
178}
179
180#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
181pub struct A2aTaskSessionProgressItem {
182 pub progress_pct: u8,
183 pub summary: String,
184 pub retryable: bool,
185 pub retry_after_ms: Option<u64>,
186 pub updated_at_ms: u64,
187}
188
189#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
190pub struct A2aTaskSessionAck {
191 pub session_id: String,
192 pub task_id: String,
193 pub state: A2aTaskSessionState,
194 pub summary: String,
195 pub retryable: bool,
196 pub retry_after_ms: Option<u64>,
197 pub updated_at_ms: u64,
198}
199
200#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
201pub struct A2aTaskSessionResult {
202 pub terminal_state: A2aTaskLifecycleState,
203 pub summary: String,
204 pub retryable: bool,
205 pub retry_after_ms: Option<u64>,
206 pub failure_code: Option<A2aErrorCode>,
207 pub failure_details: Option<String>,
208 pub replay_feedback: ReplayFeedback,
209}
210
211#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
212pub struct A2aTaskSessionCompletionResponse {
213 pub ack: A2aTaskSessionAck,
214 pub result: A2aTaskSessionResult,
215}
216
217#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
218pub struct A2aTaskSessionSnapshot {
219 pub session_id: String,
220 pub sender_id: String,
221 pub task_id: String,
222 pub protocol_version: String,
223 pub state: A2aTaskSessionState,
224 pub created_at_ms: u64,
225 pub updated_at_ms: u64,
226 pub dispatch_ids: Vec<String>,
227 pub progress: Vec<A2aTaskSessionProgressItem>,
228 pub result: Option<A2aTaskSessionResult>,
229}
230
231#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
232pub enum A2aErrorCode {
233 UnsupportedProtocol,
234 UnsupportedCapability,
235 ValidationFailed,
236 AuthorizationDenied,
237 Timeout,
238 Internal,
239}
240
241#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
242pub struct A2aErrorEnvelope {
243 pub code: A2aErrorCode,
244 pub message: String,
245 pub retriable: bool,
246 pub details: Option<String>,
247}
248
249#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
250pub enum AgentCapabilityLevel {
251 A0,
252 A1,
253 A2,
254 A3,
255 A4,
256}
257
258#[derive(Clone, Debug, Serialize, Deserialize)]
259pub enum ProposalTarget {
260 WorkspaceRoot,
261 Paths(Vec<String>),
262}
263
264#[derive(Clone, Debug, Serialize, Deserialize)]
265pub struct AgentTask {
266 pub id: String,
267 pub description: String,
268}
269
270#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
271pub enum AgentRole {
272 Planner,
273 Coder,
274 Repair,
275 Optimizer,
276}
277
278#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
279pub enum CoordinationPrimitive {
280 Sequential,
281 Parallel,
282 Conditional,
283}
284
285#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
286pub struct CoordinationTask {
287 pub id: String,
288 pub role: AgentRole,
289 pub description: String,
290 pub depends_on: Vec<String>,
291}
292
293#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
294pub struct CoordinationMessage {
295 pub from_role: AgentRole,
296 pub to_role: AgentRole,
297 pub task_id: String,
298 pub content: String,
299}
300
301#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
302pub struct CoordinationPlan {
303 pub root_goal: String,
304 pub primitive: CoordinationPrimitive,
305 pub tasks: Vec<CoordinationTask>,
306 pub timeout_ms: u64,
307 pub max_retries: u32,
308}
309
310#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
311pub struct CoordinationResult {
312 pub completed_tasks: Vec<String>,
313 pub failed_tasks: Vec<String>,
314 pub messages: Vec<CoordinationMessage>,
315 pub summary: String,
316}
317
318#[derive(Clone, Debug, Serialize, Deserialize)]
319pub struct MutationProposal {
320 pub intent: String,
321 pub files: Vec<String>,
322 pub expected_effect: String,
323}
324
325#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
326pub struct ExecutionFeedback {
327 pub accepted: bool,
328 pub asset_state: Option<String>,
329 pub summary: String,
330}
331
332#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
333pub enum ReplayPlannerDirective {
334 SkipPlanner,
335 PlanFallback,
336}
337
338#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
339pub struct ReplayFeedback {
340 pub used_capsule: bool,
341 pub capsule_id: Option<String>,
342 pub planner_directive: ReplayPlannerDirective,
343 pub reasoning_steps_avoided: u64,
344 pub fallback_reason: Option<String>,
345 pub task_class_id: String,
346 pub task_label: String,
347 pub summary: String,
348}
349
350#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
351pub enum BoundedTaskClass {
352 DocsSingleFile,
353}
354
355#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
356pub struct HumanApproval {
357 pub approved: bool,
358 pub approver: Option<String>,
359 pub note: Option<String>,
360}
361
362#[derive(Clone, Debug, Serialize, Deserialize)]
363pub struct SupervisedDevloopRequest {
364 pub task: AgentTask,
365 pub proposal: MutationProposal,
366 pub approval: HumanApproval,
367}
368
369#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
370pub enum SupervisedDevloopStatus {
371 AwaitingApproval,
372 RejectedByPolicy,
373 Executed,
374}
375
376#[derive(Clone, Debug, Serialize, Deserialize)]
377pub struct SupervisedDevloopOutcome {
378 pub task_id: String,
379 pub task_class: Option<BoundedTaskClass>,
380 pub status: SupervisedDevloopStatus,
381 pub execution_feedback: Option<ExecutionFeedback>,
382 pub summary: String,
383}
384
385#[cfg(test)]
386mod tests {
387 use super::*;
388
389 fn handshake_request_with_versions(versions: &[&str]) -> A2aHandshakeRequest {
390 A2aHandshakeRequest {
391 agent_id: "agent-test".into(),
392 role: AgentRole::Planner,
393 capability_level: AgentCapabilityLevel::A2,
394 supported_protocols: versions
395 .iter()
396 .map(|version| A2aProtocol {
397 name: A2A_PROTOCOL_NAME.into(),
398 version: (*version).into(),
399 })
400 .collect(),
401 advertised_capabilities: vec![A2aCapability::Coordination],
402 }
403 }
404
405 #[test]
406 fn negotiate_supported_protocol_prefers_v1_when_available() {
407 let req = handshake_request_with_versions(&[A2A_PROTOCOL_VERSION, A2A_PROTOCOL_VERSION_V1]);
408 let negotiated = req
409 .negotiate_supported_protocol()
410 .expect("expected protocol negotiation success");
411 assert_eq!(negotiated.name, A2A_PROTOCOL_NAME);
412 assert_eq!(negotiated.version, A2A_PROTOCOL_VERSION_V1);
413 }
414
415 #[test]
416 fn negotiate_supported_protocol_falls_back_to_experimental() {
417 let req = handshake_request_with_versions(&[A2A_PROTOCOL_VERSION]);
418 let negotiated = req
419 .negotiate_supported_protocol()
420 .expect("expected protocol negotiation success");
421 assert_eq!(negotiated.version, A2A_PROTOCOL_VERSION);
422 }
423
424 #[test]
425 fn negotiate_supported_protocol_returns_none_without_overlap() {
426 let req = handshake_request_with_versions(&["0.0.1"]);
427 assert!(req.negotiate_supported_protocol().is_none());
428 }
429}