Skip to main content

a2a_protocol_types/
params.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Tom F.
3
4//! JSON-RPC method parameter types.
5//!
6//! Each A2A v1.0 method has a corresponding `Params` struct that maps to the
7//! `params` field of a [`crate::jsonrpc::JsonRpcRequest`].
8//!
9//! | Method | Params type |
10//! |---|---|
11//! | `SendMessage` | [`MessageSendParams`] |
12//! | `SendStreamingMessage` | [`MessageSendParams`] |
13//! | `GetTask` | [`TaskQueryParams`] |
14//! | `CancelTask` | [`CancelTaskParams`] |
15//! | `ListTasks` | [`ListTasksParams`] |
16//! | `SubscribeToTask` | [`TaskIdParams`] |
17//! | `CreateTaskPushNotificationConfig` | [`crate::push::TaskPushNotificationConfig`] |
18//! | `GetTaskPushNotificationConfig` | [`GetPushConfigParams`] |
19//! | `DeleteTaskPushNotificationConfig` | [`DeletePushConfigParams`] |
20
21use serde::{Deserialize, Serialize};
22
23use crate::message::Message;
24use crate::push::TaskPushNotificationConfig;
25use crate::task::TaskState;
26
27// ── SendMessageConfiguration ──────────────────────────────────────────────────
28
29/// Optional configuration for a `SendMessage` or `SendStreamingMessage` call.
30#[derive(Debug, Clone, Serialize, Deserialize)]
31#[serde(rename_all = "camelCase")]
32pub struct SendMessageConfiguration {
33    /// MIME types the client can accept as output (e.g. `["text/plain"]`).
34    pub accepted_output_modes: Vec<String>,
35
36    /// Push notification config to register alongside this message send.
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub task_push_notification_config: Option<TaskPushNotificationConfig>,
39
40    /// Number of historical messages to include in the response.
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub history_length: Option<u32>,
43
44    /// If `true`, return immediately with the task object rather than waiting
45    /// for completion.
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub return_immediately: Option<bool>,
48}
49
50impl Default for SendMessageConfiguration {
51    fn default() -> Self {
52        Self {
53            accepted_output_modes: vec!["text/plain".to_owned()],
54            task_push_notification_config: None,
55            history_length: None,
56            return_immediately: None,
57        }
58    }
59}
60
61// ── MessageSendParams ─────────────────────────────────────────────────────────
62
63/// Parameters for the `SendMessage` and `SendStreamingMessage` methods.
64#[derive(Debug, Clone, Serialize, Deserialize)]
65#[serde(rename_all = "camelCase")]
66pub struct MessageSendParams {
67    /// Optional tenant for multi-tenancy.
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub tenant: Option<String>,
70
71    /// The message to send to the agent.
72    pub message: Message,
73
74    /// Optional send configuration.
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub configuration: Option<SendMessageConfiguration>,
77
78    /// Arbitrary caller metadata attached to the request.
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub metadata: Option<serde_json::Value>,
81}
82
83// ── TaskQueryParams ───────────────────────────────────────────────────────────
84
85/// Parameters for the `GetTask` method.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87#[serde(rename_all = "camelCase")]
88pub struct TaskQueryParams {
89    /// Optional tenant for multi-tenancy.
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub tenant: Option<String>,
92
93    /// ID of the task to retrieve.
94    pub id: String,
95
96    /// Number of historical messages to include in the response.
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub history_length: Option<u32>,
99}
100
101// ── TaskIdParams ──────────────────────────────────────────────────────────────
102
103/// Minimal parameters identifying a single task by ID.
104///
105/// Used for `SubscribeToTask`.
106#[derive(Debug, Clone, Serialize, Deserialize)]
107#[serde(rename_all = "camelCase")]
108pub struct TaskIdParams {
109    /// Optional tenant for multi-tenancy.
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub tenant: Option<String>,
112
113    /// ID of the target task.
114    pub id: String,
115}
116
117// ── CancelTaskParams ────────────────────────────────────────────────────────
118
119/// Parameters for the `CancelTask` method.
120#[derive(Debug, Clone, Serialize, Deserialize)]
121#[serde(rename_all = "camelCase")]
122pub struct CancelTaskParams {
123    /// Optional tenant for multi-tenancy.
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub tenant: Option<String>,
126
127    /// ID of the task to cancel.
128    pub id: String,
129
130    /// Arbitrary metadata.
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub metadata: Option<serde_json::Value>,
133}
134
135// ── ListTasksParams ───────────────────────────────────────────────────────────
136
137/// Parameters for the `ListTasks` method.
138///
139/// All fields are optional filters; omitting them returns all tasks visible to
140/// the caller (subject to the server's default page size).
141#[derive(Debug, Clone, Serialize, Deserialize)]
142#[serde(rename_all = "camelCase")]
143pub struct ListTasksParams {
144    /// Optional tenant for multi-tenancy.
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub tenant: Option<String>,
147
148    /// Filter to tasks belonging to this conversation context.
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub context_id: Option<String>,
151
152    /// Filter to tasks in this state.
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub status: Option<TaskState>,
155
156    /// Maximum number of tasks to return per page (1–100, default 50).
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub page_size: Option<u32>,
159
160    /// Pagination cursor returned by the previous response.
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub page_token: Option<String>,
163
164    /// Return only tasks whose status changed after this ISO 8601 timestamp.
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub status_timestamp_after: Option<String>,
167
168    /// If `true`, include artifact data in the returned tasks.
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub include_artifacts: Option<bool>,
171
172    /// Number of historical messages to include per task.
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub history_length: Option<u32>,
175}
176
177// ── GetPushConfigParams ───────────────────────────────────────────────────────
178
179/// Parameters for the `GetTaskPushNotificationConfig` method.
180#[derive(Debug, Clone, Serialize, Deserialize)]
181#[serde(rename_all = "camelCase")]
182pub struct GetPushConfigParams {
183    /// Optional tenant for multi-tenancy.
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub tenant: Option<String>,
186
187    /// The task whose push config to retrieve.
188    pub task_id: String,
189
190    /// The server-assigned push config identifier.
191    pub id: String,
192}
193
194// ── DeletePushConfigParams ────────────────────────────────────────────────────
195
196/// Parameters for the `DeleteTaskPushNotificationConfig` method.
197#[derive(Debug, Clone, Serialize, Deserialize)]
198#[serde(rename_all = "camelCase")]
199pub struct DeletePushConfigParams {
200    /// Optional tenant for multi-tenancy.
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub tenant: Option<String>,
203
204    /// The task whose push config to delete.
205    pub task_id: String,
206
207    /// The server-assigned push config identifier.
208    pub id: String,
209}
210
211// ── ListPushConfigsParams ────────────────────────────────────────────────────
212
213/// Parameters for the `ListTaskPushNotificationConfigs` method.
214#[derive(Debug, Clone, Serialize, Deserialize)]
215#[serde(rename_all = "camelCase")]
216pub struct ListPushConfigsParams {
217    /// Optional tenant for multi-tenancy.
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub tenant: Option<String>,
220
221    /// The task whose push configs to list.
222    pub task_id: String,
223
224    /// Maximum number of configs to return per page.
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub page_size: Option<u32>,
227
228    /// Pagination cursor returned by the previous response.
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub page_token: Option<String>,
231}
232
233// ── GetExtendedAgentCardParams ──────────────────────────────────────────────
234
235/// Parameters for the `GetExtendedAgentCard` method.
236#[derive(Debug, Clone, Serialize, Deserialize)]
237#[serde(rename_all = "camelCase")]
238pub struct GetExtendedAgentCardParams {
239    /// Optional tenant for multi-tenancy.
240    #[serde(skip_serializing_if = "Option::is_none")]
241    pub tenant: Option<String>,
242}
243
244// ── Tests ─────────────────────────────────────────────────────────────────────
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249    use crate::message::{MessageId, MessageRole, Part};
250
251    fn make_message() -> Message {
252        Message {
253            id: MessageId::new("msg-1"),
254            role: MessageRole::User,
255            parts: vec![Part::text("hello")],
256            task_id: None,
257            context_id: None,
258            reference_task_ids: None,
259            extensions: None,
260            metadata: None,
261        }
262    }
263
264    #[test]
265    fn message_send_params_roundtrip() {
266        let params = MessageSendParams {
267            tenant: None,
268            message: make_message(),
269            configuration: Some(SendMessageConfiguration {
270                accepted_output_modes: vec!["text/plain".into()],
271                task_push_notification_config: None,
272                history_length: Some(10),
273                return_immediately: None,
274            }),
275            metadata: None,
276        };
277        let json = serde_json::to_string(&params).expect("serialize");
278        let back: MessageSendParams = serde_json::from_str(&json).expect("deserialize");
279        assert_eq!(back.message.id, MessageId::new("msg-1"));
280    }
281
282    #[test]
283    fn list_tasks_params_empty_roundtrip() {
284        let params = ListTasksParams {
285            tenant: None,
286            context_id: None,
287            status: None,
288            page_size: None,
289            page_token: None,
290            status_timestamp_after: None,
291            include_artifacts: None,
292            history_length: None,
293        };
294        let json = serde_json::to_string(&params).expect("serialize");
295        // All optional fields should be absent
296        assert_eq!(json, "{}", "empty params should serialize to {{}}");
297    }
298
299    #[test]
300    fn task_query_params_roundtrip() {
301        let params = TaskQueryParams {
302            tenant: None,
303            id: "task-1".into(),
304            history_length: Some(5),
305        };
306        let json = serde_json::to_string(&params).expect("serialize");
307        let back: TaskQueryParams = serde_json::from_str(&json).expect("deserialize");
308        assert_eq!(back.id, "task-1");
309        assert_eq!(back.history_length, Some(5));
310    }
311
312    #[test]
313    fn cancel_task_params_roundtrip() {
314        let params = CancelTaskParams {
315            tenant: Some("my-tenant".into()),
316            id: "task-1".into(),
317            metadata: Some(serde_json::json!({"reason": "no longer needed"})),
318        };
319        let json = serde_json::to_string(&params).expect("serialize");
320        let back: CancelTaskParams = serde_json::from_str(&json).expect("deserialize");
321        assert_eq!(back.id, "task-1");
322        assert_eq!(back.tenant.as_deref(), Some("my-tenant"));
323        assert!(back.metadata.is_some());
324    }
325
326    #[test]
327    fn wire_format_list_tasks_history_length() {
328        let params = ListTasksParams {
329            tenant: None,
330            context_id: None,
331            status: None,
332            page_size: None,
333            page_token: None,
334            status_timestamp_after: None,
335            include_artifacts: None,
336            history_length: Some(10),
337        };
338        let json = serde_json::to_string(&params).unwrap();
339        assert!(
340            json.contains("\"historyLength\":10"),
341            "historyLength must appear: {json}"
342        );
343
344        let back: ListTasksParams = serde_json::from_str(&json).unwrap();
345        assert_eq!(back.history_length, Some(10));
346    }
347
348    #[test]
349    fn wire_format_list_push_configs_params() {
350        let params = super::ListPushConfigsParams {
351            tenant: None,
352            task_id: "t1".into(),
353            page_size: Some(20),
354            page_token: None,
355        };
356        let json = serde_json::to_string(&params).unwrap();
357        assert!(json.contains("\"taskId\":\"t1\""));
358        assert!(json.contains("\"pageSize\":20"));
359    }
360}