a2a_protocol_types/
responses.rs1use serde::{Deserialize, Serialize};
18
19use crate::agent_card::AgentCard;
20use crate::message::Message;
21use crate::task::Task;
22
23#[non_exhaustive]
30#[derive(Debug, Clone, Serialize, Deserialize)]
31#[serde(rename_all = "camelCase")]
32pub enum SendMessageResponse {
33 Task(Task),
35
36 Message(Message),
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub struct TaskListResponse {
50 pub tasks: Vec<Task>,
52
53 #[serde(default)]
55 pub next_page_token: String,
56
57 #[serde(default)]
59 pub page_size: u32,
60
61 #[serde(default)]
63 pub total_size: u32,
64}
65
66impl TaskListResponse {
67 #[must_use]
69 #[allow(clippy::missing_const_for_fn)] pub fn new(tasks: Vec<Task>) -> Self {
71 #[allow(clippy::cast_possible_truncation)]
72 let total = tasks.len() as u32;
73 Self {
74 page_size: total,
75 total_size: total,
76 tasks,
77 next_page_token: String::new(),
78 }
79 }
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
86#[serde(rename_all = "camelCase")]
87pub struct ListPushConfigsResponse {
88 pub configs: Vec<crate::push::TaskPushNotificationConfig>,
90
91 #[serde(skip_serializing_if = "Option::is_none")]
93 pub next_page_token: Option<String>,
94}
95
96pub type AuthenticatedExtendedCardResponse = AgentCard;
103
104#[cfg(test)]
107mod tests {
108 use super::*;
109 use crate::message::{MessageId, MessageRole, Part};
110 use crate::task::{ContextId, TaskId, TaskState, TaskStatus};
111
112 fn make_task() -> Task {
113 Task {
114 id: TaskId::new("t1"),
115 context_id: ContextId::new("c1"),
116 status: TaskStatus::new(TaskState::Completed),
117 history: None,
118 artifacts: None,
119 metadata: None,
120 }
121 }
122
123 fn make_message() -> Message {
124 Message {
125 id: MessageId::new("m1"),
126 role: MessageRole::Agent,
127 parts: vec![Part::text("hi")],
128 task_id: None,
129 context_id: None,
130 reference_task_ids: None,
131 extensions: None,
132 metadata: None,
133 }
134 }
135
136 #[test]
137 fn send_message_response_task_variant() {
138 let resp = SendMessageResponse::Task(make_task());
139 let json = serde_json::to_string(&resp).expect("serialize");
140 assert!(
142 json.contains("\"task\""),
143 "v1.0 should have 'task' wrapper key: {json}"
144 );
145
146 let back: SendMessageResponse = serde_json::from_str(&json).expect("deserialize");
147 match &back {
148 SendMessageResponse::Task(t) => {
149 assert_eq!(t.id, TaskId::new("t1"));
150 assert_eq!(t.status.state, TaskState::Completed);
151 }
152 _ => panic!("expected Task variant"),
153 }
154 }
155
156 #[test]
157 fn send_message_response_message_variant() {
158 let resp = SendMessageResponse::Message(make_message());
159 let json = serde_json::to_string(&resp).expect("serialize");
160 assert!(
162 json.contains("\"message\""),
163 "v1.0 should have 'message' wrapper key: {json}"
164 );
165
166 let back: SendMessageResponse = serde_json::from_str(&json).expect("deserialize");
167 match &back {
168 SendMessageResponse::Message(m) => {
169 assert_eq!(m.id, MessageId::new("m1"));
170 assert_eq!(m.role, MessageRole::Agent);
171 }
172 _ => panic!("expected Message variant"),
173 }
174 }
175
176 #[test]
178 fn send_message_response_deserialize_task() {
179 let json = serde_json::json!({
180 "task": {
181 "id": "t1",
182 "contextId": "c1",
183 "status": {"state": "TASK_STATE_COMPLETED"}
184 }
185 });
186 let back: SendMessageResponse =
187 serde_json::from_value(json).expect("should deserialize as Task");
188 match back {
189 SendMessageResponse::Task(task) => {
190 assert_eq!(task.id.as_ref(), "t1");
191 assert_eq!(task.context_id.as_ref(), "c1");
192 }
193 other => panic!("expected Task variant, got {other:?}"),
194 }
195 }
196
197 #[test]
199 fn send_message_response_deserialize_message() {
200 let json = serde_json::json!({
201 "message": {
202 "messageId": "m1",
203 "role": "ROLE_AGENT",
204 "parts": [{ "text": "hi" }]
205 }
206 });
207 let resp: SendMessageResponse =
208 serde_json::from_value(json).expect("should deserialize as Message");
209 assert!(
210 matches!(resp, SendMessageResponse::Message(_)),
211 "expected Message variant"
212 );
213 }
214
215 #[test]
216 fn task_list_response_roundtrip() {
217 let resp = TaskListResponse {
218 tasks: vec![make_task()],
219 next_page_token: "cursor-abc".into(),
220 page_size: 10,
221 total_size: 1,
222 };
223 let json = serde_json::to_string(&resp).expect("serialize");
224 assert!(json.contains("\"nextPageToken\":\"cursor-abc\""));
225
226 let back: TaskListResponse = serde_json::from_str(&json).expect("deserialize");
227 assert_eq!(back.tasks.len(), 1);
228 assert_eq!(back.next_page_token, "cursor-abc");
229 }
230
231 #[test]
232 fn task_list_response_empty_always_includes_required_fields() {
233 let resp = TaskListResponse::new(vec![]);
234 let json = serde_json::to_string(&resp).expect("serialize");
235 assert!(
237 json.contains("\"nextPageToken\""),
238 "nextPageToken must always be present: {json}"
239 );
240 assert!(
241 json.contains("\"pageSize\""),
242 "pageSize must always be present: {json}"
243 );
244 assert!(
245 json.contains("\"totalSize\""),
246 "totalSize must always be present: {json}"
247 );
248 }
249}