a2a_protocol_types/
events.rs1use serde::{Deserialize, Serialize};
20
21use crate::artifact::Artifact;
22use crate::message::Message;
23use crate::task::{ContextId, Task, TaskId, TaskStatus};
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
32#[serde(rename_all = "camelCase")]
33pub struct TaskStatusUpdateEvent {
34 pub task_id: TaskId,
36
37 pub context_id: ContextId,
39
40 pub status: TaskStatus,
42
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub metadata: Option<serde_json::Value>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
55#[serde(rename_all = "camelCase")]
56pub struct TaskArtifactUpdateEvent {
57 pub task_id: TaskId,
59
60 pub context_id: ContextId,
62
63 pub artifact: Artifact,
65
66 #[serde(skip_serializing_if = "Option::is_none")]
69 pub append: Option<bool>,
70
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub last_chunk: Option<bool>,
74
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub metadata: Option<serde_json::Value>,
78}
79
80#[non_exhaustive]
87#[derive(Debug, Clone, Serialize, Deserialize)]
88#[serde(rename_all = "camelCase")]
89pub enum StreamResponse {
90 Task(Task),
92
93 Message(Message),
95
96 StatusUpdate(TaskStatusUpdateEvent),
98
99 ArtifactUpdate(TaskArtifactUpdateEvent),
101}
102
103#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::artifact::ArtifactId;
109 use crate::message::Part;
110 use crate::task::{ContextId, TaskState};
111
112 #[test]
113 fn status_update_event_roundtrip() {
114 let event = TaskStatusUpdateEvent {
115 task_id: TaskId::new("task-1"),
116 context_id: ContextId::new("ctx-1"),
117 status: TaskStatus::new(TaskState::Completed),
118 metadata: None,
119 };
120 let json = serde_json::to_string(&event).expect("serialize");
121 assert!(!json.contains("\"final\""), "v1.0 removed final field");
122 assert!(json.contains("\"status\""), "should have status field");
123
124 let back: TaskStatusUpdateEvent = serde_json::from_str(&json).expect("deserialize");
125 assert_eq!(back.status.state, TaskState::Completed);
126 }
127
128 #[test]
129 fn artifact_update_event_roundtrip() {
130 let event = TaskArtifactUpdateEvent {
131 task_id: TaskId::new("task-1"),
132 context_id: ContextId::new("ctx-1"),
133 artifact: Artifact::new(ArtifactId::new("art-1"), vec![Part::text("output")]),
134 append: Some(false),
135 last_chunk: Some(true),
136 metadata: None,
137 };
138 let json = serde_json::to_string(&event).expect("serialize");
139 let back: TaskArtifactUpdateEvent = serde_json::from_str(&json).expect("deserialize");
140 assert_eq!(back.last_chunk, Some(true));
141 }
142
143 #[test]
144 fn stream_response_task_variant() {
145 let task = Task {
146 id: TaskId::new("t1"),
147 context_id: ContextId::new("c1"),
148 status: TaskStatus::new(TaskState::Working),
149 history: None,
150 artifacts: None,
151 metadata: None,
152 };
153 let resp = StreamResponse::Task(task);
154 let json = serde_json::to_string(&resp).expect("serialize");
155 assert!(
156 !json.contains("\"kind\""),
157 "v1.0 should not have kind tag: {json}"
158 );
159
160 let back: StreamResponse = serde_json::from_str(&json).expect("deserialize");
161 assert!(matches!(back, StreamResponse::Task(_)));
162 }
163
164 #[test]
165 fn stream_response_status_update_variant() {
166 let event = TaskStatusUpdateEvent {
167 task_id: TaskId::new("t1"),
168 context_id: ContextId::new("c1"),
169 status: TaskStatus::new(TaskState::Failed),
170 metadata: None,
171 };
172 let resp = StreamResponse::StatusUpdate(event);
173 let json = serde_json::to_string(&resp).expect("serialize");
174 assert!(
175 !json.contains("\"kind\""),
176 "v1.0 should not have kind tag: {json}"
177 );
178
179 let back: StreamResponse = serde_json::from_str(&json).expect("deserialize");
180 assert!(matches!(back, StreamResponse::StatusUpdate(_)));
181 }
182
183 #[test]
184 fn stream_response_message_variant_roundtrip() {
185 use crate::message::{MessageId, MessageRole, Part};
186 use crate::task::TaskId;
187
188 let msg = crate::message::Message {
189 id: MessageId::new("msg-stream-1"),
190 role: MessageRole::Agent,
191 parts: vec![Part::text("streaming response")],
192 task_id: Some(TaskId::new("t1")),
193 context_id: Some(ContextId::new("c1")),
194 reference_task_ids: None,
195 extensions: None,
196 metadata: None,
197 };
198 let resp = StreamResponse::Message(msg);
199 let json = serde_json::to_string(&resp).expect("serialize");
200 assert!(
201 !json.contains("\"kind\""),
202 "v1.0 should not have kind tag: {json}"
203 );
204 assert!(json.contains("\"messageId\":\"msg-stream-1\""));
205 assert!(json.contains("\"role\":\"ROLE_AGENT\""));
206
207 let back: StreamResponse = serde_json::from_str(&json).expect("deserialize");
208 match back {
209 StreamResponse::Message(m) => {
210 assert_eq!(m.id, MessageId::new("msg-stream-1"));
211 assert_eq!(m.role, MessageRole::Agent);
212 assert_eq!(m.parts.len(), 1);
213 }
214 other => panic!("expected Message variant, got {other:?}"),
215 }
216 }
217}