Skip to main content

a2a_rust/types/
requests.rs

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/// Optional configuration for `SendMessage`.
11#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub struct SendMessageConfiguration {
14    #[serde(default, skip_serializing_if = "Vec::is_empty")]
15    /// Output modes the caller can accept.
16    pub accepted_output_modes: Vec<String>,
17    #[serde(default, skip_serializing_if = "Option::is_none")]
18    /// Optional task push configuration to attach to the request.
19    pub task_push_notification_config: Option<TaskPushNotificationConfig>,
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    /// Maximum history items requested in task responses.
22    pub history_length: Option<i32>,
23    #[serde(default, skip_serializing_if = "crate::types::is_false")]
24    /// Whether the server should return immediately instead of waiting.
25    pub return_immediately: bool,
26}
27
28/// Request payload for `SendMessage`.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub struct SendMessageRequest {
32    /// Input message from the caller.
33    pub message: Message,
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    /// Optional message handling configuration.
36    pub configuration: Option<SendMessageConfiguration>,
37    #[serde(default, skip_serializing_if = "Option::is_none")]
38    /// Optional request metadata.
39    pub metadata: Option<JsonObject>,
40    #[serde(default, skip_serializing_if = "Option::is_none")]
41    /// Optional tenant identifier.
42    pub tenant: Option<String>,
43}
44
45impl SendMessageRequest {
46    /// Validate nested message content.
47    pub fn validate(&self) -> Result<(), A2AError> {
48        self.message.validate()
49    }
50}
51
52/// Request payload for `GetTask`.
53#[derive(Debug, Clone, Serialize, Deserialize)]
54#[serde(rename_all = "camelCase")]
55pub struct GetTaskRequest {
56    /// Task identifier.
57    pub id: String,
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    /// Maximum history items requested in the response.
60    pub history_length: Option<i32>,
61    #[serde(default, skip_serializing_if = "Option::is_none")]
62    /// Optional tenant identifier.
63    pub tenant: Option<String>,
64}
65
66/// Request payload for `ListTasks`.
67#[derive(Debug, Clone, Default, Serialize, Deserialize)]
68#[serde(rename_all = "camelCase")]
69pub struct ListTasksRequest {
70    #[serde(default, skip_serializing_if = "Option::is_none")]
71    /// Optional tenant identifier.
72    pub tenant: Option<String>,
73    #[serde(default, skip_serializing_if = "Option::is_none")]
74    /// Optional context filter.
75    pub context_id: Option<String>,
76    #[serde(default, skip_serializing_if = "Option::is_none")]
77    /// Optional task-state filter.
78    pub status: Option<TaskState>,
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    /// Requested page size.
81    pub page_size: Option<i32>,
82    #[serde(default, skip_serializing_if = "Option::is_none")]
83    /// Opaque pagination token from a previous response.
84    pub page_token: Option<String>,
85    #[serde(default, skip_serializing_if = "Option::is_none")]
86    /// Maximum history items requested per returned task.
87    pub history_length: Option<i32>,
88    #[serde(default, skip_serializing_if = "Option::is_none")]
89    /// Lower bound for task status timestamps.
90    pub status_timestamp_after: Option<String>,
91    #[serde(default, skip_serializing_if = "Option::is_none")]
92    /// Whether artifacts should be included in results.
93    pub include_artifacts: Option<bool>,
94}
95
96impl ListTasksRequest {
97    /// Validate pagination bounds.
98    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/// Request payload for `CancelTask`.
112#[derive(Debug, Clone, Serialize, Deserialize)]
113#[serde(rename_all = "camelCase")]
114pub struct CancelTaskRequest {
115    /// Task identifier.
116    pub id: String,
117    #[serde(default, skip_serializing_if = "Option::is_none")]
118    /// Optional tenant identifier.
119    pub tenant: Option<String>,
120    #[serde(default, skip_serializing_if = "Option::is_none")]
121    /// Optional request metadata.
122    pub metadata: Option<JsonObject>,
123}
124
125/// Request payload for `GetTaskPushNotificationConfig`.
126#[derive(Debug, Clone, Serialize, Deserialize)]
127#[serde(rename_all = "camelCase")]
128pub struct GetTaskPushNotificationConfigRequest {
129    /// Push configuration identifier.
130    pub id: String,
131    /// Owning task identifier.
132    pub task_id: String,
133    #[serde(default, skip_serializing_if = "Option::is_none")]
134    /// Optional tenant identifier.
135    pub tenant: Option<String>,
136}
137
138/// Request payload for `DeleteTaskPushNotificationConfig`.
139#[derive(Debug, Clone, Serialize, Deserialize)]
140#[serde(rename_all = "camelCase")]
141pub struct DeleteTaskPushNotificationConfigRequest {
142    /// Push configuration identifier.
143    pub id: String,
144    /// Owning task identifier.
145    pub task_id: String,
146    #[serde(default, skip_serializing_if = "Option::is_none")]
147    /// Optional tenant identifier.
148    pub tenant: Option<String>,
149}
150
151/// Request payload for `SubscribeToTask`.
152#[derive(Debug, Clone, Serialize, Deserialize)]
153#[serde(rename_all = "camelCase")]
154pub struct SubscribeToTaskRequest {
155    /// Task identifier.
156    pub id: String,
157    #[serde(default, skip_serializing_if = "Option::is_none")]
158    /// Optional tenant identifier.
159    pub tenant: Option<String>,
160}
161
162/// Request payload for `ListTaskPushNotificationConfigs`.
163#[derive(Debug, Clone, Serialize, Deserialize)]
164#[serde(rename_all = "camelCase")]
165pub struct ListTaskPushNotificationConfigsRequest {
166    /// Owning task identifier.
167    pub task_id: String,
168    #[serde(default, skip_serializing_if = "Option::is_none")]
169    /// Requested page size.
170    pub page_size: Option<i32>,
171    #[serde(default, skip_serializing_if = "Option::is_none")]
172    /// Opaque pagination token from a previous response.
173    pub page_token: Option<String>,
174    #[serde(default, skip_serializing_if = "Option::is_none")]
175    /// Optional tenant identifier.
176    pub tenant: Option<String>,
177}
178
179impl ListTaskPushNotificationConfigsRequest {
180    /// Validate required identifiers.
181    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/// Request payload for `GetExtendedAgentCard`.
293#[derive(Debug, Clone, Default, Serialize, Deserialize)]
294#[serde(rename_all = "camelCase")]
295pub struct GetExtendedAgentCardRequest {
296    #[serde(default, skip_serializing_if = "Option::is_none")]
297    /// Optional tenant identifier.
298    pub tenant: Option<String>,
299}