Skip to main content

scv_protocol/
server.rs

1//! [`ServerEvent`]: everything the server sends.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use crate::{
7    DaemonStatus, ErrorCode, JobChange, PeerInfo, QueueEntry, ToolErrorKind, TurnOrigin, Usage,
8};
9
10/// A message from server to client. Serialized as one JSON object per line,
11/// tagged by `type` (such as `turn.completed`). Turn events carry the
12/// session's consecutive `seq`.
13///
14/// A `type` this client does not know parses as [`ServerEvent::Unknown`], so
15/// a newer server can add events without breaking older clients; a client
16/// ignores them.
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
18#[serde(tag = "type")]
19pub enum ServerEvent {
20    /// The answer to a `daemon.control` status request.
21    #[serde(rename = "daemon.status")]
22    DaemonStatus {
23        /// The client request this answers or belongs to.
24        request_id: String,
25        /// The daemon's state.
26        status: DaemonStatus,
27    },
28    /// The answer to `initialize`.
29    #[serde(rename = "initialized")]
30    Initialized {
31        /// The client request this answers or belongs to.
32        request_id: String,
33        /// The [`PROTOCOL_VERSION`](crate::PROTOCOL_VERSION) the server speaks.
34        protocol_version: u32,
35        /// The server's name and version.
36        server: PeerInfo,
37    },
38    /// The answer to `session.start`, with the limits the client should use.
39    #[serde(rename = "session.started")]
40    SessionStarted {
41        /// The client request this answers or belongs to.
42        request_id: String,
43        /// The session this concerns.
44        session_id: String,
45        /// The session's workspace directory.
46        cwd: String,
47        /// The model answering.
48        model: String,
49        /// The model's context window.
50        context_max_tokens: usize,
51        /// Largest event frame the server sends; clients size their reads by it.
52        max_server_frame_bytes: usize,
53        /// Transcript bytes a client should keep for display.
54        max_transcript_bytes: usize,
55        /// Transcript items a client should keep for display.
56        max_transcript_items: usize,
57        /// Prompt-history bytes a client should keep.
58        max_prompt_history_bytes: usize,
59        /// Prompt-history entries a client should keep.
60        max_prompt_history_items: usize,
61    },
62    /// The whole queue, sent when a session starts or on request.
63    #[serde(rename = "queue.snapshot")]
64    QueueSnapshot {
65        /// The client request this answers or belongs to.
66        request_id: Option<String>,
67        /// The session this concerns.
68        session_id: String,
69        /// The session's event sequence number: consecutive, so a gap means events were lost.
70        seq: u64,
71        /// Queued prompts in the order they will run.
72        entries: Vec<QueueEntry>,
73        /// Whether queued prompts wait instead of starting.
74        paused: bool,
75    },
76    /// A prompt was queued behind the running turn.
77    #[serde(rename = "queue.enqueued")]
78    QueueEnqueued {
79        /// The client request this answers or belongs to.
80        request_id: String,
81        /// The session this concerns.
82        session_id: String,
83        /// The session's event sequence number: consecutive, so a gap means events were lost.
84        seq: u64,
85        /// The queued prompt.
86        entry: QueueEntry,
87        /// Its index in the queue.
88        position: usize,
89    },
90    /// A queued prompt's text changed.
91    #[serde(rename = "queue.updated")]
92    QueueUpdated {
93        /// The client request this answers or belongs to.
94        request_id: String,
95        /// The session this concerns.
96        session_id: String,
97        /// The session's event sequence number: consecutive, so a gap means events were lost.
98        seq: u64,
99        /// The queued prompt.
100        entry: QueueEntry,
101    },
102    /// A queued prompt moved.
103    #[serde(rename = "queue.moved")]
104    QueueMoved {
105        /// The client request this answers or belongs to.
106        request_id: String,
107        /// The session this concerns.
108        session_id: String,
109        /// The session's event sequence number: consecutive, so a gap means events were lost.
110        seq: u64,
111        /// The queued prompt.
112        queue_id: String,
113        /// Its index in the queue.
114        position: usize,
115        /// The entry's revision after the change.
116        revision: u64,
117    },
118    /// A queued prompt was dropped.
119    #[serde(rename = "queue.removed")]
120    QueueRemoved {
121        /// The client request this answers or belongs to.
122        request_id: String,
123        /// The session this concerns.
124        session_id: String,
125        /// The session's event sequence number: consecutive, so a gap means events were lost.
126        seq: u64,
127        /// The queued prompt.
128        queue_id: String,
129        /// The entry's revision after the change.
130        revision: u64,
131    },
132    /// A queued prompt left the queue to run as a turn.
133    #[serde(rename = "queue.dequeued")]
134    QueueDequeued {
135        /// The client request this answers or belongs to.
136        request_id: String,
137        /// The session this concerns.
138        session_id: String,
139        /// The session's event sequence number: consecutive, so a gap means events were lost.
140        seq: u64,
141        /// The queued prompt.
142        queue_id: String,
143        /// The turn this belongs to.
144        turn_id: String,
145    },
146    /// The queue was held or released.
147    #[serde(rename = "session.paused")]
148    SessionPaused {
149        /// The client request this answers or belongs to.
150        request_id: String,
151        /// The session this concerns.
152        session_id: String,
153        /// The session's event sequence number: consecutive, so a gap means events were lost.
154        seq: u64,
155        /// Whether queued prompts wait instead of starting.
156        paused: bool,
157    },
158    /// A turn began.
159    #[serde(rename = "turn.started")]
160    TurnStarted {
161        /// The client request this answers or belongs to.
162        request_id: String,
163        /// The session this concerns.
164        session_id: String,
165        /// The turn this belongs to.
166        turn_id: String,
167        /// The session's event sequence number: consecutive, so a gap means events were lost.
168        seq: u64,
169        /// Set when the server started this turn itself, such as to report
170        /// finished background work; absent for a client's own `turn.start`.
171        #[serde(default, skip_serializing_if = "Option::is_none")]
172        origin: Option<TurnOrigin>,
173    },
174    /// Answer text as it streams.
175    #[serde(rename = "assistant.delta")]
176    AssistantDelta {
177        /// The client request this answers or belongs to.
178        request_id: String,
179        /// The session this concerns.
180        session_id: String,
181        /// The turn this belongs to.
182        turn_id: String,
183        /// The session's event sequence number: consecutive, so a gap means events were lost.
184        seq: u64,
185        /// The text.
186        content: String,
187    },
188    /// The model's complete answer for one step.
189    #[serde(rename = "assistant.completed")]
190    AssistantCompleted {
191        /// The client request this answers or belongs to.
192        request_id: String,
193        /// The session this concerns.
194        session_id: String,
195        /// The turn this belongs to.
196        turn_id: String,
197        /// The session's event sequence number: consecutive, so a gap means events were lost.
198        seq: u64,
199        /// The text.
200        content: String,
201    },
202    /// The model asked to call a tool.
203    #[serde(rename = "tool.proposed")]
204    ToolProposed {
205        /// The client request this answers or belongs to.
206        request_id: String,
207        /// The session this concerns.
208        session_id: String,
209        /// The turn this belongs to.
210        turn_id: String,
211        /// The session's event sequence number: consecutive, so a gap means events were lost.
212        seq: u64,
213        /// The tool call, as the model named it.
214        call_id: String,
215        /// The tool.
216        name: String,
217        /// The call's arguments as the model sent them.
218        arguments: Value,
219    },
220    /// A tool call waits for a person's decision.
221    #[serde(rename = "approval.requested")]
222    ApprovalRequested {
223        /// The client request this answers or belongs to.
224        request_id: String,
225        /// The session this concerns.
226        session_id: String,
227        /// The turn this belongs to.
228        turn_id: String,
229        /// The session's event sequence number: consecutive, so a gap means events were lost.
230        seq: u64,
231        /// Answer with `approval.resolve` naming this ID.
232        approval_id: String,
233        /// The tool call, as the model named it.
234        call_id: String,
235        /// The tool.
236        name: String,
237        /// The call's risk, which selected the approval rule.
238        risk: String,
239        /// The session's workspace directory.
240        cwd: String,
241        /// What the call will do, for the person deciding.
242        summary: String,
243    },
244    /// An approved tool call began running.
245    #[serde(rename = "tool.started")]
246    ToolStarted {
247        /// The client request this answers or belongs to.
248        request_id: String,
249        /// The session this concerns.
250        session_id: String,
251        /// The turn this belongs to.
252        turn_id: String,
253        /// The session's event sequence number: consecutive, so a gap means events were lost.
254        seq: u64,
255        /// The tool call, as the model named it.
256        call_id: String,
257        /// The tool.
258        name: String,
259    },
260    /// Short status lines from a running tool, at most two events a second
261    /// per call and 512 bytes each. Display only; not part of the history.
262    #[serde(rename = "tool.progress")]
263    ToolProgress {
264        /// The client request this answers or belongs to.
265        request_id: String,
266        /// The session this concerns.
267        session_id: String,
268        /// The turn this belongs to.
269        turn_id: String,
270        /// The session's event sequence number: consecutive, so a gap means events were lost.
271        seq: u64,
272        /// The tool call, as the model named it.
273        call_id: String,
274        /// The status lines.
275        text: String,
276    },
277    /// A tool call finished; its output goes to the model.
278    #[serde(rename = "tool.completed")]
279    ToolCompleted {
280        /// The client request this answers or belongs to.
281        request_id: String,
282        /// The session this concerns.
283        session_id: String,
284        /// The turn this belongs to.
285        turn_id: String,
286        /// The session's event sequence number: consecutive, so a gap means events were lost.
287        seq: u64,
288        /// The tool call, as the model named it.
289        call_id: String,
290        /// The tool.
291        name: String,
292        /// Whether the tool succeeded.
293        success: bool,
294        /// What the model sees as the result.
295        output: String,
296        /// Whether `output` was cut to its limit.
297        truncated: bool,
298        /// Why the call failed; absent when it succeeded, and from servers
299        /// before 0.3.0.
300        #[serde(default, skip_serializing_if = "Option::is_none")]
301        error: Option<ToolErrorKind>,
302        /// Background jobs this call started, or whose results it showed the
303        /// model; absent when none, and from servers before 0.3.0.
304        #[serde(default, skip_serializing_if = "Vec::is_empty")]
305        jobs: Vec<JobChange>,
306    },
307    /// Older history was summarized to fit the model's context window.
308    #[serde(rename = "context.compacted")]
309    ContextCompacted {
310        /// The client request this answers or belongs to.
311        request_id: String,
312        /// The session this concerns.
313        session_id: String,
314        /// The turn this belongs to.
315        turn_id: String,
316        /// The session's event sequence number: consecutive, so a gap means events were lost.
317        seq: u64,
318        /// Estimated request size before compaction.
319        before_tokens: usize,
320        /// Estimated request size as sent.
321        after_tokens: usize,
322        /// Messages left out or removed.
323        removed_messages: usize,
324    },
325    /// Old turns were removed to keep the session within its history limits.
326    #[serde(rename = "session.trimmed")]
327    SessionTrimmed {
328        /// The client request this answers or belongs to.
329        request_id: String,
330        /// The session this concerns.
331        session_id: String,
332        /// The session's event sequence number: consecutive, so a gap means events were lost.
333        seq: u64,
334        /// Messages left out or removed.
335        removed_messages: usize,
336        /// Size of the session history afterwards.
337        history_bytes: usize,
338    },
339    /// The session's history and queue were cleared.
340    #[serde(rename = "session.cleared")]
341    SessionCleared {
342        /// The client request this answers or belongs to.
343        request_id: String,
344        /// The session this concerns.
345        session_id: String,
346        /// The session's event sequence number: consecutive, so a gap means events were lost.
347        seq: u64,
348    },
349    /// A turn finished.
350    #[serde(rename = "turn.completed")]
351    TurnCompleted {
352        /// The client request this answers or belongs to.
353        request_id: String,
354        /// The session this concerns.
355        session_id: String,
356        /// The turn this belongs to.
357        turn_id: String,
358        /// The session's event sequence number: consecutive, so a gap means events were lost.
359        seq: u64,
360        /// Model requests the turn made.
361        steps: usize,
362        /// Tokens the turn used, as the provider reported them.
363        usage: Usage,
364        /// Set when the server started this turn itself, such as to report
365        /// finished background work; absent for a client's own `turn.start`.
366        #[serde(default, skip_serializing_if = "Option::is_none")]
367        origin: Option<TurnOrigin>,
368    },
369    /// A turn was cancelled; its history changes were rolled back.
370    #[serde(rename = "turn.cancelled")]
371    TurnCancelled {
372        /// The client request this answers or belongs to.
373        request_id: String,
374        /// The session this concerns.
375        session_id: String,
376        /// The turn this belongs to.
377        turn_id: String,
378        /// The session's event sequence number: consecutive, so a gap means events were lost.
379        seq: u64,
380        /// Set when the server started this turn itself, such as to report
381        /// finished background work; absent for a client's own `turn.start`.
382        #[serde(default, skip_serializing_if = "Option::is_none")]
383        origin: Option<TurnOrigin>,
384    },
385    /// A turn failed; its history changes were rolled back.
386    #[serde(rename = "turn.failed")]
387    TurnFailed {
388        /// The client request this answers or belongs to.
389        request_id: String,
390        /// The session this concerns.
391        session_id: String,
392        /// The turn this belongs to.
393        turn_id: String,
394        /// The session's event sequence number: consecutive, so a gap means events were lost.
395        seq: u64,
396        /// Stable machine-readable error code.
397        code: ErrorCode,
398        /// What went wrong, for people.
399        message: String,
400        /// Set when the server started this turn itself, such as to report
401        /// finished background work; absent for a client's own `turn.start`.
402        #[serde(default, skip_serializing_if = "Option::is_none")]
403        origin: Option<TurnOrigin>,
404    },
405    /// A request failed, or a connection-level error.
406    #[serde(rename = "error")]
407    Error {
408        /// The client request this answers or belongs to.
409        #[serde(skip_serializing_if = "Option::is_none")]
410        request_id: Option<String>,
411        /// Stable machine-readable error code.
412        code: ErrorCode,
413        /// What went wrong, for people.
414        message: String,
415        /// Whether the connection is unusable after this error.
416        fatal: bool,
417    },
418    /// An event this client does not know, from a newer server. Never sent.
419    #[serde(other, rename = "unknown")]
420    Unknown,
421}
422
423impl ServerEvent {
424    /// The submitting request of a turn-scoped event, which identifies the
425    /// turn to a client that has several turns' events interleaved.
426    pub fn turn_request_id(&self) -> Option<&str> {
427        match self {
428            Self::QueueDequeued { request_id, .. }
429            | Self::TurnStarted { request_id, .. }
430            | Self::AssistantDelta { request_id, .. }
431            | Self::AssistantCompleted { request_id, .. }
432            | Self::ToolProposed { request_id, .. }
433            | Self::ApprovalRequested { request_id, .. }
434            | Self::ToolStarted { request_id, .. }
435            | Self::ToolProgress { request_id, .. }
436            | Self::ToolCompleted { request_id, .. }
437            | Self::ContextCompacted { request_id, .. }
438            | Self::TurnCompleted { request_id, .. }
439            | Self::TurnCancelled { request_id, .. }
440            | Self::TurnFailed { request_id, .. } => Some(request_id),
441            _ => None,
442        }
443    }
444}