Skip to main content

mobius_gateway/wire/
records.rs

1use super::*;
2
3/// Gateway-wide frontend-safe state sent after authentication.
4#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
5pub struct ReadyPayload {
6    /// Release version of the connected gateway process.
7    pub gateway_version: String,
8    pub machine_name: String,
9    pub bots: Vec<BotRecord>,
10    pub sessions: Vec<SessionRecord>,
11    pub background_approvals: Vec<BackgroundApproval>,
12    pub providers: Vec<ProviderStatus>,
13    pub provider_instances: Vec<ProviderInstance>,
14    pub bot_defaults: Option<VersionedAgentConfig>,
15    pub models: Vec<ModelChoice>,
16    pub model_providers: BTreeMap<String, String>,
17    pub middleware_features: Vec<MiddlewareFeature>,
18    pub extensions: Vec<ExtensionRecord>,
19    pub contributions: Vec<FrontendContribution>,
20    pub max_active_sessions: usize,
21    pub session_file_limits: SessionFileLimits,
22}
23
24/// One hidden Bot conversation currently waiting for a human execution decision.
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26pub struct BackgroundApproval {
27    pub session_id: String,
28    pub bot_id: String,
29    pub turn_id: String,
30    pub request_id: String,
31}
32
33/// Frontend-safe state for one opened session.
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
35pub struct SessionReadyPayload {
36    pub member_bot_ids: Option<Vec<String>>,
37    pub active_turn_ids: Vec<String>,
38    pub pending_approvals: Vec<mobius::protocol::ExecApprovalRequestEvent>,
39    pub latest_sequence: u64,
40    pub next_before_sequence: Option<u64>,
41    pub workspace: WorkspaceInfo,
42    pub attached_folders: Vec<PathBuf>,
43    pub git: Option<GitStatus>,
44    pub session: SessionConfiguredEvent,
45    pub contributions: Vec<FrontendContribution>,
46    pub widgets: Vec<SessionWidget>,
47    pub tool_count: usize,
48    pub compaction_count: u64,
49    pub context_limit_tokens: Option<i64>,
50    pub run_stats: RunStats,
51}
52
53/// One currently mounted capability widget and its owning namespace.
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55pub struct SessionWidget {
56    pub capability: String,
57    pub item: FrontendWidget,
58}
59
60/// One visible session with gateway-owned catalog presentation metadata.
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62pub struct SessionRecord {
63    pub member_bot_ids: Option<Vec<String>>,
64    pub session_id: String,
65    pub session_context: mobius::protocol::SessionContext,
66    pub parent_session_id: Option<String>,
67    pub parent_sequence: Option<u64>,
68    pub sequence: u64,
69    pub first_user_message: Option<String>,
70    pub execution_stats: mobius::backend::checkpoint::ExecutionStats,
71    pub title: Option<String>,
72    pub pinned: bool,
73    pub activity: SessionActivity,
74    pub created_at: i64,
75    pub updated_at: i64,
76}
77
78/// Gateway-observed lifecycle state for one session.
79#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
80#[serde(deny_unknown_fields)]
81pub struct SessionActivity {
82    pub state: SessionActivityState,
83    pub turn_id: Option<String>,
84    pub approval_request_id: Option<String>,
85    pub started_at: Option<i64>,
86    pub last_outcome: Option<mobius::backend::checkpoint::ExecutionOutcome>,
87    pub message: Option<String>,
88}
89
90impl Default for SessionActivity {
91    fn default() -> Self {
92        Self {
93            state: SessionActivityState::Idle,
94            turn_id: None,
95            approval_request_id: None,
96            started_at: None,
97            last_outcome: None,
98            message: None,
99        }
100    }
101}
102
103/// Current work state advertised in the session catalog.
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
105#[serde(rename_all = "snake_case")]
106pub enum SessionActivityState {
107    Idle,
108    Running,
109    AwaitingApproval,
110}
111
112/// Canonical workspace identity and path for one chat.
113#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
114pub struct WorkspaceInfo {
115    pub id: String,
116    pub path: PathBuf,
117}
118
119/// Local branch state for a Git-backed workspace.
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
121pub struct GitStatus {
122    pub current_branch: String,
123    pub branches: Vec<String>,
124}
125
126/// Public metadata for one SSH identity found on the gateway host.
127#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
128#[serde(deny_unknown_fields)]
129pub struct SshIdentityRecord {
130    pub label: String,
131    pub algorithm: String,
132    pub fingerprint: String,
133}
134
135/// One explicit Git patch selection.
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
137#[serde(rename_all = "snake_case")]
138pub enum GitDiffScope {
139    Staged,
140    Unstaged,
141    Committed,
142}
143
144/// Which openable files to include in a workspace catalog.
145#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
146#[serde(rename_all = "snake_case")]
147pub enum WorkspaceFileScope {
148    Modified,
149    All,
150}
151
152/// One regular file confined to the selected chat workspace.
153#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154pub struct WorkspaceFileRecord {
155    pub path: String,
156    pub size: u64,
157}
158
159/// One bounded folder listing from the gateway host.
160#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
161pub struct DirectoryListing {
162    pub path: PathBuf,
163    pub parent: Option<PathBuf>,
164    pub entries: Vec<DirectoryEntry>,
165}
166
167/// A selectable child folder on the gateway host.
168#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
169pub struct DirectoryEntry {
170    pub name: String,
171    pub path: PathBuf,
172    pub is_directory: bool,
173}
174
175/// A frontend-safe agent composition guarded by an optimistic revision.
176#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
177pub struct VersionedAgentConfig {
178    pub revision: u64,
179    pub config: AgentComposition,
180}
181
182/// Runtime settings an authenticated client may read and replace atomically.
183#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
184#[serde(deny_unknown_fields)]
185pub struct AgentComposition {
186    pub provider: ProviderConfig,
187    pub realtime_voice: Option<String>,
188    pub middleware: MiddlewareConfig,
189    pub extensions: BTreeSet<String>,
190    pub system_prompt: String,
191    pub max_model_steps: u64,
192}
193
194/// Package format of one gateway-managed extension.
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
196#[serde(rename_all = "snake_case")]
197pub enum ExtensionKind {
198    Skill,
199    Plugin,
200}
201
202/// One executable plugin hook shown before digest-bound trust is granted.
203#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
204#[serde(deny_unknown_fields)]
205pub struct ExtensionHookRecord {
206    pub event: String,
207    pub matcher: Option<String>,
208    pub command: String,
209    pub timeout_seconds: u64,
210}
211
212/// Frontend-safe metadata for one installed extension snapshot.
213#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
214#[serde(deny_unknown_fields)]
215pub struct ExtensionRecord {
216    pub id: String,
217    pub capability: String,
218    pub kind: ExtensionKind,
219    pub name: String,
220    pub description: String,
221    pub version: Option<String>,
222    pub source: String,
223    pub reference: Option<String>,
224    pub subdirectory: Option<String>,
225    pub resolved_revision: String,
226    pub digest: String,
227    pub skills: Vec<String>,
228    pub hooks: Vec<ExtensionHookRecord>,
229    pub hooks_trusted: bool,
230}
231
232/// Provider and model settings. Credentials are resolved only on the gateway host.
233#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
234#[serde(deny_unknown_fields)]
235pub struct ProviderConfig {
236    /// Stable identity of one configured setup of `provider`. A gateway may hold
237    /// several instances of the same provider with separate credentials.
238    pub instance: String,
239    pub provider: String,
240    pub model: String,
241    #[serde(default, skip_serializing_if = "Option::is_none")]
242    pub base_url: Option<String>,
243    pub endpoint_auth: ProviderEndpointAuth,
244    #[serde(default, skip_serializing_if = "Option::is_none")]
245    pub reasoning_effort: Option<String>,
246    pub web_search: HostedWebSearch,
247}
248
249/// Authentication applied when calling one configured provider endpoint.
250#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
251#[serde(rename_all = "snake_case")]
252pub enum ProviderEndpointAuth {
253    ProviderDefault,
254    Credentialless,
255}
256
257/// Credential availability exposed without returning credential material.
258#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
259pub struct ProviderStatus {
260    pub provider: String,
261    pub label: String,
262    pub symbol: FrontendSymbol,
263    pub description: String,
264    pub model_ids_configurable: bool,
265    pub auth: ProviderAuthKind,
266    pub default_base_url: Option<String>,
267    pub default_api_key_env: Option<String>,
268    pub models: Vec<ProviderModel>,
269    pub web_search: Vec<FrontendSettingOption>,
270    pub tool_discovery: ToolDiscoveryMode,
271    pub custom_endpoint_tool_discovery: Option<ToolDiscoveryMode>,
272    pub realtime_voices: Vec<String>,
273}
274
275/// User-chosen accent for distinguishing provider instances in model selectors.
276#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
277#[serde(rename_all = "snake_case")]
278pub enum ProviderTint {
279    #[default]
280    Blue,
281    Teal,
282    Green,
283    Yellow,
284    Orange,
285    Red,
286    Purple,
287}
288
289/// One durable setup of a provider. Several may share one `provider`.
290#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
291pub struct ProviderInstance {
292    pub label: String,
293    pub tint: ProviderTint,
294    pub configured: bool,
295    #[serde(default, skip_serializing_if = "Option::is_none")]
296    pub credential_hint: Option<String>,
297    pub selection: ProviderConfig,
298    pub model_ids: Vec<String>,
299    pub reasoning_efforts: Vec<String>,
300}
301
302/// Frontend type attached to one authenticated connection.
303#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
304#[serde(rename_all = "snake_case")]
305pub enum ClientKind {
306    Cli,
307    Macos,
308    Ios,
309    Ipados,
310    GatewayDashboard,
311}
312
313/// One paired client and its current connection state.
314#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
315pub struct ClientStatus {
316    pub client_id: String,
317    pub label: String,
318    pub kinds: Vec<ClientKind>,
319    pub connections: usize,
320}
321
322impl ProviderStatus {
323    #[must_use]
324    pub fn realtime_voices(&self, base_url: Option<&str>) -> &[String] {
325        if mobius::backend::model::provider::uses_default_endpoint(
326            self.default_base_url.as_deref(),
327            base_url,
328        ) {
329            &self.realtime_voices
330        } else {
331            &[]
332        }
333    }
334
335    #[must_use]
336    pub fn configurable_base_url(&self) -> bool {
337        self.default_base_url.is_some()
338    }
339
340    #[must_use]
341    pub fn default_model(&self) -> Option<&ProviderModel> {
342        self.models.first()
343    }
344}
345
346/// One model advertised by a provider manifest.
347#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
348pub struct ProviderModel {
349    pub id: String,
350    pub label: String,
351    pub description: String,
352    pub context_window: i64,
353    pub reasoning: Vec<ReasoningChoice>,
354    pub default_reasoning: Option<String>,
355    pub tool_discovery: ToolDiscoveryMode,
356}
357
358/// One reasoning effort advertised for a provider model.
359#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
360pub struct ReasoningChoice {
361    pub id: String,
362    pub label: String,
363    pub description: String,
364}
365
366/// Frontend-safe provider authentication mechanism.
367#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
368#[serde(rename_all = "snake_case")]
369pub enum ProviderAuthKind {
370    ApiKey,
371    DeviceCode,
372}
373
374/// Enabled optional middleware IDs and their schema-backed settings.
375#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
376#[serde(deny_unknown_fields)]
377pub struct MiddlewareConfig {
378    pub(crate) enabled: BTreeSet<String>,
379    pub settings: BTreeMap<String, BTreeMap<String, FrontendSettingValue>>,
380}
381
382impl MiddlewareConfig {
383    /// Returns the selected policy that excludes an optional capability.
384    #[must_use]
385    pub fn disabled_by<'a>(
386        &self,
387        features: &'a [mobius::protocol::MiddlewareFeature],
388        id: &str,
389    ) -> Option<&'a str> {
390        features
391            .iter()
392            .filter(|feature| feature.required || self.enabled(&feature.id))
393            .find_map(|feature| {
394                feature.settings.iter().find_map(|setting| {
395                    let mobius::protocol::FrontendSettingKind::Select { options, .. } =
396                        &setting.kind
397                    else {
398                        return None;
399                    };
400                    let Some(FrontendSettingValue::String(value)) =
401                        self.setting(&feature.id, &setting.id)
402                    else {
403                        return None;
404                    };
405                    options
406                        .iter()
407                        .find(|option| {
408                            option.value == *value
409                                && option.disables.iter().any(|disabled| disabled == id)
410                        })
411                        .map(|option| option.label.as_str())
412                })
413            })
414    }
415
416    /// Applies exclusions advertised by the currently selected policies.
417    pub fn reconcile(&mut self, features: &[mobius::protocol::MiddlewareFeature]) {
418        let excluded = self
419            .enabled
420            .iter()
421            .filter(|id| self.disabled_by(features, id).is_some())
422            .cloned()
423            .collect::<Vec<_>>();
424        for id in excluded {
425            self.enabled.remove(&id);
426        }
427    }
428
429    /// Returns whether one advertised optional middleware is enabled.
430    #[must_use]
431    pub fn enabled(&self, id: &str) -> bool {
432        self.enabled.contains(id)
433    }
434
435    /// Updates one advertised optional middleware before gateway validation.
436    pub fn set_enabled(&mut self, id: impl Into<String>, enabled: bool) {
437        let id = id.into();
438        if enabled {
439            self.enabled.insert(id);
440        } else {
441            self.enabled.remove(&id);
442        }
443    }
444
445    /// Returns one advertised middleware setting.
446    #[must_use]
447    pub fn setting(&self, middleware: &str, setting: &str) -> Option<&FrontendSettingValue> {
448        self.settings.get(middleware)?.get(setting)
449    }
450
451    /// Sets or clears one advertised middleware setting before gateway validation.
452    pub fn set_setting(
453        &mut self,
454        middleware: impl Into<String>,
455        setting: impl Into<String>,
456        value: Option<FrontendSettingValue>,
457    ) {
458        let middleware = middleware.into();
459        let setting = setting.into();
460        if let Some(value) = value {
461            self.settings
462                .entry(middleware)
463                .or_default()
464                .insert(setting, value);
465        } else if let Some(settings) = self.settings.get_mut(&middleware) {
466            settings.remove(&setting);
467            if settings.is_empty() {
468                self.settings.remove(&middleware);
469            }
470        }
471    }
472
473    pub(crate) fn entries(&self) -> impl Iterator<Item = &str> {
474        self.enabled.iter().map(String::as_str)
475    }
476}
477
478/// Capability-rendered preview whose inner events remain provider-neutral.
479#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
480pub struct RenderedPreview {
481    pub id: String,
482    pub title: String,
483    pub subtitle: String,
484    pub page_id: String,
485    pub update: FrontendPreviewUpdate,
486    pub events: Vec<RenderedEvent>,
487    pub next: Option<Op>,
488}
489
490/// One preview event and its capability-rendered blocks.
491#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
492pub struct RenderedEvent {
493    pub submission_id: Option<String>,
494    pub recorded_at_ms: i64,
495    pub event: EventMsg,
496    pub blocks: Vec<RenderedBlock>,
497}
498
499/// One timestamped semantic event and its deterministic presentation.
500#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
501pub struct RecordedEvent {
502    pub sequence: u64,
503    pub recorded_at_ms: i64,
504    pub event: Event,
505    pub stream_metrics: Vec<StreamMetrics>,
506    pub blocks: Vec<RenderedBlock>,
507    pub preview: Option<RenderedPreview>,
508}
509
510/// Gateway-owned profile and aggregate usage information.
511#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
512pub struct ProfileSnapshot {
513    pub user_name: Option<String>,
514    pub daily_usage: Vec<DailyUsage>,
515    pub provider_usage: Vec<ProviderUsage>,
516    pub run_stats: RunStats,
517    pub recent_run_groups: Vec<SessionRunGroup>,
518}
519
520/// One configured provider's remote subscription usage.
521#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
522pub struct ProviderUsage {
523    pub provider: String,
524    pub limits: Option<Vec<UsageLimit>>,
525    pub error: Option<String>,
526}
527
528/// Recent executions grouped under their nearest visible session.
529#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
530pub struct SessionRunGroup {
531    pub session_id: String,
532    pub title: String,
533    pub runs: Vec<RunSummary>,
534}
535
536/// Completed execution totals plus the active run, when one exists.
537#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
538pub struct RunStats {
539    #[serde(flatten)]
540    pub completed: mobius::backend::checkpoint::ExecutionStats,
541    pub active: Option<RunSummary>,
542}
543
544/// Frontend-safe summary of one completed or active user turn.
545#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
546pub struct RunSummary {
547    pub session_id: String,
548    pub submission_id: String,
549    pub turn_id: String,
550    pub started_at_ms: i64,
551    pub finished_at_ms: Option<i64>,
552    pub elapsed_ms: u64,
553    pub outcome: Option<mobius::backend::checkpoint::ExecutionOutcome>,
554    pub model_calls: u64,
555    pub tool_calls: u64,
556    pub failed_tool_calls: u64,
557    pub usage: TokenUsage,
558}
559
560/// Usage accrued during one Unix day.
561#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
562pub struct DailyUsage {
563    pub unix_day: u64,
564    pub provider: String,
565    pub usage: TokenUsage,
566}
567
568/// One durable Bot profile.
569#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
570pub struct BotRecord {
571    pub id: String,
572    pub handle: String,
573    pub name: String,
574    pub description: String,
575    pub tint: ProviderTint,
576    pub config: VersionedAgentConfig,
577}
578
579/// One Bot-owned routine.
580#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
581pub struct Routine {
582    pub id: String,
583    pub bot_id: String,
584    pub workspace: PathBuf,
585    pub instructions: String,
586    pub schedule: RoutineSchedule,
587    pub ends_at: Option<i64>,
588    pub enabled: bool,
589    pub finished: bool,
590    pub next_run_at: Option<i64>,
591}
592
593/// A user-selected scheduling rule.
594#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
595#[serde(deny_unknown_fields)]
596pub struct RoutineSchedule {
597    pub kind: RoutineScheduleKind,
598    pub at: Option<i64>,
599    pub every_seconds: Option<u64>,
600    pub expression: Option<String>,
601    pub time_zone: Option<String>,
602}
603
604/// The supported scheduling rule families.
605#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
606#[serde(rename_all = "snake_case")]
607pub enum RoutineScheduleKind {
608    Once,
609    Interval,
610    Cron,
611}
612
613/// A read-only page of a Bot routine transcript.
614#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
615pub struct RoutineRunPreview {
616    pub routine: Routine,
617    pub run: RoutineRun,
618    pub records: Vec<RecordedEvent>,
619    pub next_before_sequence: Option<u64>,
620}
621
622/// One completed or active invocation of a Bot routine.
623#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
624pub struct RoutineRun {
625    pub id: String,
626    pub routine_id: String,
627    pub bot_id: String,
628    pub started_at: i64,
629    pub finished_at: Option<i64>,
630    pub status: RoutineRunStatus,
631    pub session_id: Option<String>,
632    pub message: Option<String>,
633}
634
635/// Durable state of one routine invocation.
636#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
637#[serde(rename_all = "snake_case")]
638pub enum RoutineRunStatus {
639    Running,
640    Succeeded,
641    Failed,
642    Skipped,
643}