Skip to main content

systemprompt_models/events/
a2a_event.rs

1//! A2A protocol events emitted over the event bus and SSE streams.
2//!
3//! [`A2AEvent`] is the timestamped, tagged union of every agent-to-agent
4//! lifecycle event (task submission, status updates, artifacts, agent
5//! messages, input/auth prompts, JSON-RPC responses). [`A2AEventBuilder`]
6//! stamps each variant with the current time at construction.
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11use chrono::{DateTime, Utc};
12use serde::{Deserialize, Serialize};
13use systemprompt_identifiers::{ContextId, MessageId, TaskId};
14
15use super::a2a_event_type::A2AEventType;
16use super::payloads::a2a::{
17    AgentMessagePayload, ArtifactCreatedPayload, ArtifactUpdatedPayload, AuthRequiredPayload,
18    InputRequiredPayload, JsonRpcErrorPayload, JsonRpcResponsePayload, TaskStatusUpdatePayload,
19    TaskSubmittedPayload,
20};
21use crate::a2a::{Artifact, TaskState};
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
24#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")]
25pub enum A2AEvent {
26    TaskSubmitted {
27        timestamp: DateTime<Utc>,
28        #[serde(flatten)]
29        payload: TaskSubmittedPayload,
30    },
31    TaskStatusUpdate {
32        timestamp: DateTime<Utc>,
33        #[serde(flatten)]
34        payload: TaskStatusUpdatePayload,
35    },
36    ArtifactCreated {
37        timestamp: DateTime<Utc>,
38        #[serde(flatten)]
39        payload: Box<ArtifactCreatedPayload>,
40    },
41    ArtifactUpdated {
42        timestamp: DateTime<Utc>,
43        #[serde(flatten)]
44        payload: ArtifactUpdatedPayload,
45    },
46    AgentMessage {
47        timestamp: DateTime<Utc>,
48        #[serde(flatten)]
49        payload: AgentMessagePayload,
50    },
51    InputRequired {
52        timestamp: DateTime<Utc>,
53        #[serde(flatten)]
54        payload: InputRequiredPayload,
55    },
56    AuthRequired {
57        timestamp: DateTime<Utc>,
58        #[serde(flatten)]
59        payload: AuthRequiredPayload,
60    },
61    JsonRpcResponse {
62        timestamp: DateTime<Utc>,
63        #[serde(flatten)]
64        payload: JsonRpcResponsePayload,
65    },
66    JsonRpcError {
67        timestamp: DateTime<Utc>,
68        #[serde(flatten)]
69        payload: JsonRpcErrorPayload,
70    },
71}
72
73impl A2AEvent {
74    pub const fn event_type(&self) -> A2AEventType {
75        match self {
76            Self::TaskSubmitted { .. } => A2AEventType::TaskSubmitted,
77            Self::TaskStatusUpdate { .. } => A2AEventType::TaskStatusUpdate,
78            Self::ArtifactCreated { .. } => A2AEventType::ArtifactCreated,
79            Self::ArtifactUpdated { .. } => A2AEventType::ArtifactUpdated,
80            Self::AgentMessage { .. } => A2AEventType::AgentMessage,
81            Self::InputRequired { .. } => A2AEventType::InputRequired,
82            Self::AuthRequired { .. } => A2AEventType::AuthRequired,
83            Self::JsonRpcResponse { .. } => A2AEventType::JsonRpcResponse,
84            Self::JsonRpcError { .. } => A2AEventType::JsonRpcError,
85        }
86    }
87
88    pub const fn timestamp(&self) -> DateTime<Utc> {
89        match self {
90            Self::TaskSubmitted { timestamp, .. }
91            | Self::TaskStatusUpdate { timestamp, .. }
92            | Self::ArtifactCreated { timestamp, .. }
93            | Self::ArtifactUpdated { timestamp, .. }
94            | Self::AgentMessage { timestamp, .. }
95            | Self::InputRequired { timestamp, .. }
96            | Self::AuthRequired { timestamp, .. }
97            | Self::JsonRpcResponse { timestamp, .. }
98            | Self::JsonRpcError { timestamp, .. } => *timestamp,
99        }
100    }
101}
102
103#[derive(Debug, Clone, Copy)]
104pub struct A2AEventBuilder;
105
106impl A2AEventBuilder {
107    pub fn task_submitted(
108        task_id: TaskId,
109        context_id: ContextId,
110        agent_name: String,
111        // JSON: A2A `Message` input as sent by the client.
112        input: Option<serde_json::Value>,
113    ) -> A2AEvent {
114        A2AEvent::TaskSubmitted {
115            timestamp: Utc::now(),
116            payload: TaskSubmittedPayload {
117                task_id,
118                context_id,
119                agent_name,
120                input,
121            },
122        }
123    }
124
125    pub fn task_status_update(
126        task_id: TaskId,
127        context_id: ContextId,
128        state: TaskState,
129        message: Option<String>,
130    ) -> A2AEvent {
131        A2AEvent::TaskStatusUpdate {
132            timestamp: Utc::now(),
133            payload: TaskStatusUpdatePayload {
134                task_id,
135                context_id,
136                state,
137                message,
138            },
139        }
140    }
141
142    pub fn artifact_created(
143        task_id: TaskId,
144        context_id: ContextId,
145        artifact: Artifact,
146    ) -> A2AEvent {
147        A2AEvent::ArtifactCreated {
148            timestamp: Utc::now(),
149            payload: Box::new(ArtifactCreatedPayload {
150                task_id,
151                context_id,
152                artifact,
153            }),
154        }
155    }
156
157    pub fn agent_message(
158        task_id: TaskId,
159        context_id: ContextId,
160        message_id: MessageId,
161        content: String,
162    ) -> A2AEvent {
163        A2AEvent::AgentMessage {
164            timestamp: Utc::now(),
165            payload: AgentMessagePayload {
166                task_id,
167                context_id,
168                message_id,
169                content,
170            },
171        }
172    }
173
174    pub fn input_required(task_id: TaskId, context_id: ContextId, prompt: String) -> A2AEvent {
175        A2AEvent::InputRequired {
176            timestamp: Utc::now(),
177            payload: InputRequiredPayload {
178                task_id,
179                context_id,
180                prompt,
181            },
182        }
183    }
184
185    pub fn auth_required(task_id: TaskId, context_id: ContextId, auth_url: String) -> A2AEvent {
186        A2AEvent::AuthRequired {
187            timestamp: Utc::now(),
188            payload: AuthRequiredPayload {
189                task_id,
190                context_id,
191                auth_url,
192            },
193        }
194    }
195
196    // JSON: A2A JSON-RPC 2.0 envelope (`id` may be a string or a number).
197    pub fn json_rpc_response(id: serde_json::Value, result: serde_json::Value) -> A2AEvent {
198        A2AEvent::JsonRpcResponse {
199            timestamp: Utc::now(),
200            payload: JsonRpcResponsePayload { id, result },
201        }
202    }
203}