1use serde::{Deserialize, Serialize};
2
3use crate::A2AError;
4use crate::types::JsonObject;
5
6use super::message::Message;
7use super::push::TaskPushNotificationConfig;
8use super::task::TaskState;
9
10#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub struct SendMessageConfiguration {
14 #[serde(default, skip_serializing_if = "Vec::is_empty")]
15 pub accepted_output_modes: Vec<String>,
17 #[serde(default, skip_serializing_if = "Option::is_none")]
18 pub task_push_notification_config: Option<TaskPushNotificationConfig>,
20 #[serde(default, skip_serializing_if = "Option::is_none")]
21 pub history_length: Option<i32>,
23 #[serde(default, skip_serializing_if = "crate::types::is_false")]
24 pub return_immediately: bool,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub struct SendMessageRequest {
32 pub message: Message,
34 #[serde(default, skip_serializing_if = "Option::is_none")]
35 pub configuration: Option<SendMessageConfiguration>,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
38 pub metadata: Option<JsonObject>,
40 #[serde(default, skip_serializing_if = "Option::is_none")]
41 pub tenant: Option<String>,
43}
44
45impl SendMessageRequest {
46 pub fn validate(&self) -> Result<(), A2AError> {
48 self.message.validate()
49 }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54#[serde(rename_all = "camelCase")]
55pub struct GetTaskRequest {
56 pub id: String,
58 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub history_length: Option<i32>,
61 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub tenant: Option<String>,
64}
65
66#[derive(Debug, Clone, Default, Serialize, Deserialize)]
68#[serde(rename_all = "camelCase")]
69pub struct ListTasksRequest {
70 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub tenant: Option<String>,
73 #[serde(default, skip_serializing_if = "Option::is_none")]
74 pub context_id: Option<String>,
76 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub status: Option<TaskState>,
79 #[serde(default, skip_serializing_if = "Option::is_none")]
80 pub page_size: Option<i32>,
82 #[serde(default, skip_serializing_if = "Option::is_none")]
83 pub page_token: Option<String>,
85 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub history_length: Option<i32>,
88 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub status_timestamp_after: Option<String>,
91 #[serde(default, skip_serializing_if = "Option::is_none")]
92 pub include_artifacts: Option<bool>,
94}
95
96impl ListTasksRequest {
97 pub fn validate(&self) -> Result<(), A2AError> {
99 if let Some(page_size) = self.page_size
100 && !(1..=100).contains(&page_size)
101 {
102 return Err(A2AError::InvalidRequest(
103 "pageSize must be between 1 and 100".to_owned(),
104 ));
105 }
106
107 Ok(())
108 }
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113#[serde(rename_all = "camelCase")]
114pub struct CancelTaskRequest {
115 pub id: String,
117 #[serde(default, skip_serializing_if = "Option::is_none")]
118 pub tenant: Option<String>,
120 #[serde(default, skip_serializing_if = "Option::is_none")]
121 pub metadata: Option<JsonObject>,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127#[serde(rename_all = "camelCase")]
128pub struct GetTaskPushNotificationConfigRequest {
129 pub id: String,
131 pub task_id: String,
133 #[serde(default, skip_serializing_if = "Option::is_none")]
134 pub tenant: Option<String>,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140#[serde(rename_all = "camelCase")]
141pub struct DeleteTaskPushNotificationConfigRequest {
142 pub id: String,
144 pub task_id: String,
146 #[serde(default, skip_serializing_if = "Option::is_none")]
147 pub tenant: Option<String>,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
153#[serde(rename_all = "camelCase")]
154pub struct SubscribeToTaskRequest {
155 pub id: String,
157 #[serde(default, skip_serializing_if = "Option::is_none")]
158 pub tenant: Option<String>,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
164#[serde(rename_all = "camelCase")]
165pub struct ListTaskPushNotificationConfigsRequest {
166 pub task_id: String,
168 #[serde(default, skip_serializing_if = "Option::is_none")]
169 pub page_size: Option<i32>,
171 #[serde(default, skip_serializing_if = "Option::is_none")]
172 pub page_token: Option<String>,
174 #[serde(default, skip_serializing_if = "Option::is_none")]
175 pub tenant: Option<String>,
177}
178
179impl ListTaskPushNotificationConfigsRequest {
180 pub fn validate(&self) -> Result<(), A2AError> {
182 if self.task_id.is_empty() {
183 return Err(A2AError::InvalidRequest(
184 "task_id must not be empty".to_owned(),
185 ));
186 }
187
188 Ok(())
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::{ListTaskPushNotificationConfigsRequest, ListTasksRequest, SendMessageRequest};
195 use crate::types::{Message, Part, Role};
196
197 #[test]
198 fn list_task_push_notification_configs_request_rejects_empty_task_id() {
199 let request = ListTaskPushNotificationConfigsRequest {
200 task_id: String::new(),
201 page_size: None,
202 page_token: None,
203 tenant: None,
204 };
205
206 let error = request.validate().expect_err("request should be invalid");
207 assert!(error.to_string().contains("task_id must not be empty"));
208 }
209
210 #[test]
211 fn list_tasks_request_rejects_out_of_range_page_size() {
212 let request = ListTasksRequest {
213 tenant: None,
214 context_id: None,
215 status: None,
216 page_size: Some(101),
217 page_token: None,
218 history_length: None,
219 status_timestamp_after: None,
220 include_artifacts: None,
221 };
222
223 let error = request.validate().expect_err("request should be invalid");
224 assert!(
225 error
226 .to_string()
227 .contains("pageSize must be between 1 and 100")
228 );
229 }
230
231 #[test]
232 fn send_message_request_rejects_empty_message_parts() {
233 let request = SendMessageRequest {
234 message: Message {
235 message_id: "msg-1".to_owned(),
236 context_id: None,
237 task_id: None,
238 role: Role::User,
239 parts: Vec::new(),
240 metadata: None,
241 extensions: Vec::new(),
242 reference_task_ids: Vec::new(),
243 },
244 configuration: None,
245 metadata: None,
246 tenant: None,
247 };
248
249 let error = request.validate().expect_err("request should be invalid");
250 assert!(
251 error
252 .to_string()
253 .contains("message must contain at least one part")
254 );
255 }
256
257 #[test]
258 fn send_message_request_validates_part_content() {
259 let request = SendMessageRequest {
260 message: Message {
261 message_id: "msg-1".to_owned(),
262 context_id: None,
263 task_id: None,
264 role: Role::User,
265 parts: vec![Part {
266 text: Some("hello".to_owned()),
267 raw: Some(vec![104, 105]),
268 url: None,
269 data: None,
270 metadata: None,
271 filename: None,
272 media_type: None,
273 }],
274 metadata: None,
275 extensions: Vec::new(),
276 reference_task_ids: Vec::new(),
277 },
278 configuration: None,
279 metadata: None,
280 tenant: None,
281 };
282
283 let error = request.validate().expect_err("request should be invalid");
284 assert!(
285 error
286 .to_string()
287 .contains("part cannot contain more than one")
288 );
289 }
290}
291
292#[derive(Debug, Clone, Default, Serialize, Deserialize)]
294#[serde(rename_all = "camelCase")]
295pub struct GetExtendedAgentCardRequest {
296 #[serde(default, skip_serializing_if = "Option::is_none")]
297 pub tenant: Option<String>,
299}