Skip to main content

a2a_protocol_types/
push.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Tom F.
3
4//! Push notification configuration types.
5//!
6//! Push notifications allow an agent to deliver task updates to a client-owned
7//! HTTPS webhook endpoint rather than requiring the client to poll. A client
8//! registers a [`TaskPushNotificationConfig`] for a specific task via the
9//! `CreateTaskPushNotificationConfig` method.
10
11use serde::{Deserialize, Serialize};
12
13// ── AuthenticationInfo ──────────────────────────────────────────────────────
14
15/// Authentication information used by an agent when calling a push webhook.
16///
17/// In v1.0, this uses singular `scheme` (not `schemes`) and required
18/// `credentials`.
19#[derive(Debug, Clone, Serialize, Deserialize)]
20#[serde(rename_all = "camelCase")]
21pub struct AuthenticationInfo {
22    /// Authentication scheme (e.g. `"bearer"`).
23    pub scheme: String,
24
25    /// Credential value (e.g. a static token).
26    pub credentials: String,
27}
28
29// ── TaskPushNotificationConfig ──────────────────────────────────────────────
30
31/// Configuration for delivering task updates to a webhook endpoint.
32///
33/// In v1.0, this is a single flat type combining the previous
34/// `PushNotificationConfig` and `TaskPushNotificationConfig`.
35#[derive(Debug, Clone, Serialize, Deserialize)]
36#[serde(rename_all = "camelCase")]
37pub struct TaskPushNotificationConfig {
38    /// Optional tenant identifier for multi-tenancy.
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub tenant: Option<String>,
41
42    /// Server-assigned configuration identifier.
43    ///
44    /// Absent when first creating the config; populated in the server response.
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub id: Option<String>,
47
48    /// The task for which push notifications are configured.
49    pub task_id: String,
50
51    /// HTTPS URL of the client's webhook endpoint.
52    pub url: String,
53
54    /// Optional shared secret for request verification.
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub token: Option<String>,
57
58    /// Authentication details the agent should use when calling the webhook.
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub authentication: Option<AuthenticationInfo>,
61}
62
63impl TaskPushNotificationConfig {
64    /// Creates a minimal config with a task ID and URL.
65    #[must_use]
66    pub fn new(task_id: impl Into<String>, url: impl Into<String>) -> Self {
67        Self {
68            tenant: None,
69            id: None,
70            task_id: task_id.into(),
71            url: url.into(),
72            token: None,
73            authentication: None,
74        }
75    }
76}
77
78// ── Tests ─────────────────────────────────────────────────────────────────────
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn push_config_minimal_roundtrip() {
86        let cfg = TaskPushNotificationConfig::new("task-1", "https://example.com/webhook");
87        let json = serde_json::to_string(&cfg).expect("serialize");
88        assert!(json.contains("\"url\""));
89        assert!(json.contains("\"taskId\""));
90        assert!(!json.contains("\"id\""), "id should be omitted when None");
91
92        let back: TaskPushNotificationConfig = serde_json::from_str(&json).expect("deserialize");
93        assert_eq!(back.url, "https://example.com/webhook");
94        assert_eq!(back.task_id, "task-1");
95    }
96
97    #[test]
98    fn push_config_full_roundtrip() {
99        let cfg = TaskPushNotificationConfig {
100            tenant: Some("tenant-1".into()),
101            id: Some("cfg-1".into()),
102            task_id: "task-1".into(),
103            url: "https://example.com/webhook".into(),
104            token: Some("secret".into()),
105            authentication: Some(AuthenticationInfo {
106                scheme: "bearer".into(),
107                credentials: "my-token".into(),
108            }),
109        };
110        let json = serde_json::to_string(&cfg).expect("serialize");
111        let back: TaskPushNotificationConfig = serde_json::from_str(&json).expect("deserialize");
112        assert_eq!(back.task_id, "task-1");
113        assert_eq!(back.url, "https://example.com/webhook");
114        assert_eq!(back.authentication.unwrap().scheme, "bearer");
115    }
116}