Skip to main content

mobius_gateway/wire/
messages.rs

1use super::*;
2
3const MAX_REQUEST_ID_BYTES: usize = 256;
4
5/// One client-to-gateway frame.
6#[derive(Debug, Clone, PartialEq, Serialize)]
7pub struct ClientFrame {
8    /// The version.
9    pub version: u16,
10    #[serde(flatten)]
11    /// The message.
12    pub message: ClientMessage,
13}
14
15impl<'de> Deserialize<'de> for ClientFrame {
16    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
17    where
18        D: serde::Deserializer<'de>,
19    {
20        let (version, message) = deserialize_frame(deserializer)?;
21        if let Some(request_id) = message.get("request_id")
22            && !request_id
23                .as_str()
24                .is_some_and(|id| !id.is_empty() && id.len() <= MAX_REQUEST_ID_BYTES)
25        {
26            return Err(D::Error::custom("request ID must be 1–256 bytes"));
27        }
28        let message = serde_json::from_value(message).map_err(D::Error::custom)?;
29        Ok(Self { version, message })
30    }
31}
32
33impl ClientFrame {
34    /// Wraps a message in the current protocol version.
35    #[must_use]
36    pub const fn new(message: ClientMessage) -> Self {
37        Self {
38            version: PROTOCOL_VERSION,
39            message,
40        }
41    }
42}
43
44/// Authenticated operations accepted by the gateway.
45#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
46#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
47#[non_exhaustive]
48pub enum ClientMessage {
49    /// Opens the gateway-owned computer and foregrounds the chat's assigned tab.
50    OpenComputer {
51        /// The request identifier.
52        request_id: String,
53        /// The chat whose assigned browser tab to show.
54        session_id: String,
55    },
56    /// Opens or closes this authenticated connection's remote desktop stream.
57    SetDesktopStream {
58        /// The request identifier.
59        request_id: String,
60        /// Whether to stream the desktop.
61        enabled: bool,
62    },
63    /// Ordered RFB bytes for this connection's desktop stream.
64    DesktopData {
65        /// The raw RFB data, limited to [`MAX_DESKTOP_CHUNK_BYTES`].
66        #[serde(with = "desktop_bytes")]
67        data: Vec<u8>,
68    },
69    /// Requests or releases the gateway's human desktop lease.
70    SetDesktopControl {
71        /// The request identifier.
72        request_id: String,
73        /// Whether to take control.
74        enabled: bool,
75        /// The chat whose computer view requested control.
76        session_id: Option<String>,
77    },
78    /// Replaces this connection's optional notification exclusions.
79    SetNotifications {
80        /// The request identifier.
81        request_id: String,
82        /// Broadcasts to suppress until replaced or the connection closes.
83        disabled: BTreeSet<GatewayNotification>,
84    },
85    /// Selects a chat for file, history and workspace requests without replaying or
86    /// streaming its events; answered by `session_opened` alone.
87    SelectSession {
88        /// The request identifier.
89        request_id: String,
90        /// The session identifier.
91        session_id: String,
92    },
93    /// Asks for the line totals of a Git diff, as Git counts them.
94    GetGitDiffTotals {
95        /// The request identifier.
96        request_id: String,
97        /// The session identifier.
98        session_id: String,
99        /// The scope.
100        scope: GitDiffScope,
101    },
102    /// Registers the local Mac renderer for gateway-assigned browser pages.
103    SetBrowserRuntime {
104        /// The request identifier.
105        request_id: String,
106        /// Whether the renderer is available.
107        enabled: bool,
108    },
109    /// Supplies the scoped Unix endpoint of an assigned local browser page.
110    BrowserPageReply {
111        /// The gateway's page request identifier.
112        request_id: String,
113        /// The endpoint, or none if the renderer cannot provide the page.
114        endpoint: Option<String>,
115    },
116    /// Selects the set desktop runtime case.
117    SetDesktopRuntime {
118        /// The request identifier.
119        request_id: String,
120        /// The enabled.
121        enabled: bool,
122    },
123    /// Selects the desktop control reply case.
124    DesktopControlReply {
125        /// The request identifier.
126        request_id: String,
127        /// The response.
128        response: Value,
129    },
130    /// Selects the pair case.
131    Pair {
132        /// The code.
133        code: String,
134        /// The client label.
135        client_label: String,
136        /// The client kind.
137        client_kind: ClientKind,
138    },
139    /// Selects the repair pairing case.
140    RepairPairing {
141        /// The code.
142        code: String,
143        /// The digest of the client token being replaced.
144        replacing_token_digest: [u8; 32],
145        /// The client label.
146        client_label: String,
147        /// The client kind.
148        client_kind: ClientKind,
149    },
150    /// Selects the authenticate case.
151    Authenticate {
152        /// The token.
153        token: String,
154        /// The client kind.
155        client_kind: ClientKind,
156        /// The catalog this client already holds, or does not need. An empty hint is left
157        /// out, so the version check that replaces an older running gateway can reach it.
158        #[serde(default, skip_serializing_if = "CatalogHint::is_empty")]
159        catalog: CatalogHint,
160    },
161    /// Selects the list clients case.
162    ListClients {
163        /// The request identifier.
164        request_id: String,
165    },
166    /// Read collector configuration and delivery status.
167    GetTelemetry {
168        /// Correlates this response with its request.
169        request_id: String,
170    },
171    /// Read storage ownership and measured sizes without mutating files.
172    GetStorageUsage {
173        /// Correlates this response with its request.
174        request_id: String,
175    },
176    /// Replace collector configuration at an expected revision.
177    ConfigureTelemetry {
178        /// Correlates this response with its request.
179        request_id: String,
180        /// Configuration revision observed before editing.
181        expected_revision: u64,
182        /// Configured collectors.
183        sinks: Vec<crate::telemetry::TelemetrySink>,
184        /// IDs whose existing credential sources must survive a redacted report edit.
185        #[serde(default, skip_serializing_if = "Vec::is_empty")]
186        preserve_auth: Vec<String>,
187    },
188    /// Deliver a snapshot immediately.
189    SendTelemetry {
190        /// Correlates this response with its request.
191        request_id: String,
192        /// Collector to send now.
193        sink_id: String,
194    },
195    /// Selects the unpair client case.
196    UnpairClient {
197        /// The request identifier.
198        request_id: String,
199        /// The client identifier.
200        client_id: String,
201    },
202    /// Selects the list sessions case.
203    ListSessions {
204        /// The request identifier.
205        request_id: String,
206    },
207    /// Selects the list bot sessions case.
208    ListBotSessions {
209        /// The request identifier.
210        request_id: String,
211        /// The bot identifier.
212        bot_id: String,
213    },
214    /// Selects the create session case.
215    CreateSession {
216        /// The request identifier.
217        request_id: String,
218        /// The workspace.
219        workspace: PathBuf,
220        /// The bot identifier.
221        bot_id: String,
222    },
223    /// Selects the create workspace directory case.
224    CreateWorkspaceDirectory {
225        /// The request identifier.
226        request_id: String,
227        /// The parent.
228        parent: PathBuf,
229        /// The name.
230        name: String,
231    },
232    /// Selects the open session case.
233    OpenSession {
234        /// The request identifier.
235        request_id: String,
236        /// The session identifier.
237        session_id: String,
238        /// The last sequence.
239        last_sequence: Option<u64>,
240    },
241    /// Selects the get session history case.
242    GetSessionHistory {
243        /// The request identifier.
244        request_id: String,
245        /// The session identifier.
246        session_id: String,
247        /// The before sequence.
248        before_sequence: Option<u64>,
249    },
250    /// Selects the reassign session case.
251    ReassignSession {
252        /// The request identifier.
253        request_id: String,
254        /// The session identifier.
255        session_id: String,
256        /// The bot identifier.
257        bot_id: String,
258    },
259    /// Selects the rename session case.
260    RenameSession {
261        /// The request identifier.
262        request_id: String,
263        /// The session identifier.
264        session_id: String,
265        /// The title.
266        title: String,
267    },
268    /// Selects the attach session folder case.
269    AttachSessionFolder {
270        /// The request identifier.
271        request_id: String,
272        /// The session identifier.
273        session_id: String,
274        /// The folder.
275        folder: PathBuf,
276    },
277    /// Selects the set session pinned case.
278    SetSessionPinned {
279        /// The request identifier.
280        request_id: String,
281        /// The session identifier.
282        session_id: String,
283        /// The pinned.
284        pinned: bool,
285    },
286    /// Selects the delete sessions case.
287    DeleteSessions {
288        /// The request identifier.
289        request_id: String,
290        /// The session identifiers.
291        session_ids: Vec<String>,
292        /// Required deletion scope; history is removed only for all.
293        selection: mobius::backend::session_files::SessionFileSelection,
294    },
295    /// Selects the submit case.
296    Submit {
297        /// The session identifier.
298        session_id: String,
299        /// The submission.
300        submission: Submission,
301    },
302    /// Selects the start realtime voice case.
303    StartRealtimeVoice {
304        /// The request identifier.
305        request_id: String,
306        /// The session identifier.
307        session_id: String,
308        /// The offer sdp.
309        offer_sdp: String,
310    },
311    /// Selects the end realtime voice case.
312    EndRealtimeVoice {
313        /// The session identifier.
314        session_id: String,
315        /// The voice identifier.
316        voice_id: String,
317    },
318    /// Selects the get contributions case.
319    GetContributions {
320        /// The request identifier.
321        request_id: String,
322    },
323    /// Selects the submit contribution case.
324    SubmitContribution {
325        /// The request identifier.
326        request_id: String,
327        /// The operation.
328        operation: Op,
329    },
330    /// Selects the begin session file upload case.
331    BeginSessionFileUpload {
332        /// The request identifier.
333        request_id: String,
334        /// The session identifier.
335        session_id: String,
336        /// The name.
337        name: String,
338        /// The size.
339        size: u64,
340        /// The media type.
341        media_type: String,
342    },
343    /// Selects the upload session file chunk case.
344    UploadSessionFileChunk {
345        /// The request identifier.
346        request_id: String,
347        /// The session identifier.
348        session_id: String,
349        /// The upload identifier.
350        upload_id: String,
351        /// The offset.
352        offset: u64,
353        #[serde(with = "base64_bytes")]
354        /// The data.
355        data: Vec<u8>,
356    },
357    /// Selects the finish session file upload case.
358    FinishSessionFileUpload {
359        /// The request identifier.
360        request_id: String,
361        /// The session identifier.
362        session_id: String,
363        /// The upload identifier.
364        upload_id: String,
365    },
366    /// Selects the list session files case.
367    ListSessionFiles {
368        /// The request identifier.
369        request_id: String,
370        /// The session identifier.
371        session_id: String,
372    },
373    /// Selects the read session file case.
374    ReadSessionFile {
375        /// The request identifier.
376        request_id: String,
377        /// The session identifier.
378        session_id: String,
379        /// The file identifier.
380        file_id: String,
381        /// The offset.
382        offset: u64,
383        /// The max bytes.
384        max_bytes: usize,
385    },
386    /// Selects the create bot case.
387    CreateBot {
388        /// The request identifier.
389        request_id: String,
390        /// The name.
391        name: String,
392        /// The description.
393        description: String,
394    },
395    /// Selects the list bots case.
396    ListBots {
397        /// The request identifier.
398        request_id: String,
399    },
400    /// Selects the update bot case.
401    UpdateBot {
402        /// The request identifier.
403        request_id: String,
404        /// The identifier.
405        id: String,
406        /// The expected revision.
407        expected_revision: u64,
408        /// The name.
409        name: String,
410        /// The description.
411        description: String,
412        /// The tint.
413        tint: ProviderTint,
414        /// The face's silhouette.
415        shape: BotShape,
416        /// The config.
417        config: AgentComposition,
418    },
419    /// Selects the delete bot case.
420    DeleteBot {
421        /// The request identifier.
422        request_id: String,
423        /// The identifier.
424        id: String,
425        /// The expected revision.
426        expected_revision: u64,
427    },
428    /// Selects the configure bot defaults case.
429    ConfigureBotDefaults {
430        /// The request identifier.
431        request_id: String,
432        /// The expected revision.
433        expected_revision: u64,
434        /// The config.
435        config: AgentComposition,
436    },
437    /// Selects the install extension case.
438    InstallExtension {
439        /// The request identifier.
440        request_id: String,
441        /// The source.
442        source: String,
443        /// The reference.
444        reference: Option<String>,
445        /// The subdirectory.
446        subdirectory: Option<String>,
447    },
448    /// Selects the update extension case.
449    UpdateExtension {
450        /// The request identifier.
451        request_id: String,
452        /// The identifier.
453        id: String,
454    },
455    /// Selects the uninstall extension case.
456    UninstallExtension {
457        /// The request identifier.
458        request_id: String,
459        /// The identifier.
460        id: String,
461    },
462    /// Selects the trust extension hooks case.
463    TrustExtensionHooks {
464        /// The request identifier.
465        request_id: String,
466        /// The identifier.
467        id: String,
468        /// The expected digest.
469        expected_digest: String,
470    },
471    /// Selects the revoke extension hooks trust case.
472    RevokeExtensionHooksTrust {
473        /// The request identifier.
474        request_id: String,
475        /// The identifier.
476        id: String,
477        /// The expected digest.
478        expected_digest: String,
479    },
480    /// Selects the probe git credential case.
481    ProbeGitCredential {
482        /// The request identifier.
483        request_id: String,
484        /// The target.
485        target: String,
486    },
487    /// Selects the approve git credential case.
488    ApproveGitCredential {
489        /// The request identifier.
490        request_id: String,
491        /// The target.
492        target: String,
493        /// The username.
494        username: String,
495        /// The token.
496        token: String,
497    },
498    /// Selects the list ssh identities case.
499    ListSshIdentities {
500        /// The request identifier.
501        request_id: String,
502    },
503    /// Selects the generate ssh identity case.
504    GenerateSshIdentity {
505        /// The request identifier.
506        request_id: String,
507    },
508    /// Selects the get git diff case.
509    GetGitDiff {
510        /// The request identifier.
511        request_id: String,
512        /// The session identifier.
513        session_id: String,
514        /// The scope.
515        scope: GitDiffScope,
516    },
517    /// Selects the switch git branch case.
518    SwitchGitBranch {
519        /// The request identifier.
520        request_id: String,
521        /// The session identifier.
522        session_id: String,
523        /// The branch.
524        branch: String,
525    },
526    /// Selects the list directories case.
527    ListDirectories {
528        /// The request identifier.
529        request_id: String,
530        /// The path.
531        path: PathBuf,
532        /// The include files.
533        include_files: bool,
534    },
535    /// Selects the list workspace files case.
536    ListWorkspaceFiles {
537        /// The request identifier.
538        request_id: String,
539        /// The session identifier.
540        session_id: String,
541        /// The scope.
542        scope: WorkspaceFileScope,
543    },
544    /// Selects the read workspace file case.
545    ReadWorkspaceFile {
546        /// The request identifier.
547        request_id: String,
548        /// The session identifier.
549        session_id: String,
550        /// The path.
551        path: String,
552        /// The offset.
553        offset: u64,
554        /// The max bytes.
555        max_bytes: usize,
556    },
557    /// Deletes a file inside the selected workspace.
558    DeleteWorkspaceFile {
559        /// Request correlation identifier.
560        request_id: String,
561        /// Selected project chat.
562        session_id: String,
563        /// Relative regular file path.
564        path: String,
565    },
566    /// Writes one workspace text file.
567    WriteWorkspaceFile {
568        /// The request identifier.
569        request_id: String,
570        /// The session identifier.
571        session_id: String,
572        /// The path.
573        path: String,
574        /// The content.
575        content: String,
576    },
577    /// Selects the clear provider credential case.
578    ClearProviderCredential {
579        /// The request identifier.
580        request_id: String,
581        /// The instance.
582        instance: String,
583    },
584    /// Selects the set provider credential case.
585    SetProviderCredential {
586        /// The request identifier.
587        request_id: String,
588        /// The instance.
589        instance: String,
590        /// The provider.
591        provider: String,
592        /// The API key.
593        api_key: String,
594        /// The expires at.
595        expires_at: Option<u64>,
596    },
597    /// Selects the set provider endpoint credential case.
598    SetProviderEndpointCredential {
599        /// The request identifier.
600        request_id: String,
601        /// The instance.
602        instance: String,
603        /// The provider.
604        provider: String,
605        /// The base URL.
606        base_url: String,
607        /// The API key.
608        api_key: String,
609        /// The expires at.
610        expires_at: Option<u64>,
611    },
612    /// Selects the register provider case.
613    RegisterProvider {
614        /// The request identifier.
615        request_id: String,
616        /// The config.
617        config: ProviderConfig,
618        /// The label.
619        label: String,
620        /// The tint.
621        tint: ProviderTint,
622        /// The model identifiers.
623        model_ids: Vec<String>,
624        /// The reasoning efforts.
625        reasoning_efforts: Vec<String>,
626    },
627    /// Selects the remove provider case.
628    RemoveProvider {
629        /// The request identifier.
630        request_id: String,
631        /// The instance.
632        instance: String,
633    },
634    /// Selects the create pairing code case.
635    CreatePairingCode {
636        /// The request identifier.
637        request_id: String,
638    },
639    /// Selects the start provider login case.
640    StartProviderLogin {
641        /// The request identifier.
642        request_id: String,
643        /// The provider.
644        provider: String,
645    },
646    /// Selects the get profile case.
647    GetProfile {
648        /// The request identifier.
649        request_id: String,
650        /// The include provider usage.
651        include_provider_usage: bool,
652    },
653    /// Create a validated routine definition.
654    CreateRoutine {
655        /// Request identity.
656        request_id: String,
657        /// Owning Bot.
658        bot_id: String,
659        /// Editable content and exact bindings.
660        definition: RoutineDefinition,
661    },
662    /// List the current routine definitions.
663    ListRoutines {
664        /// Request identity.
665        request_id: String,
666        /// Optional owning Bot.
667        bot_id: Option<String>,
668    },
669    /// Apply the same typed command used by timers and hooks.
670    RoutineCommand {
671        /// Request and command identity.
672        request_id: String,
673        /// Target and action.
674        command: super::RoutineCommand,
675    },
676    /// Selects the list routine history case.
677    ListRoutineHistory {
678        /// The request identifier.
679        request_id: String,
680        /// The identifier.
681        id: Option<String>,
682    },
683    /// Selects the delete routine run case.
684    DeleteRoutineRun {
685        /// The request identifier.
686        request_id: String,
687        /// The identifier.
688        id: String,
689    },
690    /// Selects the get routine run preview case.
691    GetRoutineRunPreview {
692        /// The request identifier.
693        request_id: String,
694        /// The identifier.
695        id: String,
696        /// The before sequence.
697        before_sequence: Option<u64>,
698    },
699}
700
701/// One gateway-to-client frame.
702#[derive(Debug, Clone, PartialEq, Serialize)]
703pub struct ServerFrame {
704    /// The version.
705    pub version: u16,
706    #[serde(flatten)]
707    /// The message.
708    pub message: ServerMessage,
709}
710
711impl<'de> Deserialize<'de> for ServerFrame {
712    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
713    where
714        D: serde::Deserializer<'de>,
715    {
716        let (version, message) = deserialize_frame(deserializer)?;
717        let message = serde_json::from_value(message).map_err(D::Error::custom)?;
718        Ok(Self { version, message })
719    }
720}
721
722impl ServerFrame {
723    /// Wraps a message in the current protocol version.
724    #[must_use]
725    pub const fn new(message: ServerMessage) -> Self {
726        Self {
727            version: PROTOCOL_VERSION,
728            message,
729        }
730    }
731}
732
733/// Results and broadcasts emitted by the gateway.
734#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
735#[serde(tag = "type", rename_all = "snake_case")]
736#[non_exhaustive]
737pub enum ServerMessage {
738    /// Acknowledges showing the gateway-owned computer.
739    ComputerOpened {
740        /// The request identifier.
741        request_id: String,
742        /// The chat whose browser tab was shown.
743        session_id: String,
744    },
745    /// Acknowledges opening or closing this connection's desktop stream.
746    DesktopStreamState {
747        /// The request identifier.
748        request_id: String,
749        /// Whether the stream is open.
750        enabled: bool,
751    },
752    /// Ordered RFB bytes from the gateway desktop.
753    DesktopData {
754        /// The raw RFB data, limited to [`MAX_DESKTOP_CHUNK_BYTES`].
755        #[serde(with = "desktop_bytes")]
756        data: Vec<u8>,
757    },
758    /// The gateway's current desktop lease, relative to this connection.
759    DesktopControlState {
760        /// The acknowledged request, or absent for a broadcast update.
761        request_id: Option<String>,
762        /// Whether execution is held for pending or granted human control.
763        enabled: bool,
764        /// Whether this connection owns control.
765        is_owner: bool,
766        /// The controlling chat, or the latest agent chat when watching.
767        session_id: Option<String>,
768    },
769    /// A committed source changed; clients refresh their existing snapshots.
770    HookEvent {
771        /// Committed typed lifecycle fact.
772        event: super::HookEvent,
773    },
774    /// Fresh measured storage usage.
775    StorageUsage {
776        /// Correlates this response with its request.
777        request_id: String,
778        /// Measured storage ownership and totals.
779        usage: crate::storage_usage::StorageUsage,
780    },
781    /// Collector configuration and delivery state.
782    Telemetry {
783        /// Correlates this response with its request.
784        request_id: String,
785        /// Current collector configuration revision.
786        revision: u64,
787        /// Configured collectors.
788        sinks: Vec<crate::telemetry::TelemetrySinkReport>,
789    },
790    /// Requests the local renderer's assigned page for one chat.
791    BrowserPageRequested {
792        /// The request identifier.
793        request_id: String,
794        /// The chat identifier.
795        session_id: String,
796        /// Whether the human explicitly asked to activate this page.
797        foreground: bool,
798    },
799    /// Selects the desktop control requested case.
800    DesktopControlRequested {
801        /// The request identifier.
802        request_id: String,
803        /// The execution identifier.
804        execution_id: String,
805        /// The session identifier.
806        session_id: String,
807        /// The request.
808        request: Value,
809    },
810    /// Selects the desktop control ended case.
811    DesktopControlEnded {
812        /// The execution identifier.
813        execution_id: String,
814    },
815    /// Selects the paired case.
816    Paired {
817        /// The client identifier.
818        client_id: String,
819        /// The token.
820        token: String,
821    },
822    /// Selects the authenticated case.
823    Authenticated,
824    /// Selects the ready case.
825    Ready {
826        /// The payload.
827        payload: ReadyPayload,
828    },
829    /// Selects the session opened case.
830    SessionOpened {
831        /// The request identifier.
832        request_id: String,
833        /// The payload.
834        payload: SessionReadyPayload,
835    },
836    /// Selects the session replay complete case.
837    SessionReplayComplete {
838        /// The request identifier.
839        request_id: String,
840        /// The session identifier.
841        session_id: String,
842    },
843    /// Selects the session history case.
844    SessionHistory {
845        /// The request identifier.
846        request_id: String,
847        /// The session identifier.
848        session_id: String,
849        /// The records.
850        records: Vec<RecordedEvent>,
851        /// The next before sequence.
852        next_before_sequence: Option<u64>,
853    },
854    /// Selects the session changed case.
855    SessionChanged {
856        /// The payload.
857        payload: SessionReadyPayload,
858    },
859    /// Selects the realtime voice started case.
860    RealtimeVoiceStarted {
861        /// The request identifier.
862        request_id: String,
863        /// The session identifier.
864        session_id: String,
865        /// The voice identifier.
866        voice_id: String,
867        /// The answer sdp.
868        answer_sdp: String,
869    },
870    /// Selects the realtime voice failed case.
871    RealtimeVoiceFailed {
872        /// The request identifier.
873        request_id: String,
874        /// The session identifier.
875        session_id: String,
876        /// The message.
877        message: String,
878    },
879    /// Selects the realtime voice ended case.
880    RealtimeVoiceEnded {
881        /// The session identifier.
882        session_id: String,
883        /// The voice identifier.
884        voice_id: String,
885        /// The reason.
886        reason: Option<String>,
887    },
888    /// Selects the gateway configured case.
889    GatewayConfigured {
890        /// The request identifier.
891        request_id: String,
892        /// The payload.
893        payload: ReadyPayload,
894    },
895    /// Selects the contributions case.
896    Contributions {
897        /// The request identifier.
898        request_id: String,
899        /// The contributions.
900        contributions: Vec<FrontendContribution>,
901    },
902    /// Selects the accepted case.
903    Accepted {
904        /// The request identifier.
905        request_id: String,
906    },
907    /// Selects the session file upload ready case.
908    SessionFileUploadReady {
909        /// The request identifier.
910        request_id: String,
911        /// The session identifier.
912        session_id: String,
913        /// The upload identifier.
914        upload_id: String,
915        /// The max chunk bytes.
916        max_chunk_bytes: usize,
917    },
918    /// Selects the session file upload chunk accepted case.
919    SessionFileUploadChunkAccepted {
920        /// The request identifier.
921        request_id: String,
922        /// The session identifier.
923        session_id: String,
924        /// The upload identifier.
925        upload_id: String,
926        /// The next offset.
927        next_offset: u64,
928    },
929    /// Selects the session file upload completed case.
930    SessionFileUploadCompleted {
931        /// The request identifier.
932        request_id: String,
933        /// The session identifier.
934        session_id: String,
935        /// The file.
936        file: SessionFileReference,
937    },
938    /// Selects the session files case.
939    SessionFiles {
940        /// The request identifier.
941        request_id: String,
942        /// The session identifier.
943        session_id: String,
944        /// The files.
945        files: Vec<SessionFileRecord>,
946    },
947    /// Selects the session file chunk case.
948    SessionFileChunk {
949        /// The request identifier.
950        request_id: String,
951        /// The session identifier.
952        session_id: String,
953        /// The file identifier.
954        file_id: String,
955        /// The offset.
956        offset: u64,
957        #[serde(with = "base64_bytes")]
958        /// The data.
959        data: Vec<u8>,
960        /// The next offset.
961        next_offset: Option<u64>,
962    },
963    /// Selects the rejected case.
964    Rejected {
965        /// The request identifier.
966        request_id: String,
967        /// The code.
968        code: String,
969        /// The message.
970        message: String,
971        /// The fatal.
972        fatal: bool,
973    },
974    /// Selects the agent event case.
975    AgentEvent {
976        /// The session identifier.
977        session_id: String,
978        /// The record.
979        record: RecordedEvent,
980    },
981    /// Selects the sessions case.
982    Sessions {
983        #[serde(default, skip_serializing_if = "Option::is_none")]
984        /// The request identifier.
985        request_id: Option<String>,
986        /// The sessions.
987        sessions: Vec<SessionRecord>,
988    },
989    /// Selects the background approvals case.
990    BackgroundApprovals {
991        /// The approvals.
992        approvals: Vec<BackgroundApproval>,
993    },
994    /// The whole visible session catalog, in order, relative to the catalog this
995    /// connection last received; unsolicited catalog updates arrive only in this form.
996    SessionsChanged {
997        /// Every session of the catalog, unchanged ones by ID.
998        sessions: Vec<SessionSlot>,
999        /// The new catalog's revision.
1000        revision: String,
1001    },
1002
1003    /// Selects the bot sessions case.
1004    BotSessions {
1005        /// The request identifier.
1006        request_id: String,
1007        /// The bot identifier.
1008        bot_id: String,
1009        /// The sessions.
1010        sessions: Vec<SessionRecord>,
1011    },
1012    /// Selects the bots case.
1013    Bots {
1014        #[serde(default, skip_serializing_if = "Option::is_none")]
1015        /// The request identifier.
1016        request_id: Option<String>,
1017        /// The bots.
1018        bots: Vec<BotRecord>,
1019    },
1020
1021    /// Selects the clients case.
1022    Clients {
1023        /// The request identifier.
1024        request_id: String,
1025        /// The current client identifier.
1026        current_client_id: String,
1027        /// The clients.
1028        clients: Vec<ClientStatus>,
1029    },
1030    /// Selects the provider credential cleared case.
1031    ProviderCredentialCleared {
1032        /// The request identifier.
1033        request_id: String,
1034        /// The instance.
1035        instance: String,
1036    },
1037    /// Selects the provider credential saved case.
1038    ProviderCredentialSaved {
1039        /// The request identifier.
1040        request_id: String,
1041        /// The instance.
1042        instance: String,
1043        /// The provider.
1044        provider: String,
1045    },
1046    /// Selects the pairing code case.
1047    PairingCode {
1048        /// The request identifier.
1049        request_id: String,
1050        /// The code.
1051        code: String,
1052        /// The expires at.
1053        expires_at: i64,
1054    },
1055    /// Selects the provider login started case.
1056    ProviderLoginStarted {
1057        /// The request identifier.
1058        request_id: String,
1059        /// The login identifier.
1060        login_id: String,
1061        /// The provider.
1062        provider: String,
1063        /// The verification URL.
1064        verification_url: String,
1065        /// The user code.
1066        user_code: String,
1067    },
1068    /// Selects the provider login finished case.
1069    ProviderLoginFinished {
1070        /// The request identifier.
1071        request_id: String,
1072        /// The login identifier.
1073        login_id: String,
1074        /// The provider.
1075        provider: String,
1076    },
1077    /// Selects the git credential status case.
1078    GitCredentialStatus {
1079        /// The request identifier.
1080        request_id: String,
1081        /// The available.
1082        available: bool,
1083        #[serde(skip_serializing_if = "Option::is_none")]
1084        /// The username.
1085        username: Option<String>,
1086    },
1087    /// Selects the ssh identities case.
1088    SshIdentities {
1089        /// The request identifier.
1090        request_id: String,
1091        /// The identities.
1092        identities: Vec<SshIdentityRecord>,
1093    },
1094    /// Selects the ssh identity generated case.
1095    SshIdentityGenerated {
1096        /// The request identifier.
1097        request_id: String,
1098        /// The identity.
1099        identity: SshIdentityRecord,
1100        /// The public key.
1101        public_key: String,
1102    },
1103    /// Selects the profile case.
1104    Profile {
1105        /// The request identifier.
1106        request_id: String,
1107        /// The profile.
1108        profile: ProfileSnapshot,
1109    },
1110    /// Selects the git diff case.
1111    GitDiff {
1112        /// The request identifier.
1113        request_id: String,
1114        /// The session identifier.
1115        session_id: String,
1116        /// The scope.
1117        scope: GitDiffScope,
1118        /// The diff.
1119        diff: String,
1120    },
1121    /// Line totals of a Git diff.
1122    GitDiffTotals {
1123        /// The request identifier.
1124        request_id: String,
1125        /// The session identifier.
1126        session_id: String,
1127        /// The scope.
1128        scope: GitDiffScope,
1129        /// The totals.
1130        totals: DiffTotals,
1131    },
1132    /// Selects the directories case.
1133    Directories {
1134        /// The request identifier.
1135        request_id: String,
1136        /// The listing.
1137        listing: DirectoryListing,
1138    },
1139    /// Selects the workspace files case.
1140    WorkspaceFiles {
1141        /// The request identifier.
1142        request_id: String,
1143        /// The session identifier.
1144        session_id: String,
1145        /// The files.
1146        files: Vec<WorkspaceFileRecord>,
1147        /// The truncated.
1148        truncated: bool,
1149    },
1150    /// Selects the workspace file chunk case.
1151    WorkspaceFileChunk {
1152        /// The request identifier.
1153        request_id: String,
1154        /// The session identifier.
1155        session_id: String,
1156        /// The path.
1157        path: String,
1158        /// The offset.
1159        offset: u64,
1160        #[serde(with = "base64_bytes")]
1161        /// The data.
1162        data: Vec<u8>,
1163        /// The next offset.
1164        next_offset: Option<u64>,
1165    },
1166    /// Selects the routines case.
1167    Routines {
1168        /// The request identifier.
1169        request_id: String,
1170        /// The routines.
1171        routines: Vec<Routine>,
1172    },
1173    /// Selects the routine history case.
1174    RoutineHistory {
1175        /// The request identifier.
1176        request_id: String,
1177        /// The runs.
1178        runs: Vec<RoutineRun>,
1179    },
1180    /// Selects the routine run preview case.
1181    RoutineRunPreview {
1182        /// The request identifier.
1183        request_id: String,
1184        /// The preview.
1185        preview: RoutineRunPreview,
1186    },
1187    /// Selects the error case.
1188    Error {
1189        /// The code.
1190        code: String,
1191        /// The message.
1192        message: String,
1193        /// The fatal.
1194        fatal: bool,
1195    },
1196}
1197
1198/// One gateway response failure with its connection-level terminality preserved.
1199#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1200pub struct GatewayResponseError<'a> {
1201    /// The message.
1202    pub message: &'a str,
1203    /// The fatal.
1204    pub fatal: bool,
1205}
1206
1207impl ServerMessage {
1208    /// Returns the error which terminates a wait for `request_id`, if any.
1209    ///
1210    /// A correlated rejection terminates that request. An uncorrelated global error
1211    /// only terminates a wait when it also terminates the connection.
1212    #[must_use]
1213    pub fn response_error(&self, request_id: Option<&str>) -> Option<GatewayResponseError<'_>> {
1214        match self {
1215            Self::Rejected {
1216                request_id: actual,
1217                message,
1218                fatal,
1219                ..
1220            } if request_id == Some(actual.as_str()) => Some(GatewayResponseError {
1221                message,
1222                fatal: *fatal,
1223            }),
1224            Self::Error {
1225                message,
1226                fatal: true,
1227                ..
1228            } => Some(GatewayResponseError {
1229                message,
1230                fatal: true,
1231            }),
1232            _ => None,
1233        }
1234    }
1235}