1use super::*;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
6#[serde(rename_all = "snake_case")]
7pub enum GatewayNotification {
8 Sessions,
10 Bots,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(rename_all = "snake_case")]
17pub enum ComputerView {
18 Unavailable,
20 EmbeddedBrowser,
22 RemoteDesktop,
24}
25
26#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
28pub struct ReadyPayload {
29 pub gateway_version: String,
31 pub machine_name: String,
33 pub computer_view: ComputerView,
35 pub bots: Vec<BotRecord>,
37 pub sessions: Vec<SessionRecord>,
39 pub background_approvals: Vec<BackgroundApproval>,
41 pub providers: Vec<ProviderStatus>,
43 pub provider_instances: Vec<ProviderInstance>,
45 pub bot_defaults: Option<VersionedAgentConfig>,
47 pub models: Vec<ModelChoice>,
49 pub model_providers: BTreeMap<String, String>,
51 pub middleware_features: Vec<MiddlewareFeature>,
53 pub extensions: Vec<ExtensionRecord>,
55 pub contributions: Vec<FrontendContribution>,
57 pub max_active_sessions: usize,
59 pub session_file_limits: SessionFileLimits,
61 pub revisions: BTreeMap<ReadySection, String>,
63 pub omitted: BTreeSet<ReadySection>,
65}
66
67impl ReadyPayload {
68 pub fn swap_section(&mut self, other: &mut Self, section: ReadySection) {
70 use std::mem::swap;
71 match section {
72 ReadySection::Config => {
73 swap(&mut self.providers, &mut other.providers);
74 swap(&mut self.provider_instances, &mut other.provider_instances);
75 swap(&mut self.bot_defaults, &mut other.bot_defaults);
76 swap(&mut self.models, &mut other.models);
77 swap(&mut self.model_providers, &mut other.model_providers);
78 swap(
79 &mut self.middleware_features,
80 &mut other.middleware_features,
81 );
82 swap(&mut self.extensions, &mut other.extensions);
83 swap(&mut self.contributions, &mut other.contributions);
84 }
85 ReadySection::Bots => swap(&mut self.bots, &mut other.bots),
86 ReadySection::Sessions => swap(&mut self.sessions, &mut other.sessions),
87 }
88 }
89
90 pub fn restore_omitted(&mut self, held: &mut Self) {
92 for section in std::mem::take(&mut self.omitted) {
93 self.swap_section(held, section);
94 }
95 }
96
97 pub fn update(&mut self, mut next: Self) {
99 next.restore_omitted(self);
100 *self = next;
101 }
102
103 #[must_use]
105 pub fn revision(&self, section: ReadySection) -> String {
106 match section {
107 ReadySection::Config => content_revision(&(
108 &self.providers,
109 &self.provider_instances,
110 &self.bot_defaults,
111 &self.models,
112 &self.model_providers,
113 &self.middleware_features,
114 &self.extensions,
115 &self.contributions,
116 )),
117 ReadySection::Bots => content_revision(&self.bots),
118 ReadySection::Sessions => content_revision(&self.sessions),
119 }
120 }
121
122 pub(crate) fn blank(&self) -> Self {
124 Self {
125 gateway_version: String::new(),
126 machine_name: String::new(),
127 computer_view: self.computer_view,
128 bots: Vec::new(),
129 sessions: Vec::new(),
130 background_approvals: Vec::new(),
131 providers: Vec::new(),
132 provider_instances: Vec::new(),
133 bot_defaults: None,
134 models: Vec::new(),
135 model_providers: BTreeMap::new(),
136 middleware_features: Vec::new(),
137 extensions: Vec::new(),
138 contributions: Vec::new(),
139 max_active_sessions: self.max_active_sessions,
140 session_file_limits: self.session_file_limits,
141 revisions: BTreeMap::new(),
142 omitted: BTreeSet::new(),
143 }
144 }
145}
146
147#[must_use]
149pub fn content_revision(value: &impl Serialize) -> String {
150 use sha2::Digest as _;
151 let mut hasher = sha2::Sha256::new();
152 let _ = serde_json::to_writer(&mut hasher, value);
154 hasher.finalize()[..16]
155 .iter()
156 .map(|byte| format!("{byte:02x}"))
157 .collect()
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
162#[serde(rename_all = "snake_case")]
163pub enum ReadySection {
164 Config,
166 Bots,
168 Sessions,
170}
171
172pub const READY_SECTIONS: [ReadySection; 3] = [
174 ReadySection::Config,
175 ReadySection::Bots,
176 ReadySection::Sessions,
177];
178
179#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
181pub struct CatalogHint {
182 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
184 pub known: BTreeMap<ReadySection, String>,
185 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
187 pub skip: BTreeSet<ReadySection>,
188}
189
190impl CatalogHint {
191 #[must_use]
193 pub fn is_empty(&self) -> bool {
194 self.known.is_empty() && self.skip.is_empty()
195 }
196}
197
198#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
200#[serde(untagged)]
201pub enum SessionSlot {
202 Unchanged(String),
204 Changed(Box<SessionRecord>),
206}
207
208pub fn apply_session_changes(sessions: &mut Vec<SessionRecord>, changes: Vec<SessionSlot>) {
210 let mut held: Vec<_> = std::mem::take(sessions).into_iter().map(Some).collect();
211 *sessions = changes
212 .into_iter()
213 .filter_map(|slot| match slot {
214 SessionSlot::Changed(session) => Some(*session),
215 SessionSlot::Unchanged(id) => held
216 .iter_mut()
217 .find(|held| held.as_ref().is_some_and(|held| held.session_id == id))
218 .and_then(Option::take),
219 })
220 .collect();
221}
222
223#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
225pub struct DiffTotals {
226 pub additions: u64,
228 pub deletions: u64,
230}
231
232#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
234pub struct BackgroundApproval {
235 pub session_id: String,
237 pub bot_id: String,
239 pub turn_id: String,
241 pub request_id: String,
243}
244
245#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
247pub struct SessionReadyPayload {
248 pub active_turn_ids: Vec<String>,
250 pub pending_approvals: Vec<mobius::protocol::ExecApprovalRequestEvent>,
252 pub latest_sequence: u64,
254 pub next_before_sequence: Option<u64>,
256 pub workspace: Option<WorkspaceInfo>,
258 pub attached_folders: Vec<PathBuf>,
260 pub git: Option<GitStatus>,
262 pub session: SessionConfiguredEvent,
264 pub contributions: Vec<FrontendContribution>,
266 pub widgets: Vec<SessionWidget>,
268 pub tool_count: usize,
270 pub compaction_count: u64,
272 pub context_limit_tokens: Option<i64>,
274 pub active_message_delivery: mobius::protocol::ActiveMessageDelivery,
276 pub run_stats: RunStats,
278}
279
280#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
282pub struct SessionWidget {
283 pub capability: String,
285 pub item: FrontendWidget,
287}
288
289#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
291pub struct SessionRecord {
292 pub session_id: String,
294 pub session_context: mobius::protocol::SessionContext,
296 pub parent_session_id: Option<String>,
298 pub parent_sequence: Option<u64>,
300 pub sequence: u64,
302 pub first_user_message: Option<String>,
304 pub execution_stats: mobius::backend::checkpoint::ExecutionStats,
306 pub title: Option<String>,
308 pub pinned: bool,
310 pub activity: SessionActivity,
312 pub created_at: i64,
314 pub updated_at: i64,
316}
317
318#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
320#[serde(deny_unknown_fields)]
321pub struct SessionActivity {
322 pub attention: u32,
324 pub state: SessionActivityState,
326 pub turn_id: Option<String>,
328 pub approval_request_id: Option<String>,
330 pub started_at: Option<i64>,
332 pub last_outcome: Option<mobius::backend::checkpoint::ExecutionOutcome>,
334 pub message: Option<String>,
336}
337
338impl Default for SessionActivity {
339 fn default() -> Self {
340 Self {
341 attention: 0,
342 state: SessionActivityState::Idle,
343 turn_id: None,
344 approval_request_id: None,
345 started_at: None,
346 last_outcome: None,
347 message: None,
348 }
349 }
350}
351
352#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
354#[serde(rename_all = "snake_case")]
355pub enum SessionActivityState {
356 Idle,
358 Running,
360 AwaitingApproval,
362}
363
364#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
366pub struct WorkspaceInfo {
367 pub id: String,
369 pub path: PathBuf,
371}
372
373#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
375pub struct GitStatus {
376 pub current_branch: String,
378 pub branches: Vec<String>,
380}
381
382#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
384#[serde(deny_unknown_fields)]
385pub struct SshIdentityRecord {
386 pub label: String,
388 pub algorithm: String,
390 pub fingerprint: String,
392}
393
394#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
396#[serde(rename_all = "snake_case")]
397pub enum GitDiffScope {
398 Staged,
400 Unstaged,
402 Committed,
404}
405
406#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
408#[serde(rename_all = "snake_case")]
409pub enum WorkspaceFileScope {
410 Modified,
412 All,
414}
415
416#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
418pub struct WorkspaceFileRecord {
419 pub path: String,
421 pub size: u64,
423}
424
425#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
427pub struct DirectoryListing {
428 pub path: PathBuf,
430 pub parent: Option<PathBuf>,
432 pub entries: Vec<DirectoryEntry>,
434}
435
436#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
438pub struct DirectoryEntry {
439 pub name: String,
441 pub path: PathBuf,
443 pub is_directory: bool,
445}
446
447#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
449pub struct VersionedAgentConfig {
450 pub revision: u64,
452 pub config: AgentComposition,
454}
455
456#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
458#[serde(deny_unknown_fields)]
459pub struct AgentComposition {
460 pub provider: ProviderConfig,
462 pub realtime_voice: Option<String>,
464 pub middleware: MiddlewareConfig,
466 pub extensions: BTreeSet<String>,
468 pub system_prompt: String,
470 pub max_model_steps: u64,
472}
473
474#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
476#[serde(rename_all = "snake_case")]
477pub enum ExtensionKind {
478 Skill,
480 Plugin,
482}
483
484#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
486#[serde(deny_unknown_fields)]
487pub struct ExtensionHookRecord {
488 pub event: String,
490 pub matcher: Option<String>,
492 pub command: String,
494 pub timeout_seconds: u64,
496}
497
498#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
500#[serde(deny_unknown_fields)]
501pub struct ExtensionRecord {
502 pub id: String,
504 pub capability: String,
506 pub kind: ExtensionKind,
508 pub name: String,
510 pub description: String,
512 pub version: Option<String>,
514 pub source: String,
516 pub reference: Option<String>,
518 pub subdirectory: Option<String>,
520 pub resolved_revision: String,
522 pub digest: String,
524 pub skills: Vec<String>,
526 pub hooks: Vec<ExtensionHookRecord>,
528 pub hooks_trusted: bool,
530}
531
532#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
534#[serde(deny_unknown_fields)]
535pub struct ProviderConfig {
536 pub instance: String,
539 pub provider: String,
541 pub model: String,
543 #[serde(default, skip_serializing_if = "Option::is_none")]
544 pub base_url: Option<String>,
546 pub endpoint_auth: ProviderEndpointAuth,
548 #[serde(default, skip_serializing_if = "Option::is_none")]
549 pub reasoning_effort: Option<String>,
551 #[serde(default, skip_serializing_if = "Option::is_none")]
552 pub service_tier: Option<String>,
554 pub web_search: HostedWebSearch,
556}
557
558#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
560#[serde(rename_all = "snake_case")]
561pub enum ProviderEndpointAuth {
562 ProviderDefault,
564 Credentialless,
566}
567
568#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
570pub struct ProviderStatus {
571 pub provider: String,
573 pub label: String,
575 pub symbol: FrontendSymbol,
577 pub description: String,
579 pub model_ids_configurable: bool,
581 pub auth: ProviderAuthKind,
583 pub default_base_url: Option<String>,
585 pub native_custom_endpoints: bool,
587 pub default_api_key_env: Option<String>,
589 pub models: Vec<ProviderModel>,
591 pub web_search: Vec<FrontendSettingOption>,
593 pub tool_discovery: ToolDiscoveryMode,
595 pub custom_endpoint_tool_discovery: Option<ToolDiscoveryMode>,
597 pub realtime_voices: Vec<String>,
599}
600
601#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
603#[serde(rename_all = "snake_case")]
604pub enum ProviderTint {
605 #[default]
606 Blue,
608 Teal,
610 Green,
612 Yellow,
614 Orange,
616 Red,
618 Purple,
620 White,
622}
623
624#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
626#[serde(rename_all = "snake_case")]
627pub enum BotShape {
628 Circle,
630 Squircle,
632 Triangle,
634 Diamond,
636 Hexagon,
638 Star,
640 Flower,
642}
643
644impl BotShape {
645 pub const ALL: [Self; 7] = [
647 Self::Circle,
648 Self::Squircle,
649 Self::Triangle,
650 Self::Diamond,
651 Self::Hexagon,
652 Self::Star,
653 Self::Flower,
654 ];
655}
656
657#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
659pub struct ProviderInstance {
660 pub label: String,
662 pub tint: ProviderTint,
664 pub configured: bool,
666 #[serde(default, skip_serializing_if = "Option::is_none")]
667 pub credential_hint: Option<String>,
669 pub selection: ProviderConfig,
671 pub model_ids: Vec<String>,
673 pub reasoning_efforts: Vec<String>,
675}
676
677#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
679#[serde(rename_all = "snake_case")]
680pub enum ClientKind {
681 Cli,
683 Macos,
685 Ios,
687 Ipados,
689 GatewayDashboard,
691}
692
693#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
695pub struct ClientStatus {
696 pub client_id: String,
698 pub label: String,
700 pub kinds: Vec<ClientKind>,
702 pub connections: usize,
704}
705
706impl ProviderStatus {
707 #[must_use]
708 pub fn realtime_voices(&self, base_url: Option<&str>) -> &[String] {
710 if self.native_custom_endpoints
711 || mobius::backend::model::provider::uses_default_endpoint(
712 self.default_base_url.as_deref(),
713 base_url,
714 )
715 {
716 &self.realtime_voices
717 } else {
718 &[]
719 }
720 }
721
722 #[must_use]
723 pub fn configurable_base_url(&self) -> bool {
725 self.default_base_url.is_some()
726 }
727
728 #[must_use]
729 pub fn default_model(&self) -> Option<&ProviderModel> {
731 self.models.first()
732 }
733}
734
735#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
737pub struct ProviderModel {
738 pub id: String,
740 pub label: String,
742 pub description: String,
744 pub context_window: i64,
746 pub reasoning: Vec<ReasoningChoice>,
748 pub default_reasoning: Option<String>,
750 pub tool_discovery: ToolDiscoveryMode,
752}
753
754#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
756pub struct ReasoningChoice {
757 pub id: String,
759 pub label: String,
761 pub description: String,
763}
764
765#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
767#[serde(rename_all = "snake_case")]
768pub enum ProviderAuthKind {
769 ApiKey,
771 DeviceCode,
773}
774
775#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
777#[serde(deny_unknown_fields)]
778pub struct MiddlewareConfig {
779 pub(crate) enabled: BTreeSet<String>,
780 pub settings: BTreeMap<String, BTreeMap<String, FrontendSettingValue>>,
782}
783
784impl MiddlewareConfig {
785 #[must_use]
787 pub fn disabled_by<'a>(
788 &self,
789 features: &'a [mobius::protocol::MiddlewareFeature],
790 id: &str,
791 selected_model: Option<&mobius::protocol::ModelChoice>,
792 ) -> Option<&'a str> {
793 if let Some(capability) = features
794 .iter()
795 .find(|feature| feature.id == id)
796 .and_then(|feature| feature.required_model_capability)
797 && selected_model.is_some_and(|model| !model.supports(capability))
798 {
799 return Some(match capability {
800 mobius::protocol::ModelCapability::ImageGeneration => {
801 "a model without image generation"
802 }
803 mobius::protocol::ModelCapability::RealtimeVoice => {
804 "a model without realtime voice"
805 }
806 });
807 }
808 features
809 .iter()
810 .filter(|feature| feature.required || self.enabled(&feature.id))
811 .find_map(|feature| {
812 feature.settings.iter().find_map(|setting| {
813 let mobius::protocol::FrontendSettingKind::Select { options, .. } =
814 &setting.kind
815 else {
816 return None;
817 };
818 let Some(FrontendSettingValue::String(value)) =
819 self.setting(&feature.id, &setting.id)
820 else {
821 return None;
822 };
823 options
824 .iter()
825 .find(|option| {
826 option.value == *value
827 && option.disables.iter().any(|disabled| disabled == id)
828 })
829 .map(|option| option.label.as_str())
830 })
831 })
832 }
833
834 pub fn reconcile(
836 &mut self,
837 features: &[mobius::protocol::MiddlewareFeature],
838 selected_model: Option<&mobius::protocol::ModelChoice>,
839 ) {
840 let excluded = self
841 .enabled
842 .iter()
843 .filter(|id| self.disabled_by(features, id, selected_model).is_some())
844 .cloned()
845 .collect::<Vec<_>>();
846 for id in excluded {
847 self.enabled.remove(&id);
848 }
849 }
850
851 #[must_use]
853 pub fn enabled(&self, id: &str) -> bool {
854 self.enabled.contains(id)
855 }
856
857 pub fn set_enabled(&mut self, id: impl Into<String>, enabled: bool) {
859 let id = id.into();
860 if enabled {
861 self.enabled.insert(id);
862 } else {
863 self.enabled.remove(&id);
864 }
865 }
866
867 #[must_use]
869 pub fn setting(&self, middleware: &str, setting: &str) -> Option<&FrontendSettingValue> {
870 self.settings.get(middleware)?.get(setting)
871 }
872
873 pub fn set_setting(
875 &mut self,
876 middleware: impl Into<String>,
877 setting: impl Into<String>,
878 value: Option<FrontendSettingValue>,
879 ) {
880 let middleware = middleware.into();
881 let setting = setting.into();
882 if let Some(value) = value {
883 self.settings
884 .entry(middleware)
885 .or_default()
886 .insert(setting, value);
887 } else if let Some(settings) = self.settings.get_mut(&middleware) {
888 settings.remove(&setting);
889 if settings.is_empty() {
890 self.settings.remove(&middleware);
891 }
892 }
893 }
894
895 pub(crate) fn entries(&self) -> impl Iterator<Item = &str> {
896 self.enabled.iter().map(String::as_str)
897 }
898}
899
900#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
902pub struct RenderedPreview {
903 pub symbol: Option<FrontendSymbol>,
905 pub duration_ms: Option<u64>,
907 pub started_at_ms: Option<i64>,
909 pub id: String,
911 pub title: String,
913 pub subtitle: String,
915 pub page_id: String,
917 pub update: FrontendPreviewUpdate,
919 pub events: Vec<RenderedEvent>,
921 pub next: Option<Op>,
923}
924
925#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
927pub struct RenderedEvent {
928 pub submission_id: Option<String>,
930 pub recorded_at_ms: i64,
932 pub event: EventMsg,
934 pub blocks: Vec<RenderedBlock>,
936}
937
938#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
940pub struct RecordedEvent {
941 pub sequence: u64,
943 pub recorded_at_ms: i64,
945 pub event: Event,
947 pub stream_metrics: Vec<StreamMetrics>,
949 pub blocks: Vec<RenderedBlock>,
951 pub preview: Option<RenderedPreview>,
953}
954
955#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
957pub struct ProfileSnapshot {
958 pub user_name: Option<String>,
960 pub daily_usage: Vec<DailyUsage>,
962 pub provider_usage: Vec<ProviderUsage>,
964 pub run_stats: RunStats,
966 pub recent_run_groups: Vec<SessionRunGroup>,
968}
969
970#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
972pub struct ProviderUsage {
973 pub provider: String,
975 pub limits: Option<Vec<UsageLimit>>,
977 pub error: Option<String>,
979}
980
981#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
983pub struct SessionRunGroup {
984 pub session_id: String,
986 pub title: String,
988 pub runs: Vec<RunSummary>,
990}
991
992#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
994pub struct RunStats {
995 #[serde(flatten)]
996 pub completed: mobius::backend::checkpoint::ExecutionStats,
998 pub active: Option<RunSummary>,
1000}
1001
1002#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1004pub struct RunSummary {
1005 pub session_id: String,
1007 pub submission_id: String,
1009 pub turn_id: String,
1011 pub started_at_ms: i64,
1013 pub finished_at_ms: Option<i64>,
1015 pub elapsed_ms: u64,
1017 pub outcome: Option<mobius::backend::checkpoint::ExecutionOutcome>,
1019 pub model_calls: u64,
1021 pub tool_calls: u64,
1023 pub failed_tool_calls: u64,
1025 pub usage: TokenUsage,
1027}
1028
1029#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1031pub struct DailyUsage {
1032 pub unix_day: u64,
1034 pub provider: String,
1036 pub usage: TokenUsage,
1038}
1039
1040#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1042pub struct BotRecord {
1043 pub conversation_session_id: String,
1045 pub id: String,
1047 pub handle: String,
1049 pub name: String,
1051 pub description: String,
1053 pub tint: ProviderTint,
1055 pub shape: BotShape,
1057 pub config: VersionedAgentConfig,
1059 pub accepts_file_attachments: bool,
1061 pub routine_interaction_policy: RoutineInteractionPolicy,
1063}
1064
1065#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1067#[serde(rename_all = "snake_case")]
1068pub enum RoutineInteractionPolicy {
1069 Unattended,
1071 MayPauseForApproval,
1073}
1074
1075#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1077pub struct Routine {
1078 pub id: String,
1080 pub bot_id: String,
1082 pub workspace: PathBuf,
1084 pub instructions: String,
1086 pub bindings: Vec<RoutineBinding>,
1088 pub enabled: bool,
1090 pub finished: bool,
1092 pub next_run_at: Option<i64>,
1094}
1095
1096#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1098#[serde(deny_unknown_fields)]
1099pub struct RoutineSchedule {
1100 pub kind: RoutineScheduleKind,
1102 pub at: Option<i64>,
1104 pub every_seconds: Option<u64>,
1106 pub expression: Option<String>,
1108 pub time_zone: Option<String>,
1110}
1111
1112#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1114#[serde(rename_all = "snake_case")]
1115pub enum RoutineScheduleKind {
1116 Once,
1118 Interval,
1120 Cron,
1122}
1123
1124#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1126pub struct RoutineRunPreview {
1127 pub routine: Routine,
1129 pub run: RoutineRun,
1131 pub records: Vec<RecordedEvent>,
1133 pub next_before_sequence: Option<u64>,
1135}
1136
1137#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1139pub struct RoutineRun {
1140 pub id: String,
1142 pub routine_id: String,
1144 pub bot_id: String,
1146 pub started_at: i64,
1148 pub finished_at: Option<i64>,
1150 pub status: RoutineRunStatus,
1152 pub session_id: Option<String>,
1154 pub message: Option<String>,
1156}
1157
1158#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1160#[serde(rename_all = "snake_case")]
1161pub enum RoutineRunStatus {
1162 Running,
1164 Succeeded,
1166 Failed,
1168 Skipped,
1170 Cancelled,
1172}
1173
1174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1176#[serde(deny_unknown_fields)]
1177pub struct RoutineDefinition {
1178 pub workspace: PathBuf,
1180 pub instructions: String,
1182 pub bindings: Vec<RoutineBinding>,
1184}
1185
1186#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1188#[serde(deny_unknown_fields)]
1189pub struct HookBinding<A> {
1190 pub id: String,
1192 pub on: HookSelector,
1194 pub action: A,
1196}
1197
1198pub type RoutineBinding = HookBinding<RoutineAction>;
1200
1201#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1203#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1204pub enum BotAction {
1205 Report {
1207 instruction: String,
1209 },
1210 Routine {
1212 command: RoutineCommand,
1214 },
1215 Session {
1217 session_id: String,
1219 op: Box<mobius::protocol::Op>,
1221 },
1222}
1223
1224#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1226#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1227pub enum RoutineAction {
1228 Start,
1230 Stop {
1232 run_id: String,
1234 },
1235 Pause,
1237 Resume,
1239 Delete,
1241 Update {
1243 definition: RoutineDefinition,
1245 },
1246}
1247
1248#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1250#[serde(deny_unknown_fields)]
1251pub struct RoutineCommand {
1252 pub routine_id: String,
1254 pub action: RoutineAction,
1256}
1257
1258#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1260#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1261pub enum HookSource {
1262 Bot {
1264 bot_id: String,
1266 },
1267 Routine {
1269 routine_id: String,
1271 },
1272 Session {
1274 session_id: String,
1276 },
1277 Schedule {
1279 routine_id: String,
1281 binding_id: String,
1283 },
1284 Gateway,
1286 Client {
1288 client_id: String,
1290 },
1291}
1292
1293#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1295#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1296pub enum HookSelector {
1297 Schedule {
1299 schedule: RoutineSchedule,
1301 ends_at: Option<i64>,
1303 },
1304 Event {
1306 source: HookSource,
1308 kind: HookKind,
1310 routine_outcome: Option<RoutineRunStatus>,
1312 session_outcome: Option<mobius::backend::checkpoint::ExecutionOutcome>,
1314 custom_name: Option<String>,
1316 },
1317}
1318
1319#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1321#[serde(rename_all = "snake_case")]
1322pub enum HookKind {
1323 RoutineCreated,
1325 RoutineUpdated,
1327 RoutinePaused,
1329 RoutineResumed,
1331 RoutineDeleted,
1333 RunStarted,
1335 RunFinished,
1337 RunSkipped,
1339 ScheduleDue,
1341 SessionCreated,
1343 SessionTurnStarted,
1345 SessionTurnFinished,
1347 SessionApproval,
1349 SessionAttention,
1351 SessionDeleted,
1353 SessionOwnerChanged,
1355 ClientConnected,
1357 ClientDisconnected,
1359 CustomReceived,
1361}
1362
1363#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1365#[serde(deny_unknown_fields)]
1366pub struct HookEvent {
1367 pub id: String,
1369 pub source: HookSource,
1371 pub cause_id: Option<String>,
1373 pub ancestry: Vec<String>,
1375 pub bot_id: String,
1377 pub occurred_at: i64,
1379 pub data: HookData,
1381}
1382
1383#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1385#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1386pub enum HookData {
1387 RoutineCreated {
1389 routine_id: String,
1391 },
1392 RoutineUpdated {
1394 routine_id: String,
1396 },
1397 RoutinePaused {
1399 routine_id: String,
1401 },
1402 RoutineResumed {
1404 routine_id: String,
1406 },
1407 RoutineDeleted {
1409 routine_id: String,
1411 },
1412 RunStarted {
1414 routine_id: String,
1416 run_id: String,
1418 session_id: Option<String>,
1420 },
1421 RunFinished {
1423 routine_id: String,
1425 run_id: String,
1427 status: RoutineRunStatus,
1429 session_id: Option<String>,
1431 reason: Option<String>,
1433 },
1434 RunSkipped {
1436 routine_id: String,
1438 run_id: String,
1440 reason: String,
1442 },
1443 ScheduleDue {
1445 binding_id: String,
1447 },
1448 SessionCreated {
1450 session_id: String,
1452 },
1453 SessionTurnStarted {
1455 session_id: String,
1457 turn_id: String,
1459 },
1460 SessionTurnFinished {
1462 session_id: String,
1464 turn_id: String,
1466 outcome: mobius::backend::checkpoint::ExecutionOutcome,
1468 },
1469 SessionApproval {
1471 session_id: String,
1473 turn_id: String,
1475 request_id: String,
1477 },
1478 SessionAttention {
1480 session_id: String,
1482 capability: String,
1484 item_id: String,
1486 text: String,
1488 },
1489 SessionDeleted {
1491 session_id: String,
1493 },
1494 SessionOwnerChanged {
1496 session_id: String,
1498 previous_bot_id: String,
1500 },
1501 ClientConnected {
1503 client_id: String,
1505 },
1506 ClientDisconnected {
1508 client_id: String,
1510 },
1511 CustomReceived {
1513 name: String,
1515 data: serde_json::Value,
1517 },
1518}
1519
1520impl HookData {
1521 #[must_use]
1523 pub fn kind(&self) -> HookKind {
1524 match self {
1525 Self::RoutineCreated { .. } => HookKind::RoutineCreated,
1526 Self::RoutineUpdated { .. } => HookKind::RoutineUpdated,
1527 Self::RoutinePaused { .. } => HookKind::RoutinePaused,
1528 Self::RoutineResumed { .. } => HookKind::RoutineResumed,
1529 Self::RoutineDeleted { .. } => HookKind::RoutineDeleted,
1530 Self::RunStarted { .. } => HookKind::RunStarted,
1531 Self::RunFinished { .. } => HookKind::RunFinished,
1532 Self::RunSkipped { .. } => HookKind::RunSkipped,
1533 Self::ScheduleDue { .. } => HookKind::ScheduleDue,
1534 Self::SessionCreated { .. } => HookKind::SessionCreated,
1535 Self::SessionTurnStarted { .. } => HookKind::SessionTurnStarted,
1536 Self::SessionTurnFinished { .. } => HookKind::SessionTurnFinished,
1537 Self::SessionApproval { .. } => HookKind::SessionApproval,
1538 Self::SessionAttention { .. } => HookKind::SessionAttention,
1539 Self::SessionDeleted { .. } => HookKind::SessionDeleted,
1540 Self::SessionOwnerChanged { .. } => HookKind::SessionOwnerChanged,
1541 Self::ClientConnected { .. } => HookKind::ClientConnected,
1542 Self::ClientDisconnected { .. } => HookKind::ClientDisconnected,
1543 Self::CustomReceived { .. } => HookKind::CustomReceived,
1544 }
1545 }
1546}
1547
1548#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1550#[serde(deny_unknown_fields)]
1551pub struct BotSubscription {
1552 pub bot_id: String,
1554 pub binding: HookBinding<BotAction>,
1556 pub enabled: bool,
1558}