Skip to main content

a2a_protocol_types/
responses.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Tom F.
3
4//! RPC method response types.
5//!
6//! These types appear as the `result` field of a
7//! [`crate::jsonrpc::JsonRpcSuccessResponse`].
8//!
9//! | Method | Response type |
10//! |---|---|
11//! | `SendMessage` | [`SendMessageResponse`] |
12//! | `ListTasks` | [`TaskListResponse`] |
13//! | `GetExtendedAgentCard` | [`AgentCard`] (re-exported as [`AuthenticatedExtendedCardResponse`]) |
14
15use serde::{Deserialize, Serialize};
16
17use crate::agent_card::AgentCard;
18use crate::message::Message;
19use crate::task::Task;
20
21// ── SendMessageResponse ───────────────────────────────────────────────────────
22
23/// The result of a `SendMessage` call: either a completed [`Task`] or an
24/// immediate [`Message`] response.
25///
26/// Discriminated by field presence (untagged oneof): `{"task": {...}}` or
27/// `{"message": {...}}`.
28#[non_exhaustive]
29#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub enum SendMessageResponse {
32    /// The agent accepted the message and created (or updated) a task.
33    Task(Task),
34
35    /// The agent responded immediately with a message (no task created).
36    Message(Message),
37}
38
39// ── TaskListResponse ──────────────────────────────────────────────────────────
40
41/// The result of a `ListTasks` call: a page of tasks with pagination.
42#[derive(Debug, Clone, Serialize, Deserialize)]
43#[serde(rename_all = "camelCase")]
44pub struct TaskListResponse {
45    /// The tasks in this page of results.
46    pub tasks: Vec<Task>,
47
48    /// Pagination token for the next page; absent on the last page.
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub next_page_token: Option<String>,
51
52    /// The requested page size.
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub page_size: Option<u32>,
55
56    /// Total number of tasks matching the query (across all pages).
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub total_size: Option<u32>,
59}
60
61impl TaskListResponse {
62    /// Creates a single-page response with no next-page token.
63    #[must_use]
64    pub const fn new(tasks: Vec<Task>) -> Self {
65        Self {
66            tasks,
67            next_page_token: None,
68            page_size: None,
69            total_size: None,
70        }
71    }
72}
73
74// ── ListPushConfigsResponse ────────────────────────────────────────────────────
75
76/// The result of a `ListTaskPushNotificationConfigs` call.
77#[derive(Debug, Clone, Serialize, Deserialize)]
78#[serde(rename_all = "camelCase")]
79pub struct ListPushConfigsResponse {
80    /// The push notification configs in this page of results.
81    pub configs: Vec<crate::push::TaskPushNotificationConfig>,
82
83    /// Pagination token for the next page; absent on the last page.
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub next_page_token: Option<String>,
86}
87
88// ── AuthenticatedExtendedCardResponse ─────────────────────────────────────────
89
90/// The full (private) agent card returned by `agent/authenticatedExtendedCard`.
91///
92/// This is structurally identical to the public [`AgentCard`]; the type alias
93/// signals intent and may gain additional fields in a future spec revision.
94pub type AuthenticatedExtendedCardResponse = AgentCard;
95
96// ── Tests ─────────────────────────────────────────────────────────────────────
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use crate::message::{MessageId, MessageRole, Part};
102    use crate::task::{ContextId, TaskId, TaskState, TaskStatus};
103
104    fn make_task() -> Task {
105        Task {
106            id: TaskId::new("t1"),
107            context_id: ContextId::new("c1"),
108            status: TaskStatus::new(TaskState::Completed),
109            history: None,
110            artifacts: None,
111            metadata: None,
112        }
113    }
114
115    fn make_message() -> Message {
116        Message {
117            id: MessageId::new("m1"),
118            role: MessageRole::Agent,
119            parts: vec![Part::text("hi")],
120            task_id: None,
121            context_id: None,
122            reference_task_ids: None,
123            extensions: None,
124            metadata: None,
125        }
126    }
127
128    #[test]
129    fn send_message_response_task_variant() {
130        let resp = SendMessageResponse::Task(make_task());
131        let json = serde_json::to_string(&resp).expect("serialize");
132        assert!(
133            !json.contains("\"kind\""),
134            "v1.0 should not have kind: {json}"
135        );
136
137        let back: SendMessageResponse = serde_json::from_str(&json).expect("deserialize");
138        assert!(matches!(back, SendMessageResponse::Task(_)));
139    }
140
141    #[test]
142    fn send_message_response_message_variant() {
143        let resp = SendMessageResponse::Message(make_message());
144        let json = serde_json::to_string(&resp).expect("serialize");
145        assert!(
146            !json.contains("\"kind\""),
147            "v1.0 should not have kind: {json}"
148        );
149
150        let back: SendMessageResponse = serde_json::from_str(&json).expect("deserialize");
151        assert!(matches!(back, SendMessageResponse::Message(_)));
152    }
153
154    #[test]
155    fn task_list_response_roundtrip() {
156        let resp = TaskListResponse {
157            tasks: vec![make_task()],
158            next_page_token: Some("cursor-abc".into()),
159            page_size: Some(10),
160            total_size: Some(1),
161        };
162        let json = serde_json::to_string(&resp).expect("serialize");
163        assert!(json.contains("\"nextPageToken\":\"cursor-abc\""));
164
165        let back: TaskListResponse = serde_json::from_str(&json).expect("deserialize");
166        assert_eq!(back.tasks.len(), 1);
167        assert_eq!(back.next_page_token.as_deref(), Some("cursor-abc"));
168    }
169
170    #[test]
171    fn task_list_response_no_token_omitted() {
172        let resp = TaskListResponse::new(vec![]);
173        let json = serde_json::to_string(&resp).expect("serialize");
174        assert!(
175            !json.contains("\"nextPageToken\""),
176            "token should be absent: {json}"
177        );
178    }
179}