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 enabled(&self, id: &str) -> bool {
431 self.enabled.contains(id)
432 }
433
434 pub fn set_enabled(&mut self, id: impl Into<String>, enabled: bool) {
436 let id = id.into();
437 if enabled {
438 self.enabled.insert(id);
439 } else {
440 self.enabled.remove(&id);
441 }
442 }
443
444 #[must_use]
446 pub fn setting(&self, middleware: &str, setting: &str) -> Option<&FrontendSettingValue> {
447 self.settings.get(middleware)?.get(setting)
448 }
449
450 pub fn set_setting(
452 &mut self,
453 middleware: impl Into<String>,
454 setting: impl Into<String>,
455 value: Option<FrontendSettingValue>,
456 ) {
457 let middleware = middleware.into();
458 let setting = setting.into();
459 if let Some(value) = value {
460 self.settings
461 .entry(middleware)
462 .or_default()
463 .insert(setting, value);
464 } else if let Some(settings) = self.settings.get_mut(&middleware) {
465 settings.remove(&setting);
466 if settings.is_empty() {
467 self.settings.remove(&middleware);
468 }
469 }
470 }
471
472 pub(crate) fn entries(&self) -> impl Iterator<Item = &str> {
473 self.enabled.iter().map(String::as_str)
474 }
475}
476
477#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
479pub struct RenderedPreview {
480 pub id: String,
481 pub title: String,
482 pub subtitle: String,
483 pub page_id: String,
484 pub update: FrontendPreviewUpdate,
485 pub events: Vec<RenderedEvent>,
486 pub next: Option<Op>,
487}
488
489#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
491pub struct RenderedEvent {
492 pub submission_id: Option<String>,
493 pub recorded_at_ms: i64,
494 pub event: EventMsg,
495 pub blocks: Vec<RenderedBlock>,
496}
497
498#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
500pub struct RecordedEvent {
501 pub sequence: u64,
502 pub recorded_at_ms: i64,
503 pub event: Event,
504 pub stream_metrics: Vec<StreamMetrics>,
505 pub blocks: Vec<RenderedBlock>,
506 pub preview: Option<RenderedPreview>,
507}
508
509#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
511pub struct ProfileSnapshot {
512 pub user_name: Option<String>,
513 pub daily_usage: Vec<DailyUsage>,
514 pub run_stats: RunStats,
515 pub recent_run_groups: Vec<SessionRunGroup>,
516}
517
518#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
520pub struct SessionRunGroup {
521 pub session_id: String,
522 pub title: String,
523 pub runs: Vec<RunSummary>,
524}
525
526#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
528pub struct RunStats {
529 #[serde(flatten)]
530 pub completed: mobius::backend::checkpoint::ExecutionStats,
531 pub active: Option<RunSummary>,
532}
533
534#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
536pub struct RunSummary {
537 pub session_id: String,
538 pub submission_id: String,
539 pub turn_id: String,
540 pub started_at_ms: i64,
541 pub finished_at_ms: Option<i64>,
542 pub elapsed_ms: u64,
543 pub outcome: Option<mobius::backend::checkpoint::ExecutionOutcome>,
544 pub model_calls: u64,
545 pub tool_calls: u64,
546 pub failed_tool_calls: u64,
547 pub usage: TokenUsage,
548}
549
550#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
552pub struct DailyUsage {
553 pub unix_day: u64,
554 pub provider: String,
555 pub usage: TokenUsage,
556}
557
558#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
560pub struct BotRecord {
561 pub id: String,
562 pub handle: String,
563 pub name: String,
564 pub description: String,
565 pub tint: ProviderTint,
566 pub config: VersionedAgentConfig,
567}
568
569#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
571pub struct Routine {
572 pub id: String,
573 pub bot_id: String,
574 pub workspace: PathBuf,
575 pub instructions: String,
576 pub schedule: RoutineSchedule,
577 pub ends_at: Option<i64>,
578 pub enabled: bool,
579 pub finished: bool,
580 pub next_run_at: Option<i64>,
581}
582
583#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
585#[serde(deny_unknown_fields)]
586pub struct RoutineSchedule {
587 pub kind: RoutineScheduleKind,
588 pub at: Option<i64>,
589 pub every_seconds: Option<u64>,
590 pub expression: Option<String>,
591 pub time_zone: Option<String>,
592}
593
594#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
596#[serde(rename_all = "snake_case")]
597pub enum RoutineScheduleKind {
598 Once,
599 Interval,
600 Cron,
601}
602
603#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
605pub struct RoutineRunPreview {
606 pub routine: Routine,
607 pub run: RoutineRun,
608 pub records: Vec<RecordedEvent>,
609 pub next_before_sequence: Option<u64>,
610}
611
612#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
614pub struct RoutineRun {
615 pub id: String,
616 pub routine_id: String,
617 pub bot_id: String,
618 pub started_at: i64,
619 pub finished_at: Option<i64>,
620 pub status: RoutineRunStatus,
621 pub session_id: Option<String>,
622 pub message: Option<String>,
623}
624
625#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
627#[serde(rename_all = "snake_case")]
628pub enum RoutineRunStatus {
629 Running,
630 Succeeded,
631 Failed,
632 Skipped,
633}