Skip to main content

systemprompt_models/agui/
payloads.rs

1//! Event payload types for the AG-UI streaming protocol.
2//!
3//! Each struct here is the body of one AG-UI event — run lifecycle
4//! (`RunStarted`/`RunFinished`/`RunError`), step boundaries, streaming text and
5//! tool-call deltas, state snapshots/deltas, and the [`CustomPayload`] envelope
6//! for application-defined events (artifacts, execution steps, loaded skills).
7//! Field casing follows the wire format via `rename_all = "camelCase"`.
8//!
9//! Copyright (c) systemprompt.io — Business Source License 1.1.
10//! See <https://systemprompt.io> for licensing details.
11
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14use systemprompt_identifiers::{AiToolCallId, ContextId, MessageId, TaskId};
15
16use super::JsonPatchOperation;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(rename_all = "lowercase")]
20pub enum MessageRole {
21    User,
22    Assistant,
23    System,
24    Tool,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28#[serde(rename_all = "camelCase")]
29pub struct RunStartedPayload {
30    pub thread_id: ContextId,
31    pub run_id: TaskId,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    // JSON: AG-UI event payload; the protocol defines this field as free-form.
34    pub input: Option<Value>,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
38#[serde(rename_all = "camelCase")]
39pub struct RunFinishedPayload {
40    pub thread_id: ContextId,
41    pub run_id: TaskId,
42    #[serde(skip_serializing_if = "Option::is_none")]
43    // JSON: AG-UI event payload; the protocol defines this field as free-form.
44    pub result: Option<Value>,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub struct RunErrorPayload {
50    pub message: String,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub code: Option<String>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56#[serde(rename_all = "camelCase")]
57pub struct StepStartedPayload {
58    pub step_name: String,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62#[serde(rename_all = "camelCase")]
63pub struct StepFinishedPayload {
64    pub step_name: String,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
68#[serde(rename_all = "camelCase")]
69pub struct TextMessageStartPayload {
70    pub message_id: MessageId,
71    pub role: MessageRole,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
75#[serde(rename_all = "camelCase")]
76pub struct TextMessageContentPayload {
77    pub message_id: MessageId,
78    pub delta: String,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub struct TextMessageEndPayload {
84    pub message_id: MessageId,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
88#[serde(rename_all = "camelCase")]
89pub struct ToolCallStartPayload {
90    pub tool_call_id: AiToolCallId,
91    pub tool_call_name: String,
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub parent_message_id: Option<MessageId>,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
97#[serde(rename_all = "camelCase")]
98pub struct ToolCallArgsPayload {
99    pub tool_call_id: AiToolCallId,
100    pub delta: String,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
104#[serde(rename_all = "camelCase")]
105pub struct ToolCallEndPayload {
106    pub tool_call_id: AiToolCallId,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
110#[serde(rename_all = "camelCase")]
111pub struct ToolCallResultPayload {
112    pub message_id: MessageId,
113    pub tool_call_id: AiToolCallId,
114    // JSON: AG-UI event payload; the protocol defines this field as free-form.
115    pub content: Value,
116    pub role: MessageRole,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
120#[serde(rename_all = "camelCase")]
121pub struct StateSnapshotPayload {
122    // JSON: AG-UI event payload; the protocol defines this field as free-form.
123    pub snapshot: Value,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
127#[serde(rename_all = "camelCase")]
128pub struct StateDeltaPayload {
129    pub delta: Vec<JsonPatchOperation>,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
133#[serde(rename_all = "camelCase")]
134pub struct MessagesSnapshotPayload {
135    // JSON: AG-UI event payload; the protocol defines this field as free-form.
136    pub messages: Vec<Value>,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
140#[serde(rename_all = "camelCase")]
141pub struct ArtifactCustomPayload {
142    pub artifact: crate::a2a::Artifact,
143    pub task_id: TaskId,
144    pub context_id: ContextId,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
148#[serde(rename_all = "camelCase")]
149pub struct ExecutionStepCustomPayload {
150    pub step: crate::execution::ExecutionStep,
151    pub context_id: ContextId,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
155#[serde(rename_all = "camelCase")]
156pub struct SkillLoadedCustomPayload {
157    pub skill_id: systemprompt_identifiers::SkillId,
158    pub skill_name: String,
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub description: Option<String>,
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub task_id: Option<TaskId>,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
166#[serde(rename_all = "camelCase")]
167pub struct GenericCustomPayload {
168    pub name: String,
169    // JSON: AG-UI event payload; the protocol defines this field as free-form.
170    pub value: Value,
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize)]
174#[serde(tag = "name", content = "value", rename_all = "snake_case")]
175pub enum CustomPayload {
176    Artifact(Box<ArtifactCustomPayload>),
177    ExecutionStep(Box<ExecutionStepCustomPayload>),
178    SkillLoaded(SkillLoadedCustomPayload),
179    #[serde(untagged)]
180    Generic(GenericCustomPayload),
181}