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    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/// One gateway-to-client frame.
307#[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    /// Wraps a message in the current protocol version.
327    #[must_use]
328    pub const fn new(message: ServerMessage) -> Self {
329        Self {
330            version: PROTOCOL_VERSION,
331            message,
332        }
333    }
334}
335
336/// Results and broadcasts emitted by the gateway.
337#[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}