1use super::*;
2
3#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
5pub struct ReadyPayload {
6 pub machine_name: String,
7 pub bots: Vec<BotRecord>,
8 pub sessions: Vec<SessionRecord>,
9 pub background_approvals: Vec<BackgroundApproval>,
10 pub swarm_attentions: Vec<SwarmAttention>,
11 pub swarms: Vec<SwarmRecord>,
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, Eq, Serialize, Deserialize)]
35pub struct SwarmAttention {
36 pub swarm_id: String,
37 pub swarm_title: String,
38 pub message_id: String,
39 pub bot_id: String,
40 pub text: String,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45pub struct SwarmRecord {
46 pub id: String,
47 pub title: String,
48 pub leader_bot_id: String,
49 pub members: Vec<SwarmMemberRecord>,
50 pub messages: Vec<SwarmMessageRecord>,
51 pub updated_at_ms: i64,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56pub struct SwarmMemberRecord {
57 pub bot_id: String,
58 pub handle: String,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63pub struct SwarmMessageRecord {
64 pub id: String,
65 pub sequence: u64,
66 pub author_bot_id: String,
67 pub author_handle: String,
68 pub source_session_id: String,
69 pub text: String,
70 pub created_at_ms: i64,
71 pub in_reply_to_message_id: Option<String>,
72 pub reply_depth: u8,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77#[serde(tag = "type", rename_all = "snake_case")]
78pub enum ContributionScope {
79 Global,
80 Swarm { id: String },
81}
82
83#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
85pub struct SessionReadyPayload {
86 pub latest_sequence: u64,
87 pub next_before_sequence: Option<u64>,
88 pub workspace: WorkspaceInfo,
89 pub git: Option<GitStatus>,
90 pub session: SessionConfiguredEvent,
91 pub contributions: Vec<FrontendContribution>,
92 pub widgets: Vec<SessionWidget>,
93 pub tool_count: usize,
94 pub compaction_count: u64,
95 pub context_limit_tokens: Option<i64>,
96 pub run_stats: RunStats,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
101pub struct SessionWidget {
102 pub capability: String,
103 pub item: FrontendWidget,
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
108pub struct SessionRecord {
109 pub session_id: String,
110 pub session_context: mobius::protocol::SessionContext,
111 pub parent_session_id: Option<String>,
112 pub parent_sequence: Option<u64>,
113 pub sequence: u64,
114 pub first_user_message: Option<String>,
115 pub execution_stats: mobius::backend::checkpoint::ExecutionStats,
116 pub title: Option<String>,
117 pub pinned: bool,
118 pub activity: SessionActivity,
119 pub created_at: i64,
120 pub updated_at: i64,
121}
122
123#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
125#[serde(deny_unknown_fields)]
126pub struct SessionActivity {
127 pub state: SessionActivityState,
128 pub turn_id: Option<String>,
129 pub approval_request_id: Option<String>,
130 pub started_at: Option<i64>,
131 pub last_outcome: Option<mobius::backend::checkpoint::ExecutionOutcome>,
132 pub message: Option<String>,
133}
134
135impl Default for SessionActivity {
136 fn default() -> Self {
137 Self {
138 state: SessionActivityState::Idle,
139 turn_id: None,
140 approval_request_id: None,
141 started_at: None,
142 last_outcome: None,
143 message: None,
144 }
145 }
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
150#[serde(rename_all = "snake_case")]
151pub enum SessionActivityState {
152 Idle,
153 Running,
154 AwaitingApproval,
155}
156
157#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
159pub struct WorkspaceInfo {
160 pub id: String,
161 pub path: PathBuf,
162}
163
164#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
166pub struct GitStatus {
167 pub current_branch: String,
168 pub branches: Vec<String>,
169}
170
171#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
173#[serde(deny_unknown_fields)]
174pub struct SshIdentityRecord {
175 pub label: String,
176 pub algorithm: String,
177 pub fingerprint: String,
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
182#[serde(rename_all = "snake_case")]
183pub enum GitDiffScope {
184 Staged,
185 Unstaged,
186 Committed,
187}
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
191#[serde(rename_all = "snake_case")]
192pub enum WorkspaceFileScope {
193 Modified,
194 All,
195}
196
197#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
199pub struct WorkspaceFileRecord {
200 pub path: String,
201 pub size: u64,
202}
203
204#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
206pub struct DirectoryListing {
207 pub path: PathBuf,
208 pub parent: Option<PathBuf>,
209 pub entries: Vec<DirectoryEntry>,
210}
211
212#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
214pub struct DirectoryEntry {
215 pub name: String,
216 pub path: PathBuf,
217 pub is_directory: bool,
218}
219
220#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
222pub struct VersionedAgentConfig {
223 pub revision: u64,
224 pub config: AgentComposition,
225}
226
227#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
229#[serde(deny_unknown_fields)]
230pub struct AgentComposition {
231 pub provider: ProviderConfig,
232 pub realtime_voice: Option<String>,
233 pub middleware: MiddlewareConfig,
234 pub extensions: BTreeSet<String>,
235 pub system_prompt: String,
236 pub max_model_steps: u64,
237}
238
239#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
241#[serde(rename_all = "snake_case")]
242pub enum ExtensionKind {
243 Skill,
244 Plugin,
245}
246
247#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
249#[serde(deny_unknown_fields)]
250pub struct ExtensionHookRecord {
251 pub event: String,
252 pub matcher: Option<String>,
253 pub command: String,
254 pub timeout_seconds: u64,
255}
256
257#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
259#[serde(deny_unknown_fields)]
260pub struct ExtensionRecord {
261 pub id: String,
262 pub capability: String,
263 pub kind: ExtensionKind,
264 pub name: String,
265 pub description: String,
266 pub version: Option<String>,
267 pub source: String,
268 pub reference: Option<String>,
269 pub subdirectory: Option<String>,
270 pub resolved_revision: String,
271 pub digest: String,
272 pub skills: Vec<String>,
273 pub hooks: Vec<ExtensionHookRecord>,
274 pub hooks_trusted: bool,
275}
276
277#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
279#[serde(deny_unknown_fields)]
280pub struct ProviderConfig {
281 pub instance: String,
284 pub provider: String,
285 pub model: String,
286 #[serde(default, skip_serializing_if = "Option::is_none")]
287 pub base_url: Option<String>,
288 pub endpoint_auth: ProviderEndpointAuth,
289 #[serde(default, skip_serializing_if = "Option::is_none")]
290 pub reasoning_effort: Option<String>,
291 pub web_search: HostedWebSearch,
292}
293
294#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
296#[serde(rename_all = "snake_case")]
297pub enum ProviderEndpointAuth {
298 ProviderDefault,
299 Credentialless,
300}
301
302#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
304pub struct ProviderStatus {
305 pub provider: String,
306 pub label: String,
307 pub symbol: FrontendSymbol,
308 pub description: String,
309 pub model_ids_configurable: bool,
310 pub auth: ProviderAuthKind,
311 pub default_base_url: Option<String>,
312 pub default_api_key_env: Option<String>,
313 pub models: Vec<ProviderModel>,
314 pub web_search: Vec<FrontendSettingOption>,
315 pub tool_discovery: ToolDiscoveryMode,
316 pub custom_endpoint_tool_discovery: Option<ToolDiscoveryMode>,
317 pub realtime_voices: Vec<String>,
318}
319
320#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
322#[serde(rename_all = "snake_case")]
323pub enum ProviderTint {
324 #[default]
325 Blue,
326 Teal,
327 Green,
328 Yellow,
329 Orange,
330 Red,
331 Purple,
332}
333
334#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
336pub struct ProviderInstance {
337 pub label: String,
338 pub tint: ProviderTint,
339 pub configured: bool,
340 #[serde(default, skip_serializing_if = "Option::is_none")]
341 pub credential_hint: Option<String>,
342 pub selection: ProviderConfig,
343 pub model_ids: Vec<String>,
344 pub reasoning_efforts: Vec<String>,
345}
346
347#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
349#[serde(rename_all = "snake_case")]
350pub enum ClientKind {
351 Cli,
352 Macos,
353 Ios,
354 Ipados,
355 GatewayDashboard,
356}
357
358#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
360pub struct ClientStatus {
361 pub client_id: String,
362 pub label: String,
363 pub kinds: Vec<ClientKind>,
364 pub connections: usize,
365}
366
367impl ProviderStatus {
368 #[must_use]
369 pub fn realtime_voices(&self, base_url: Option<&str>) -> &[String] {
370 if mobius::backend::model::provider::uses_default_endpoint(
371 self.default_base_url.as_deref(),
372 base_url,
373 ) {
374 &self.realtime_voices
375 } else {
376 &[]
377 }
378 }
379
380 #[must_use]
381 pub fn configurable_base_url(&self) -> bool {
382 self.default_base_url.is_some()
383 }
384
385 #[must_use]
386 pub fn default_model(&self) -> Option<&ProviderModel> {
387 self.models.first()
388 }
389}
390
391#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
393pub struct ProviderModel {
394 pub id: String,
395 pub label: String,
396 pub description: String,
397 pub context_window: i64,
398 pub reasoning: Vec<ReasoningChoice>,
399 pub default_reasoning: Option<String>,
400 pub tool_discovery: ToolDiscoveryMode,
401}
402
403#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
405pub struct ReasoningChoice {
406 pub id: String,
407 pub label: String,
408 pub description: String,
409}
410
411#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
413#[serde(rename_all = "snake_case")]
414pub enum ProviderAuthKind {
415 ApiKey,
416 DeviceCode,
417}
418
419#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
421#[serde(deny_unknown_fields)]
422pub struct MiddlewareConfig {
423 pub(crate) enabled: BTreeSet<String>,
424 pub settings: BTreeMap<String, BTreeMap<String, FrontendSettingValue>>,
425}
426
427impl MiddlewareConfig {
428 #[must_use]
430 pub fn disabled_by<'a>(
431 &self,
432 features: &'a [mobius::protocol::MiddlewareFeature],
433 id: &str,
434 ) -> Option<&'a str> {
435 features
436 .iter()
437 .filter(|feature| feature.required || self.enabled(&feature.id))
438 .find_map(|feature| {
439 feature.settings.iter().find_map(|setting| {
440 let mobius::protocol::FrontendSettingKind::Select { options, .. } =
441 &setting.kind
442 else {
443 return None;
444 };
445 let Some(FrontendSettingValue::String(value)) =
446 self.setting(&feature.id, &setting.id)
447 else {
448 return None;
449 };
450 options
451 .iter()
452 .find(|option| {
453 option.value == *value
454 && option.disables.iter().any(|disabled| disabled == id)
455 })
456 .map(|option| option.label.as_str())
457 })
458 })
459 }
460
461 pub fn reconcile(&mut self, features: &[mobius::protocol::MiddlewareFeature]) {
463 let excluded = self
464 .enabled
465 .iter()
466 .filter(|id| self.disabled_by(features, id).is_some())
467 .cloned()
468 .collect::<Vec<_>>();
469 for id in excluded {
470 self.enabled.remove(&id);
471 }
472 }
473
474 #[must_use]
476 pub fn enabled(&self, id: &str) -> bool {
477 self.enabled.contains(id)
478 }
479
480 pub fn set_enabled(&mut self, id: impl Into<String>, enabled: bool) {
482 let id = id.into();
483 if enabled {
484 self.enabled.insert(id);
485 } else {
486 self.enabled.remove(&id);
487 }
488 }
489
490 #[must_use]
492 pub fn setting(&self, middleware: &str, setting: &str) -> Option<&FrontendSettingValue> {
493 self.settings.get(middleware)?.get(setting)
494 }
495
496 pub fn set_setting(
498 &mut self,
499 middleware: impl Into<String>,
500 setting: impl Into<String>,
501 value: Option<FrontendSettingValue>,
502 ) {
503 let middleware = middleware.into();
504 let setting = setting.into();
505 if let Some(value) = value {
506 self.settings
507 .entry(middleware)
508 .or_default()
509 .insert(setting, value);
510 } else if let Some(settings) = self.settings.get_mut(&middleware) {
511 settings.remove(&setting);
512 if settings.is_empty() {
513 self.settings.remove(&middleware);
514 }
515 }
516 }
517
518 pub(crate) fn entries(&self) -> impl Iterator<Item = &str> {
519 self.enabled.iter().map(String::as_str)
520 }
521}
522
523#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
525pub struct RenderedPreview {
526 pub id: String,
527 pub title: String,
528 pub subtitle: String,
529 pub page_id: String,
530 pub update: FrontendPreviewUpdate,
531 pub events: Vec<RenderedEvent>,
532 pub next: Option<Op>,
533}
534
535#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
537pub struct RenderedEvent {
538 pub submission_id: Option<String>,
539 pub recorded_at_ms: i64,
540 pub event: EventMsg,
541 pub blocks: Vec<RenderedBlock>,
542}
543
544#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
546pub struct RecordedEvent {
547 pub sequence: u64,
548 pub recorded_at_ms: i64,
549 pub event: Event,
550 pub stream_metrics: Vec<StreamMetrics>,
551 pub blocks: Vec<RenderedBlock>,
552 pub preview: Option<RenderedPreview>,
553}
554
555#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
557pub struct ProfileSnapshot {
558 pub user_name: Option<String>,
559 pub daily_usage: Vec<DailyUsage>,
560 pub run_stats: RunStats,
561 pub recent_run_groups: Vec<SessionRunGroup>,
562}
563
564#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
566pub struct SessionRunGroup {
567 pub session_id: String,
568 pub title: String,
569 pub runs: Vec<RunSummary>,
570}
571
572#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
574pub struct RunStats {
575 #[serde(flatten)]
576 pub completed: mobius::backend::checkpoint::ExecutionStats,
577 pub active: Option<RunSummary>,
578}
579
580#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
582pub struct RunSummary {
583 pub session_id: String,
584 pub submission_id: String,
585 pub turn_id: String,
586 pub started_at_ms: i64,
587 pub finished_at_ms: Option<i64>,
588 pub elapsed_ms: u64,
589 pub outcome: Option<mobius::backend::checkpoint::ExecutionOutcome>,
590 pub model_calls: u64,
591 pub tool_calls: u64,
592 pub failed_tool_calls: u64,
593 pub usage: TokenUsage,
594}
595
596#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
598pub struct DailyUsage {
599 pub unix_day: u64,
600 pub provider: String,
601 pub usage: TokenUsage,
602}
603
604#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
606pub struct BotRecord {
607 pub id: String,
608 pub handle: String,
609 pub name: String,
610 pub description: String,
611 pub tint: ProviderTint,
612 pub config: VersionedAgentConfig,
613}
614
615impl Serialize for BotRecord {
617 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
618 where
619 S: serde::Serializer,
620 {
621 use serde::ser::SerializeStruct as _;
622
623 let mut record = serializer.serialize_struct("BotRecord", 7)?;
624 record.serialize_field("id", &self.id)?;
625 record.serialize_field("handle", &self.handle)?;
626 record.serialize_field("name", &self.name)?;
627 record.serialize_field("description", &self.description)?;
628 record.serialize_field("tint", &self.tint)?;
629 record.serialize_field("config", &self.config)?;
630 record.serialize_field("collaboration_enabled", &self.collaboration_enabled())?;
631 record.end()
632 }
633}
634
635impl BotRecord {
636 #[must_use]
638 pub fn collaboration_enabled(&self) -> bool {
639 mobius::middleware::bots::collaboration_enabled(
640 self.config
641 .config
642 .middleware
643 .setting("bots", "collaboration"),
644 )
645 }
646}
647
648#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
650pub struct Routine {
651 pub id: String,
652 pub bot_id: String,
653 pub workspace: PathBuf,
654 pub instructions: String,
655 pub schedule: RoutineSchedule,
656 pub ends_at: Option<i64>,
657 pub enabled: bool,
658 pub finished: bool,
659 pub next_run_at: Option<i64>,
660}
661
662#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
664#[serde(deny_unknown_fields)]
665pub struct RoutineSchedule {
666 pub kind: RoutineScheduleKind,
667 pub at: Option<i64>,
668 pub every_seconds: Option<u64>,
669 pub expression: Option<String>,
670 pub time_zone: Option<String>,
671}
672
673#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
675#[serde(rename_all = "snake_case")]
676pub enum RoutineScheduleKind {
677 Once,
678 Interval,
679 Cron,
680}
681
682#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
684pub struct RoutineRunPreview {
685 pub routine: Routine,
686 pub run: RoutineRun,
687 pub records: Vec<RecordedEvent>,
688 pub next_before_sequence: Option<u64>,
689}
690
691#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
693pub struct RoutineRun {
694 pub id: String,
695 pub routine_id: String,
696 pub bot_id: String,
697 pub started_at: i64,
698 pub finished_at: Option<i64>,
699 pub status: RoutineRunStatus,
700 pub session_id: Option<String>,
701 pub message: Option<String>,
702}
703
704#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
706#[serde(rename_all = "snake_case")]
707pub enum RoutineRunStatus {
708 Running,
709 Succeeded,
710 Failed,
711 Skipped,
712}