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