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    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/// One gateway-to-client frame.
351#[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    /// Wraps a message in the current protocol version.
371    #[must_use]
372    pub const fn new(message: ServerMessage) -> Self {
373        Self {
374            version: PROTOCOL_VERSION,
375            message,
376        }
377    }
378}
379
380/// Results and broadcasts emitted by the gateway.
381#[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}