1use super::*;
2
3#[derive(Debug, Clone, PartialEq, Serialize)]
5pub struct ClientFrame {
6 pub version: u16,
7 #[serde(flatten)]
8 pub message: ClientMessage,
9}
10
11impl<'de> Deserialize<'de> for ClientFrame {
12 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
13 where
14 D: serde::Deserializer<'de>,
15 {
16 let (version, message) = deserialize_frame(deserializer)?;
17 let message = serde_json::from_value(message).map_err(D::Error::custom)?;
18 Ok(Self { version, message })
19 }
20}
21
22impl ClientFrame {
23 #[must_use]
25 pub const fn new(message: ClientMessage) -> Self {
26 Self {
27 version: PROTOCOL_VERSION,
28 message,
29 }
30 }
31}
32
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
35#[serde(tag = "type", rename_all = "snake_case")]
36#[non_exhaustive]
37pub enum ClientMessage {
38 Pair {
39 code: String,
40 client_label: String,
41 client_kind: ClientKind,
42 },
43 Authenticate {
44 token: String,
45 client_kind: ClientKind,
46 },
47 ListClients {
48 request_id: String,
49 },
50 UnpairClient {
51 request_id: String,
52 client_id: String,
53 },
54 ListSessions {
55 request_id: String,
56 },
57 CreateSession {
58 request_id: String,
59 workspace: PathBuf,
60 },
61 CreateWorkspaceDirectory {
62 request_id: String,
63 parent: PathBuf,
64 name: String,
65 },
66 OpenSession {
67 request_id: String,
68 session_id: String,
69 last_sequence: Option<u64>,
70 },
71 GetSessionHistory {
72 request_id: String,
73 session_id: String,
74 before_sequence: Option<u64>,
75 },
76 RenameSession {
77 request_id: String,
78 session_id: String,
79 title: String,
80 },
81 SetSessionPinned {
82 request_id: String,
83 session_id: String,
84 pinned: bool,
85 },
86 DeleteSession {
87 request_id: String,
88 session_id: String,
89 },
90 CreateSwarm {
91 request_id: String,
92 leader_session_id: String,
93 member_session_ids: Vec<String>,
94 },
95 AddSwarmMember {
96 request_id: String,
97 swarm_id: String,
98 session_id: String,
99 },
100 LeaveSwarm {
101 request_id: String,
102 swarm_id: String,
103 session_id: String,
104 },
105 DisbandSwarm {
106 request_id: String,
107 swarm_id: String,
108 },
109 Submit {
110 session_id: String,
111 submission: Submission,
112 },
113 SubmitGlobalScratchpad {
114 request_id: String,
115 operation: Op,
116 },
117 BeginSessionFileUpload {
118 request_id: String,
119 session_id: String,
120 name: String,
121 size: u64,
122 media_type: String,
123 },
124 UploadSessionFileChunk {
125 request_id: String,
126 session_id: String,
127 upload_id: String,
128 offset: u64,
129 #[serde(with = "base64_bytes")]
130 data: Vec<u8>,
131 },
132 FinishSessionFileUpload {
133 request_id: String,
134 session_id: String,
135 upload_id: String,
136 },
137 ListSessionFiles {
138 request_id: String,
139 session_id: String,
140 },
141 ReadSessionFile {
142 request_id: String,
143 session_id: String,
144 file_id: String,
145 offset: u64,
146 max_bytes: usize,
147 },
148 ConfigureSession {
149 request_id: String,
150 session_id: String,
151 expected_revision: u64,
152 config: AgentComposition,
153 },
154 ConfigureDefaultAgent {
155 request_id: String,
156 expected_revision: u64,
157 config: AgentComposition,
158 },
159 InstallExtension {
160 request_id: String,
161 source: String,
162 reference: Option<String>,
163 subdirectory: Option<String>,
164 },
165 UpdateExtension {
166 request_id: String,
167 id: String,
168 },
169 UninstallExtension {
170 request_id: String,
171 id: String,
172 },
173 TrustExtensionHooks {
174 request_id: String,
175 id: String,
176 expected_digest: String,
177 },
178 RevokeExtensionHooksTrust {
179 request_id: String,
180 id: String,
181 expected_digest: String,
182 },
183 ProbeGitCredential {
184 request_id: String,
185 target: String,
186 },
187 ApproveGitCredential {
188 request_id: String,
189 target: String,
190 username: String,
191 token: String,
192 },
193 ListSshIdentities {
194 request_id: String,
195 },
196 GenerateSshIdentity {
197 request_id: String,
198 },
199 GetGitDiff {
200 request_id: String,
201 session_id: String,
202 scope: GitDiffScope,
203 },
204 SwitchGitBranch {
205 request_id: String,
206 session_id: String,
207 branch: String,
208 },
209 ListDirectories {
210 request_id: String,
211 path: PathBuf,
212 include_files: bool,
213 },
214 ListWorkspaceFiles {
215 request_id: String,
216 session_id: String,
217 scope: WorkspaceFileScope,
218 },
219 ReadWorkspaceFile {
220 request_id: String,
221 session_id: String,
222 path: String,
223 offset: u64,
224 max_bytes: usize,
225 },
226 WriteWorkspaceFile {
227 request_id: String,
228 session_id: String,
229 path: String,
230 content: String,
231 },
232 SetProviderCredential {
233 request_id: String,
234 instance: String,
235 provider: String,
236 api_key: String,
237 },
238 SetProviderEndpointCredential {
239 request_id: String,
240 instance: String,
241 provider: String,
242 base_url: String,
243 api_key: String,
244 },
245 RegisterProvider {
246 request_id: String,
247 config: ProviderConfig,
248 label: String,
249 tint: ProviderTint,
250 model_ids: Vec<String>,
251 reasoning_efforts: Vec<String>,
252 replace_existing_selections: bool,
253 },
254 RemoveProvider {
255 request_id: String,
256 instance: String,
257 },
258 CreatePairingCode {
259 request_id: String,
260 },
261 StartProviderLogin {
262 request_id: String,
263 provider: String,
264 },
265 GetProfile {
266 request_id: String,
267 },
268 CreateCron {
269 request_id: String,
270 source_session_id: String,
271 task: String,
272 schedule: CronSchedule,
273 ends_at: Option<i64>,
274 },
275 ListCron {
276 request_id: String,
277 },
278 UpdateCron {
279 request_id: String,
280 id: String,
281 source_session_id: String,
282 task: String,
283 schedule: CronSchedule,
284 ends_at: Option<i64>,
285 enabled: bool,
286 },
287 DeleteCron {
288 request_id: String,
289 id: String,
290 },
291 RunCron {
292 request_id: String,
293 id: String,
294 },
295 ListCronHistory {
296 request_id: String,
297 id: Option<String>,
298 },
299 GetCronRunPreview {
300 request_id: String,
301 id: String,
302 before_sequence: Option<u64>,
303 },
304}
305
306#[derive(Debug, Clone, PartialEq, Serialize)]
308pub struct ServerFrame {
309 pub version: u16,
310 #[serde(flatten)]
311 pub message: ServerMessage,
312}
313
314impl<'de> Deserialize<'de> for ServerFrame {
315 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
316 where
317 D: serde::Deserializer<'de>,
318 {
319 let (version, message) = deserialize_frame(deserializer)?;
320 let message = serde_json::from_value(message).map_err(D::Error::custom)?;
321 Ok(Self { version, message })
322 }
323}
324
325impl ServerFrame {
326 #[must_use]
328 pub const fn new(message: ServerMessage) -> Self {
329 Self {
330 version: PROTOCOL_VERSION,
331 message,
332 }
333 }
334}
335
336#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
338#[serde(tag = "type", rename_all = "snake_case")]
339#[non_exhaustive]
340pub enum ServerMessage {
341 Paired {
342 client_id: String,
343 token: String,
344 },
345 Authenticated,
346 Ready {
347 payload: ReadyPayload,
348 },
349 SessionOpened {
350 request_id: String,
351 payload: SessionReadyPayload,
352 },
353 SessionReplayComplete {
354 request_id: String,
355 session_id: String,
356 },
357 SessionHistory {
358 request_id: String,
359 session_id: String,
360 records: Vec<RecordedEvent>,
361 next_before_sequence: Option<u64>,
362 },
363 SessionChanged {
364 payload: SessionReadyPayload,
365 },
366 GatewayConfigured {
367 request_id: String,
368 payload: ReadyPayload,
369 },
370 GlobalScratchpadChanged {
371 request_id: String,
372 contribution: FrontendContribution,
373 },
374 Accepted {
375 request_id: String,
376 },
377 SessionFileUploadReady {
378 request_id: String,
379 session_id: String,
380 upload_id: String,
381 max_chunk_bytes: usize,
382 },
383 SessionFileUploadChunkAccepted {
384 request_id: String,
385 session_id: String,
386 upload_id: String,
387 next_offset: u64,
388 },
389 SessionFileUploadCompleted {
390 request_id: String,
391 session_id: String,
392 file: SessionFileReference,
393 },
394 SessionFiles {
395 request_id: String,
396 session_id: String,
397 files: Vec<SessionFileRecord>,
398 },
399 SessionFileChunk {
400 request_id: String,
401 session_id: String,
402 file_id: String,
403 offset: u64,
404 #[serde(with = "base64_bytes")]
405 data: Vec<u8>,
406 next_offset: Option<u64>,
407 },
408 Rejected {
409 request_id: String,
410 code: String,
411 message: String,
412 fatal: bool,
413 },
414 AgentEvent {
415 session_id: String,
416 record: RecordedEvent,
417 },
418 Sessions {
419 #[serde(default, skip_serializing_if = "Option::is_none")]
420 request_id: Option<String>,
421 sessions: Vec<SessionRecord>,
422 },
423 Swarms {
424 #[serde(default, skip_serializing_if = "Option::is_none")]
425 request_id: Option<String>,
426 swarms: Vec<SwarmRecord>,
427 },
428 Clients {
429 request_id: String,
430 current_client_id: String,
431 clients: Vec<ClientStatus>,
432 },
433 ProviderCredentialSaved {
434 request_id: String,
435 instance: String,
436 provider: String,
437 },
438 PairingCode {
439 request_id: String,
440 code: String,
441 expires_at: i64,
442 },
443 ProviderLoginStarted {
444 request_id: String,
445 login_id: String,
446 provider: String,
447 verification_url: String,
448 user_code: String,
449 },
450 ProviderLoginFinished {
451 request_id: String,
452 login_id: String,
453 provider: String,
454 },
455 GitCredentialStatus {
456 request_id: String,
457 available: bool,
458 #[serde(skip_serializing_if = "Option::is_none")]
459 username: Option<String>,
460 },
461 SshIdentities {
462 request_id: String,
463 identities: Vec<SshIdentityRecord>,
464 },
465 SshIdentityGenerated {
466 request_id: String,
467 identity: SshIdentityRecord,
468 public_key: String,
469 },
470 Profile {
471 request_id: String,
472 profile: ProfileSnapshot,
473 },
474 GitDiff {
475 request_id: String,
476 session_id: String,
477 scope: GitDiffScope,
478 diff: String,
479 },
480 Directories {
481 request_id: String,
482 listing: DirectoryListing,
483 },
484 WorkspaceFiles {
485 request_id: String,
486 session_id: String,
487 files: Vec<WorkspaceFileRecord>,
488 truncated: bool,
489 },
490 WorkspaceFileChunk {
491 request_id: String,
492 session_id: String,
493 path: String,
494 offset: u64,
495 #[serde(with = "base64_bytes")]
496 data: Vec<u8>,
497 next_offset: Option<u64>,
498 },
499 CronTasks {
500 request_id: String,
501 tasks: Vec<CronTask>,
502 },
503 CronHistory {
504 request_id: String,
505 runs: Vec<CronRun>,
506 },
507 CronRunPreview {
508 request_id: String,
509 preview: CronRunPreview,
510 },
511 Error {
512 code: String,
513 message: String,
514 fatal: bool,
515 },
516}