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}