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