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}
317
318#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
320#[serde(rename_all = "snake_case")]
321pub enum ProviderTint {
322 #[default]
323 Blue,
324 Teal,
325 Green,
326 Yellow,
327 Orange,
328 Red,
329 Purple,
330}
331
332#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
334pub struct ProviderInstance {
335 pub label: String,
336 pub tint: ProviderTint,
337 pub configured: bool,
338 #[serde(default, skip_serializing_if = "Option::is_none")]
339 pub credential_hint: Option<String>,
340 pub selection: ProviderConfig,
341 pub model_ids: Vec<String>,
342 pub reasoning_efforts: Vec<String>,
343}
344
345#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
347#[serde(rename_all = "snake_case")]
348pub enum ClientKind {
349 Cli,
350 Macos,
351 Ios,
352 Ipados,
353 GatewayDashboard,
354}
355
356#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
358pub struct ClientStatus {
359 pub client_id: String,
360 pub label: String,
361 pub kinds: Vec<ClientKind>,
362 pub connections: usize,
363}
364
365impl ProviderStatus {
366 #[must_use]
367 pub fn configurable_base_url(&self) -> bool {
368 self.default_base_url.is_some()
369 }
370
371 #[must_use]
372 pub fn default_model(&self) -> Option<&ProviderModel> {
373 self.models.first()
374 }
375}
376
377#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
379pub struct ProviderModel {
380 pub id: String,
381 pub label: String,
382 pub description: String,
383 pub context_window: i64,
384 pub reasoning: Vec<ReasoningChoice>,
385 pub default_reasoning: Option<String>,
386 pub tool_discovery: ToolDiscoveryMode,
387}
388
389#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
391pub struct ReasoningChoice {
392 pub id: String,
393 pub label: String,
394 pub description: String,
395}
396
397#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
399#[serde(rename_all = "snake_case")]
400pub enum ProviderAuthKind {
401 ApiKey,
402 DeviceCode,
403}
404
405#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
407#[serde(deny_unknown_fields)]
408pub struct MiddlewareConfig {
409 pub(crate) enabled: BTreeSet<String>,
410 pub settings: BTreeMap<String, BTreeMap<String, FrontendSettingValue>>,
411}
412
413impl MiddlewareConfig {
414 #[must_use]
416 pub fn enabled(&self, id: &str) -> bool {
417 self.enabled.contains(id)
418 }
419
420 pub fn set_enabled(&mut self, id: impl Into<String>, enabled: bool) {
422 let id = id.into();
423 if enabled {
424 self.enabled.insert(id);
425 } else {
426 self.enabled.remove(&id);
427 }
428 }
429
430 #[must_use]
432 pub fn setting(&self, middleware: &str, setting: &str) -> Option<&FrontendSettingValue> {
433 self.settings.get(middleware)?.get(setting)
434 }
435
436 pub fn set_setting(
438 &mut self,
439 middleware: impl Into<String>,
440 setting: impl Into<String>,
441 value: Option<FrontendSettingValue>,
442 ) {
443 let middleware = middleware.into();
444 let setting = setting.into();
445 if let Some(value) = value {
446 self.settings
447 .entry(middleware)
448 .or_default()
449 .insert(setting, value);
450 } else if let Some(settings) = self.settings.get_mut(&middleware) {
451 settings.remove(&setting);
452 if settings.is_empty() {
453 self.settings.remove(&middleware);
454 }
455 }
456 }
457
458 pub(crate) fn entries(&self) -> impl Iterator<Item = &str> {
459 self.enabled.iter().map(String::as_str)
460 }
461}
462
463#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
465pub struct RenderedPreview {
466 pub id: String,
467 pub title: String,
468 pub subtitle: String,
469 pub page_id: String,
470 pub update: FrontendPreviewUpdate,
471 pub events: Vec<RenderedEvent>,
472 pub next: Option<Op>,
473}
474
475#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
477pub struct RenderedEvent {
478 pub recorded_at_ms: i64,
479 pub event: EventMsg,
480 pub blocks: Vec<RenderedBlock>,
481}
482
483#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
485pub struct RecordedEvent {
486 pub sequence: u64,
487 pub recorded_at_ms: i64,
488 pub event: Event,
489 pub stream_metrics: Vec<StreamMetrics>,
490 pub blocks: Vec<RenderedBlock>,
491 pub preview: Option<RenderedPreview>,
492}
493
494#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
496pub struct ProfileSnapshot {
497 pub user_name: Option<String>,
498 pub daily_usage: Vec<DailyUsage>,
499 pub run_stats: RunStats,
500 pub recent_run_groups: Vec<SessionRunGroup>,
501}
502
503#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
505pub struct SessionRunGroup {
506 pub session_id: String,
507 pub title: String,
508 pub runs: Vec<RunSummary>,
509}
510
511#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
513pub struct RunStats {
514 #[serde(flatten)]
515 pub completed: mobius::backend::checkpoint::ExecutionStats,
516 pub active: Option<RunSummary>,
517}
518
519#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
521pub struct RunSummary {
522 pub session_id: String,
523 pub submission_id: String,
524 pub turn_id: String,
525 pub started_at_ms: i64,
526 pub finished_at_ms: Option<i64>,
527 pub elapsed_ms: u64,
528 pub outcome: Option<mobius::backend::checkpoint::ExecutionOutcome>,
529 pub model_calls: u64,
530 pub tool_calls: u64,
531 pub failed_tool_calls: u64,
532 pub usage: TokenUsage,
533}
534
535#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
537pub struct DailyUsage {
538 pub unix_day: u64,
539 pub provider: String,
540 pub usage: TokenUsage,
541}
542
543#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
545pub struct BotRecord {
546 pub id: String,
547 pub handle: String,
548 pub name: String,
549 pub description: String,
550 pub tint: ProviderTint,
551 pub config: VersionedAgentConfig,
552}
553
554#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
556pub struct Routine {
557 pub id: String,
558 pub bot_id: String,
559 pub workspace: PathBuf,
560 pub instructions: String,
561 pub schedule: RoutineSchedule,
562 pub ends_at: Option<i64>,
563 pub enabled: bool,
564 pub finished: bool,
565 pub next_run_at: Option<i64>,
566}
567
568#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
570#[serde(deny_unknown_fields)]
571pub struct RoutineSchedule {
572 pub kind: RoutineScheduleKind,
573 pub at: Option<i64>,
574 pub every_seconds: Option<u64>,
575 pub expression: Option<String>,
576 pub time_zone: Option<String>,
577}
578
579#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
581#[serde(rename_all = "snake_case")]
582pub enum RoutineScheduleKind {
583 Once,
584 Interval,
585 Cron,
586}
587
588#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
590pub struct RoutineRunPreview {
591 pub routine: Routine,
592 pub run: RoutineRun,
593 pub records: Vec<RecordedEvent>,
594 pub next_before_sequence: Option<u64>,
595}
596
597#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
599pub struct RoutineRun {
600 pub id: String,
601 pub routine_id: String,
602 pub bot_id: String,
603 pub started_at: i64,
604 pub finished_at: Option<i64>,
605 pub status: RoutineRunStatus,
606 pub session_id: Option<String>,
607 pub message: Option<String>,
608}
609
610#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
612#[serde(rename_all = "snake_case")]
613pub enum RoutineRunStatus {
614 Running,
615 Succeeded,
616 Failed,
617 Skipped,
618}