Skip to main content

mobius_gateway/wire/
messages.rs

1use super::*;
2
3/// One client-to-gateway frame.
4#[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    /// Wraps a message in the current protocol version.
24    #[must_use]
25    pub const fn new(message: ClientMessage) -> Self {
26        Self {
27            version: PROTOCOL_VERSION,
28            message,
29        }
30    }
31}
32
33/// Authenticated operations accepted by the gateway.
34#[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/// One gateway-to-client frame.
364#[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    /// Wraps a message in the current protocol version.
384    #[must_use]
385    pub const fn new(message: ServerMessage) -> Self {
386        Self {
387            version: PROTOCOL_VERSION,
388            message,
389        }
390    }
391}
392
393/// Results and broadcasts emitted by the gateway.
394#[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}