Skip to main content

ag_ui/event/
state.rs

1//! Shared state and message-history snapshots: `STATE_SNAPSHOT`,
2//! `STATE_DELTA`, `MESSAGES_SNAPSHOT`.
3
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7use crate::event::BaseEvent;
8use crate::ids::SubagentRunId;
9use crate::message::Message;
10use crate::patch::PatchOperation;
11
12/// Replaces the shared state wholesale.
13#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
16#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
17pub struct StateSnapshotEvent {
18    /// Timestamp and raw provider event.
19    #[serde(flatten)]
20    pub base: BaseEvent,
21    /// The complete new state. Free-form JSON, opaque to the protocol.
22    pub snapshot: Value,
23    /// The subagent that produced this update; absent means the parent
24    /// agent. A JSON `null` is rejected — see [`crate::event::subagent`].
25    /// Provenance, not ownership: state is run-scoped, and an attributed
26    /// snapshot still replaces the run's one state document.
27    #[serde(
28        default,
29        deserialize_with = "crate::serde_util::reject_null",
30        skip_serializing_if = "Option::is_none"
31    )]
32    pub subagent_run_id: Option<SubagentRunId>,
33}
34
35impl StateSnapshotEvent {
36    /// Publishes a new state.
37    pub fn new(snapshot: impl Into<Value>) -> Self {
38        Self {
39            base: BaseEvent::default(),
40            snapshot: snapshot.into(),
41            subagent_run_id: None,
42        }
43    }
44}
45
46/// Mutates the shared state with a JSON Patch document.
47#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
48#[serde(rename_all = "camelCase")]
49#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
50#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
51pub struct StateDeltaEvent {
52    /// Timestamp and raw provider event.
53    #[serde(flatten)]
54    pub base: BaseEvent,
55    /// RFC 6902 operations, applied in order to the previous state.
56    pub delta: Vec<PatchOperation>,
57    /// The subagent that produced this update; absent means the parent
58    /// agent. A JSON `null` is rejected — see [`crate::event::subagent`].
59    /// Provenance, not ownership, as on [`StateSnapshotEvent`].
60    #[serde(
61        default,
62        deserialize_with = "crate::serde_util::reject_null",
63        skip_serializing_if = "Option::is_none"
64    )]
65    pub subagent_run_id: Option<SubagentRunId>,
66}
67
68impl StateDeltaEvent {
69    /// Publishes a patch against the current state.
70    pub fn new(delta: impl Into<Vec<PatchOperation>>) -> Self {
71        Self {
72            base: BaseEvent::default(),
73            delta: delta.into(),
74            subagent_run_id: None,
75        }
76    }
77}
78
79/// Replaces the message history wholesale.
80///
81/// Used to reconcile after a reconnect, or when an agent rewrites history (for
82/// example after summarizing older turns).
83///
84/// Carries no `subagentRunId` of its own: one snapshot mixes messages from
85/// several producers, so attribution travels per message instead.
86#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
87#[serde(rename_all = "camelCase")]
88#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
89#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
90pub struct MessagesSnapshotEvent {
91    /// Timestamp and raw provider event.
92    #[serde(flatten)]
93    pub base: BaseEvent,
94    /// The complete message list, oldest first.
95    pub messages: Vec<Message>,
96}
97
98impl MessagesSnapshotEvent {
99    /// Publishes a new message history.
100    pub fn new(messages: impl Into<Vec<Message>>) -> Self {
101        Self {
102            base: BaseEvent::default(),
103            messages: messages.into(),
104        }
105    }
106}