1use super::*;
2
3#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
5pub struct ReadyPayload {
6 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#[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#[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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55pub struct SessionWidget {
56 pub capability: String,
57 pub item: FrontendWidget,
58}
59
60#[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#[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#[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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
114pub struct WorkspaceInfo {
115 pub id: String,
116 pub path: PathBuf,
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
121pub struct GitStatus {
122 pub current_branch: String,
123 pub branches: Vec<String>,
124}
125
126#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
146#[serde(rename_all = "snake_case")]
147pub enum WorkspaceFileScope {
148 Modified,
149 All,
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154pub struct WorkspaceFileRecord {
155 pub path: String,
156 pub size: u64,
157}
158
159#[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#[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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
177pub struct VersionedAgentConfig {
178 pub revision: u64,
179 pub config: AgentComposition,
180}
181
182#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
196#[serde(rename_all = "snake_case")]
197pub enum ExtensionKind {
198 Skill,
199 Plugin,
200}
201
202#[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#[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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
234#[serde(deny_unknown_fields)]
235pub struct ProviderConfig {
236 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
251#[serde(rename_all = "snake_case")]
252pub enum ProviderEndpointAuth {
253 ProviderDefault,
254 Credentialless,
255}
256
257#[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#[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#[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#[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#[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#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
368#[serde(rename_all = "snake_case")]
369pub enum ProviderAuthKind {
370 ApiKey,
371 DeviceCode,
372}
373
374#[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 #[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 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 #[must_use]
431 pub fn enabled(&self, id: &str) -> bool {
432 self.enabled.contains(id)
433 }
434
435 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 #[must_use]
447 pub fn setting(&self, middleware: &str, setting: &str) -> Option<&FrontendSettingValue> {
448 self.settings.get(middleware)?.get(setting)
449 }
450
451 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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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}