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 ListBotSessions {
58 request_id: String,
59 bot_id: String,
60 },
61 CreateSession {
62 request_id: String,
63 workspace: PathBuf,
64 bot_id: String,
65 },
66 CreateWorkspaceDirectory {
67 request_id: String,
68 parent: PathBuf,
69 name: String,
70 },
71 OpenSession {
72 request_id: String,
73 session_id: String,
74 last_sequence: Option<u64>,
75 },
76 GetSessionHistory {
77 request_id: String,
78 session_id: String,
79 before_sequence: Option<u64>,
80 },
81 RenameSession {
82 request_id: String,
83 session_id: String,
84 title: String,
85 },
86 SetSessionPinned {
87 request_id: String,
88 session_id: String,
89 pinned: bool,
90 },
91 DeleteSessions {
92 request_id: String,
93 session_ids: Vec<String>,
94 },
95 CreateSwarm {
96 request_id: String,
97 title: String,
98 leader_bot_id: String,
99 member_bot_ids: Vec<String>,
100 },
101 AddSwarmMember {
102 request_id: String,
103 swarm_id: String,
104 bot_id: String,
105 },
106 LeaveSwarm {
107 request_id: String,
108 swarm_id: String,
109 bot_id: String,
110 },
111 RenameSwarm {
112 request_id: String,
113 swarm_id: String,
114 title: String,
115 },
116 DisbandSwarm {
117 request_id: String,
118 swarm_id: String,
119 },
120 PostSwarmMessage {
121 request_id: String,
122 swarm_id: String,
123 text: String,
124 },
125 Submit {
126 session_id: String,
127 submission: Submission,
128 },
129 SubmitScratchpad {
130 request_id: String,
131 scope: ScratchpadScope,
132 operation: Op,
133 },
134 BeginSessionFileUpload {
135 request_id: String,
136 session_id: String,
137 name: String,
138 size: u64,
139 media_type: String,
140 },
141 UploadSessionFileChunk {
142 request_id: String,
143 session_id: String,
144 upload_id: String,
145 offset: u64,
146 #[serde(with = "base64_bytes")]
147 data: Vec<u8>,
148 },
149 FinishSessionFileUpload {
150 request_id: String,
151 session_id: String,
152 upload_id: String,
153 },
154 DeleteSessionFile {
155 request_id: String,
156 session_id: String,
157 file_id: String,
158 },
159 ListSessionFiles {
160 request_id: String,
161 session_id: String,
162 },
163 ReadSessionFile {
164 request_id: String,
165 session_id: String,
166 file_id: String,
167 offset: u64,
168 max_bytes: usize,
169 },
170 CreateBot {
171 request_id: String,
172 name: String,
173 description: String,
174 },
175 ListBots {
176 request_id: String,
177 },
178 UpdateBot {
179 request_id: String,
180 id: String,
181 expected_revision: u64,
182 name: String,
183 description: String,
184 tint: ProviderTint,
185 config: AgentComposition,
186 },
187 DeleteBot {
188 request_id: String,
189 id: String,
190 expected_revision: u64,
191 },
192 ConfigureBotDefaults {
193 request_id: String,
194 expected_revision: u64,
195 config: AgentComposition,
196 },
197 InstallExtension {
198 request_id: String,
199 source: String,
200 reference: Option<String>,
201 subdirectory: Option<String>,
202 },
203 UpdateExtension {
204 request_id: String,
205 id: String,
206 },
207 UninstallExtension {
208 request_id: String,
209 id: String,
210 },
211 TrustExtensionHooks {
212 request_id: String,
213 id: String,
214 expected_digest: String,
215 },
216 RevokeExtensionHooksTrust {
217 request_id: String,
218 id: String,
219 expected_digest: String,
220 },
221 ProbeGitCredential {
222 request_id: String,
223 target: String,
224 },
225 ApproveGitCredential {
226 request_id: String,
227 target: String,
228 username: String,
229 token: String,
230 },
231 ListSshIdentities {
232 request_id: String,
233 },
234 GenerateSshIdentity {
235 request_id: String,
236 },
237 GetGitDiff {
238 request_id: String,
239 session_id: String,
240 scope: GitDiffScope,
241 },
242 SwitchGitBranch {
243 request_id: String,
244 session_id: String,
245 branch: String,
246 },
247 ListDirectories {
248 request_id: String,
249 path: PathBuf,
250 include_files: bool,
251 },
252 ListWorkspaceFiles {
253 request_id: String,
254 session_id: String,
255 scope: WorkspaceFileScope,
256 },
257 ReadWorkspaceFile {
258 request_id: String,
259 session_id: String,
260 path: String,
261 offset: u64,
262 max_bytes: usize,
263 },
264 WriteWorkspaceFile {
265 request_id: String,
266 session_id: String,
267 path: String,
268 content: String,
269 },
270 SetProviderCredential {
271 request_id: String,
272 instance: String,
273 provider: String,
274 api_key: String,
275 },
276 SetProviderEndpointCredential {
277 request_id: String,
278 instance: String,
279 provider: String,
280 base_url: String,
281 api_key: String,
282 },
283 RegisterProvider {
284 request_id: String,
285 config: ProviderConfig,
286 label: String,
287 tint: ProviderTint,
288 model_ids: Vec<String>,
289 reasoning_efforts: Vec<String>,
290 },
291 RemoveProvider {
292 request_id: String,
293 instance: String,
294 },
295 CreatePairingCode {
296 request_id: String,
297 },
298 StartProviderLogin {
299 request_id: String,
300 provider: String,
301 },
302 GetProfile {
303 request_id: String,
304 },
305 CreateRoutine {
306 request_id: String,
307 bot_id: String,
308 workspace: PathBuf,
309 instructions: String,
310 schedule: RoutineSchedule,
311 ends_at: Option<i64>,
312 },
313 ListRoutines {
314 request_id: String,
315 bot_id: Option<String>,
316 },
317 UpdateRoutine {
318 request_id: String,
319 id: String,
320 bot_id: String,
321 workspace: PathBuf,
322 instructions: String,
323 schedule: RoutineSchedule,
324 ends_at: Option<i64>,
325 enabled: bool,
326 },
327 DeleteRoutine {
328 request_id: String,
329 id: String,
330 },
331 RunRoutine {
332 request_id: String,
333 id: String,
334 },
335 ListRoutineHistory {
336 request_id: String,
337 id: Option<String>,
338 },
339 DeleteRoutineRun {
340 request_id: String,
341 id: String,
342 },
343 GetRoutineRunPreview {
344 request_id: String,
345 id: String,
346 before_sequence: Option<u64>,
347 },
348}
349
350#[derive(Debug, Clone, PartialEq, Serialize)]
352pub struct ServerFrame {
353 pub version: u16,
354 #[serde(flatten)]
355 pub message: ServerMessage,
356}
357
358impl<'de> Deserialize<'de> for ServerFrame {
359 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
360 where
361 D: serde::Deserializer<'de>,
362 {
363 let (version, message) = deserialize_frame(deserializer)?;
364 let message = serde_json::from_value(message).map_err(D::Error::custom)?;
365 Ok(Self { version, message })
366 }
367}
368
369impl ServerFrame {
370 #[must_use]
372 pub const fn new(message: ServerMessage) -> Self {
373 Self {
374 version: PROTOCOL_VERSION,
375 message,
376 }
377 }
378}
379
380#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
382#[serde(tag = "type", rename_all = "snake_case")]
383#[non_exhaustive]
384pub enum ServerMessage {
385 Paired {
386 client_id: String,
387 token: String,
388 },
389 Authenticated,
390 Ready {
391 payload: ReadyPayload,
392 },
393 SessionOpened {
394 request_id: String,
395 payload: SessionReadyPayload,
396 },
397 SessionReplayComplete {
398 request_id: String,
399 session_id: String,
400 },
401 SessionHistory {
402 request_id: String,
403 session_id: String,
404 records: Vec<RecordedEvent>,
405 next_before_sequence: Option<u64>,
406 },
407 SessionChanged {
408 payload: SessionReadyPayload,
409 },
410 GatewayConfigured {
411 request_id: String,
412 payload: ReadyPayload,
413 },
414 ScratchpadChanged {
415 request_id: String,
416 scope: ScratchpadScope,
417 contribution: FrontendContribution,
418 },
419 Accepted {
420 request_id: String,
421 },
422 SessionFileUploadReady {
423 request_id: String,
424 session_id: String,
425 upload_id: String,
426 max_chunk_bytes: usize,
427 },
428 SessionFileUploadChunkAccepted {
429 request_id: String,
430 session_id: String,
431 upload_id: String,
432 next_offset: u64,
433 },
434 SessionFileUploadCompleted {
435 request_id: String,
436 session_id: String,
437 file: SessionFileReference,
438 },
439 SessionFiles {
440 request_id: String,
441 session_id: String,
442 files: Vec<SessionFileRecord>,
443 },
444 SessionFileChunk {
445 request_id: String,
446 session_id: String,
447 file_id: String,
448 offset: u64,
449 #[serde(with = "base64_bytes")]
450 data: Vec<u8>,
451 next_offset: Option<u64>,
452 },
453 Rejected {
454 request_id: String,
455 code: String,
456 message: String,
457 fatal: bool,
458 },
459 AgentEvent {
460 session_id: String,
461 record: RecordedEvent,
462 },
463 Sessions {
464 #[serde(default, skip_serializing_if = "Option::is_none")]
465 request_id: Option<String>,
466 sessions: Vec<SessionRecord>,
467 },
468 BackgroundApprovals {
469 approvals: Vec<BackgroundApproval>,
470 },
471 SwarmAttentions {
472 attentions: Vec<SwarmAttention>,
473 },
474 BotSessions {
475 request_id: String,
476 bot_id: String,
477 sessions: Vec<SessionRecord>,
478 },
479 Bots {
480 #[serde(default, skip_serializing_if = "Option::is_none")]
481 request_id: Option<String>,
482 bots: Vec<BotRecord>,
483 },
484 Swarms {
485 #[serde(default, skip_serializing_if = "Option::is_none")]
486 request_id: Option<String>,
487 swarms: Vec<SwarmRecord>,
488 },
489 Clients {
490 request_id: String,
491 current_client_id: String,
492 clients: Vec<ClientStatus>,
493 },
494 ProviderCredentialSaved {
495 request_id: String,
496 instance: String,
497 provider: String,
498 },
499 PairingCode {
500 request_id: String,
501 code: String,
502 expires_at: i64,
503 },
504 ProviderLoginStarted {
505 request_id: String,
506 login_id: String,
507 provider: String,
508 verification_url: String,
509 user_code: String,
510 },
511 ProviderLoginFinished {
512 request_id: String,
513 login_id: String,
514 provider: String,
515 },
516 GitCredentialStatus {
517 request_id: String,
518 available: bool,
519 #[serde(skip_serializing_if = "Option::is_none")]
520 username: Option<String>,
521 },
522 SshIdentities {
523 request_id: String,
524 identities: Vec<SshIdentityRecord>,
525 },
526 SshIdentityGenerated {
527 request_id: String,
528 identity: SshIdentityRecord,
529 public_key: String,
530 },
531 Profile {
532 request_id: String,
533 profile: ProfileSnapshot,
534 },
535 GitDiff {
536 request_id: String,
537 session_id: String,
538 scope: GitDiffScope,
539 diff: String,
540 },
541 Directories {
542 request_id: String,
543 listing: DirectoryListing,
544 },
545 WorkspaceFiles {
546 request_id: String,
547 session_id: String,
548 files: Vec<WorkspaceFileRecord>,
549 truncated: bool,
550 },
551 WorkspaceFileChunk {
552 request_id: String,
553 session_id: String,
554 path: String,
555 offset: u64,
556 #[serde(with = "base64_bytes")]
557 data: Vec<u8>,
558 next_offset: Option<u64>,
559 },
560 Routines {
561 request_id: String,
562 routines: Vec<Routine>,
563 },
564 RoutineHistory {
565 request_id: String,
566 runs: Vec<RoutineRun>,
567 },
568 RoutineRunPreview {
569 request_id: String,
570 preview: RoutineRunPreview,
571 },
572 Error {
573 code: String,
574 message: String,
575 fatal: bool,
576 },
577}