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 middleware: MiddlewareConfig,
233 pub extensions: BTreeSet<String>,
234 pub system_prompt: String,
235 pub max_model_steps: u64,
236}
237
238#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
240#[serde(rename_all = "snake_case")]
241pub enum ExtensionKind {
242 Skill,
243 Plugin,
244}
245
246#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
248#[serde(deny_unknown_fields)]
249pub struct ExtensionHookRecord {
250 pub event: String,
251 pub matcher: Option<String>,
252 pub command: String,
253 pub timeout_seconds: u64,
254}
255
256#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
258#[serde(deny_unknown_fields)]
259pub struct ExtensionRecord {
260 pub id: String,
261 pub capability: String,
262 pub kind: ExtensionKind,
263 pub name: String,
264 pub description: String,
265 pub version: Option<String>,
266 pub source: String,
267 pub reference: Option<String>,
268 pub subdirectory: Option<String>,
269 pub resolved_revision: String,
270 pub digest: String,
271 pub skills: Vec<String>,
272 pub hooks: Vec<ExtensionHookRecord>,
273 pub hooks_trusted: bool,
274}
275
276#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
278#[serde(deny_unknown_fields)]
279pub struct ProviderConfig {
280 pub instance: String,
283 pub provider: String,
284 pub model: String,
285 #[serde(default, skip_serializing_if = "Option::is_none")]
286 pub base_url: Option<String>,
287 pub endpoint_auth: ProviderEndpointAuth,
288 #[serde(default, skip_serializing_if = "Option::is_none")]
289 pub reasoning_effort: Option<String>,
290 pub web_search: HostedWebSearch,
291}
292
293#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
295#[serde(rename_all = "snake_case")]
296pub enum ProviderEndpointAuth {
297 ProviderDefault,
298 Credentialless,
299}
300
301#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
303pub struct ProviderStatus {
304 pub provider: String,
305 pub label: String,
306 pub symbol: FrontendSymbol,
307 pub description: String,
308 pub model_ids_configurable: bool,
309 pub auth: ProviderAuthKind,
310 pub default_base_url: Option<String>,
311 pub default_api_key_env: Option<String>,
312 pub models: Vec<ProviderModel>,
313 pub web_search: Vec<FrontendSettingOption>,
314 pub tool_discovery: ToolDiscoveryMode,
315 pub custom_endpoint_tool_discovery: Option<ToolDiscoveryMode>,
316 pub supports_realtime_voice: bool,
317}
318
319#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
321#[serde(rename_all = "snake_case")]
322pub enum ProviderTint {
323 #[default]
324 Blue,
325 Teal,
326 Green,
327 Yellow,
328 Orange,
329 Red,
330 Purple,
331}
332
333#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
335pub struct ProviderInstance {
336 pub label: String,
337 pub tint: ProviderTint,
338 pub configured: bool,
339 #[serde(default, skip_serializing_if = "Option::is_none")]
340 pub credential_hint: Option<String>,
341 pub selection: ProviderConfig,
342 pub model_ids: Vec<String>,
343 pub reasoning_efforts: Vec<String>,
344}
345
346#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
348#[serde(rename_all = "snake_case")]
349pub enum ClientKind {
350 Cli,
351 Macos,
352 Ios,
353 Ipados,
354 GatewayDashboard,
355}
356
357#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
359pub struct ClientStatus {
360 pub client_id: String,
361 pub label: String,
362 pub kinds: Vec<ClientKind>,
363 pub connections: usize,
364}
365
366impl ProviderStatus {
367 #[must_use]
368 pub fn configurable_base_url(&self) -> bool {
369 self.default_base_url.is_some()
370 }
371
372 #[must_use]
373 pub fn default_model(&self) -> Option<&ProviderModel> {
374 self.models.first()
375 }
376}
377
378#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
380pub struct ProviderModel {
381 pub id: String,
382 pub label: String,
383 pub description: String,
384 pub context_window: i64,
385 pub reasoning: Vec<ReasoningChoice>,
386 pub default_reasoning: Option<String>,
387 pub tool_discovery: ToolDiscoveryMode,
388}
389
390#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
392pub struct ReasoningChoice {
393 pub id: String,
394 pub label: String,
395 pub description: String,
396}
397
398#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
400#[serde(rename_all = "snake_case")]
401pub enum ProviderAuthKind {
402 ApiKey,
403 DeviceCode,
404}
405
406#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
408#[serde(deny_unknown_fields)]
409pub struct MiddlewareConfig {
410 pub(crate) enabled: BTreeSet<String>,
411 pub settings: BTreeMap<String, BTreeMap<String, FrontendSettingValue>>,
412}
413
414impl MiddlewareConfig {
415 #[must_use]
417 pub fn enabled(&self, id: &str) -> bool {
418 self.enabled.contains(id)
419 }
420
421 pub fn set_enabled(&mut self, id: impl Into<String>, enabled: bool) {
423 let id = id.into();
424 if enabled {
425 self.enabled.insert(id);
426 } else {
427 self.enabled.remove(&id);
428 }
429 }
430
431 #[must_use]
433 pub fn setting(&self, middleware: &str, setting: &str) -> Option<&FrontendSettingValue> {
434 self.settings.get(middleware)?.get(setting)
435 }
436
437 pub fn set_setting(
439 &mut self,
440 middleware: impl Into<String>,
441 setting: impl Into<String>,
442 value: Option<FrontendSettingValue>,
443 ) {
444 let middleware = middleware.into();
445 let setting = setting.into();
446 if let Some(value) = value {
447 self.settings
448 .entry(middleware)
449 .or_default()
450 .insert(setting, value);
451 } else if let Some(settings) = self.settings.get_mut(&middleware) {
452 settings.remove(&setting);
453 if settings.is_empty() {
454 self.settings.remove(&middleware);
455 }
456 }
457 }
458
459 pub(crate) fn entries(&self) -> impl Iterator<Item = &str> {
460 self.enabled.iter().map(String::as_str)
461 }
462}
463
464#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
466pub struct RenderedPreview {
467 pub id: String,
468 pub title: String,
469 pub subtitle: String,
470 pub page_id: String,
471 pub update: FrontendPreviewUpdate,
472 pub events: Vec<RenderedEvent>,
473 pub next: Option<Op>,
474}
475
476#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
478pub struct RenderedEvent {
479 pub recorded_at_ms: i64,
480 pub event: EventMsg,
481 pub blocks: Vec<RenderedBlock>,
482}
483
484#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
486pub struct RecordedEvent {
487 pub sequence: u64,
488 pub recorded_at_ms: i64,
489 pub event: Event,
490 pub stream_metrics: Vec<StreamMetrics>,
491 pub blocks: Vec<RenderedBlock>,
492 pub preview: Option<RenderedPreview>,
493}
494
495#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
497pub struct ProfileSnapshot {
498 pub user_name: Option<String>,
499 pub daily_usage: Vec<DailyUsage>,
500 pub run_stats: RunStats,
501 pub recent_run_groups: Vec<SessionRunGroup>,
502}
503
504#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
506pub struct SessionRunGroup {
507 pub session_id: String,
508 pub title: String,
509 pub runs: Vec<RunSummary>,
510}
511
512#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
514pub struct RunStats {
515 #[serde(flatten)]
516 pub completed: mobius::backend::checkpoint::ExecutionStats,
517 pub active: Option<RunSummary>,
518}
519
520#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
522pub struct RunSummary {
523 pub session_id: String,
524 pub submission_id: String,
525 pub turn_id: String,
526 pub started_at_ms: i64,
527 pub finished_at_ms: Option<i64>,
528 pub elapsed_ms: u64,
529 pub outcome: Option<mobius::backend::checkpoint::ExecutionOutcome>,
530 pub model_calls: u64,
531 pub tool_calls: u64,
532 pub failed_tool_calls: u64,
533 pub usage: TokenUsage,
534}
535
536#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
538pub struct DailyUsage {
539 pub unix_day: u64,
540 pub provider: String,
541 pub usage: TokenUsage,
542}
543
544#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
546pub struct BotRecord {
547 pub id: String,
548 pub handle: String,
549 pub name: String,
550 pub description: String,
551 pub tint: ProviderTint,
552 pub config: VersionedAgentConfig,
553}
554
555#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
557pub struct Routine {
558 pub id: String,
559 pub bot_id: String,
560 pub workspace: PathBuf,
561 pub instructions: String,
562 pub schedule: RoutineSchedule,
563 pub ends_at: Option<i64>,
564 pub enabled: bool,
565 pub finished: bool,
566 pub next_run_at: Option<i64>,
567}
568
569#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
571#[serde(deny_unknown_fields)]
572pub struct RoutineSchedule {
573 pub kind: RoutineScheduleKind,
574 pub at: Option<i64>,
575 pub every_seconds: Option<u64>,
576 pub expression: Option<String>,
577 pub time_zone: Option<String>,
578}
579
580#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
582#[serde(rename_all = "snake_case")]
583pub enum RoutineScheduleKind {
584 Once,
585 Interval,
586 Cron,
587}
588
589#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
591pub struct RoutineRunPreview {
592 pub routine: Routine,
593 pub run: RoutineRun,
594 pub records: Vec<RecordedEvent>,
595 pub next_before_sequence: Option<u64>,
596}
597
598#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
600pub struct RoutineRun {
601 pub id: String,
602 pub routine_id: String,
603 pub bot_id: String,
604 pub started_at: i64,
605 pub finished_at: Option<i64>,
606 pub status: RoutineRunStatus,
607 pub session_id: Option<String>,
608 pub message: Option<String>,
609}
610
611#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
613#[serde(rename_all = "snake_case")]
614pub enum RoutineRunStatus {
615 Running,
616 Succeeded,
617 Failed,
618 Skipped,
619}