Skip to main content

lash_remote_protocol/
observations.rs

1//! Session observation: cursors, resumable observation events, and live
2//! replay gap envelopes.
3
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7use crate::registry_errors::{RemoteProtocolError, require_non_empty};
8use crate::usage_activity::{RemoteTurnActivity, RemoteUsage};
9use crate::{REMOTE_PROTOCOL_VERSION, ensure_protocol_version};
10
11#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
12pub struct RemoteSessionCursor {
13    pub protocol_version: u32,
14    pub cursor: String,
15}
16
17impl RemoteSessionCursor {
18    pub fn new(cursor: impl Into<String>) -> Self {
19        Self {
20            protocol_version: REMOTE_PROTOCOL_VERSION,
21            cursor: cursor.into(),
22        }
23    }
24
25    pub fn validate(&self) -> Result<(), RemoteProtocolError> {
26        ensure_protocol_version(self.protocol_version)?;
27        require_non_empty("RemoteSessionCursor", "cursor", &self.cursor)
28    }
29}
30
31#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
32pub struct RemoteSessionObservation {
33    pub protocol_version: u32,
34    pub session_id: String,
35    pub cursor: String,
36    pub turn_index: u64,
37    pub usage: RemoteUsage,
38}
39
40impl RemoteSessionObservation {
41    pub fn validate(&self) -> Result<(), RemoteProtocolError> {
42        ensure_protocol_version(self.protocol_version)?;
43        require_non_empty("RemoteSessionObservation", "session_id", &self.session_id)?;
44        require_non_empty("RemoteSessionObservation", "cursor", &self.cursor)
45    }
46}
47
48#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
49pub struct RemoteSessionObservationEvent {
50    pub protocol_version: u32,
51    pub session_id: String,
52    pub revision: u64,
53    pub cursor: String,
54    #[serde(flatten)]
55    pub event: RemoteSessionObservationEventPayload,
56}
57
58impl RemoteSessionObservationEvent {
59    pub fn validate(&self) -> Result<(), RemoteProtocolError> {
60        ensure_protocol_version(self.protocol_version)?;
61        require_non_empty(
62            "RemoteSessionObservationEvent",
63            "session_id",
64            &self.session_id,
65        )?;
66        require_non_empty("RemoteSessionObservationEvent", "cursor", &self.cursor)?;
67        if let RemoteSessionObservationEventPayload::TurnActivity { activity } = &self.event {
68            activity.validate()?;
69            if activity.protocol_version != self.protocol_version {
70                return Err(RemoteProtocolError::MismatchedNestedProtocolVersion {
71                    parent: "RemoteSessionObservationEvent",
72                    child: "activity",
73                    parent_version: self.protocol_version,
74                    child_version: activity.protocol_version,
75                });
76            }
77        }
78        Ok(())
79    }
80}
81
82#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
83#[serde(tag = "type", rename_all = "snake_case")]
84pub enum RemoteSessionObservationEventPayload {
85    TurnActivity {
86        activity: Box<RemoteTurnActivity>,
87    },
88    Committed,
89    AgentFrameSwitched {
90        frame_id: String,
91    },
92    QueueChanged {
93        kind: RemoteSessionQueueEventKind,
94        batch_ids: Vec<String>,
95    },
96    ProcessChanged {
97        kind: RemoteSessionProcessEventKind,
98        process_ids: Vec<String>,
99    },
100}
101
102#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
103#[serde(rename_all = "snake_case")]
104pub enum RemoteSessionQueueEventKind {
105    Enqueued,
106    Cancelled,
107}
108
109#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
110#[serde(rename_all = "snake_case")]
111pub enum RemoteSessionProcessEventKind {
112    Started,
113    Cancelled,
114}
115
116#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
117pub struct RemoteLiveReplayGap {
118    pub protocol_version: u32,
119    pub session_id: String,
120    pub requested_cursor: String,
121    pub latest_cursor: String,
122    pub latest_revision: u64,
123    pub reason: RemoteLiveReplayGapReason,
124}
125
126impl RemoteLiveReplayGap {
127    pub fn validate(&self) -> Result<(), RemoteProtocolError> {
128        ensure_protocol_version(self.protocol_version)?;
129        require_non_empty("RemoteLiveReplayGap", "session_id", &self.session_id)?;
130        require_non_empty(
131            "RemoteLiveReplayGap",
132            "requested_cursor",
133            &self.requested_cursor,
134        )?;
135        require_non_empty("RemoteLiveReplayGap", "latest_cursor", &self.latest_cursor)
136    }
137}
138
139#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
140#[serde(rename_all = "snake_case")]
141pub enum RemoteLiveReplayGapReason {
142    Trimmed,
143    Unavailable,
144}