use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GatewayNotification {
Sessions,
Bots,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReadyPayload {
pub gateway_version: String,
pub machine_name: String,
pub bots: Vec<BotRecord>,
pub sessions: Vec<SessionRecord>,
pub background_approvals: Vec<BackgroundApproval>,
pub providers: Vec<ProviderStatus>,
pub provider_instances: Vec<ProviderInstance>,
pub bot_defaults: Option<VersionedAgentConfig>,
pub models: Vec<ModelChoice>,
pub model_providers: BTreeMap<String, String>,
pub middleware_features: Vec<MiddlewareFeature>,
pub extensions: Vec<ExtensionRecord>,
pub contributions: Vec<FrontendContribution>,
pub max_active_sessions: usize,
pub session_file_limits: SessionFileLimits,
pub revisions: BTreeMap<ReadySection, String>,
pub omitted: BTreeSet<ReadySection>,
}
impl ReadyPayload {
pub fn swap_section(&mut self, other: &mut Self, section: ReadySection) {
use std::mem::swap;
match section {
ReadySection::Config => {
swap(&mut self.providers, &mut other.providers);
swap(&mut self.provider_instances, &mut other.provider_instances);
swap(&mut self.bot_defaults, &mut other.bot_defaults);
swap(&mut self.models, &mut other.models);
swap(&mut self.model_providers, &mut other.model_providers);
swap(
&mut self.middleware_features,
&mut other.middleware_features,
);
swap(&mut self.extensions, &mut other.extensions);
swap(&mut self.contributions, &mut other.contributions);
}
ReadySection::Bots => swap(&mut self.bots, &mut other.bots),
ReadySection::Sessions => swap(&mut self.sessions, &mut other.sessions),
}
}
pub fn restore_omitted(&mut self, held: &mut Self) {
for section in std::mem::take(&mut self.omitted) {
self.swap_section(held, section);
}
}
pub fn update(&mut self, mut next: Self) {
next.restore_omitted(self);
*self = next;
}
#[must_use]
pub fn revision(&self, section: ReadySection) -> String {
match section {
ReadySection::Config => content_revision(&(
&self.providers,
&self.provider_instances,
&self.bot_defaults,
&self.models,
&self.model_providers,
&self.middleware_features,
&self.extensions,
&self.contributions,
)),
ReadySection::Bots => content_revision(&self.bots),
ReadySection::Sessions => content_revision(&self.sessions),
}
}
pub(crate) fn blank(&self) -> Self {
Self {
gateway_version: String::new(),
machine_name: String::new(),
bots: Vec::new(),
sessions: Vec::new(),
background_approvals: Vec::new(),
providers: Vec::new(),
provider_instances: Vec::new(),
bot_defaults: None,
models: Vec::new(),
model_providers: BTreeMap::new(),
middleware_features: Vec::new(),
extensions: Vec::new(),
contributions: Vec::new(),
max_active_sessions: self.max_active_sessions,
session_file_limits: self.session_file_limits,
revisions: BTreeMap::new(),
omitted: BTreeSet::new(),
}
}
}
#[must_use]
pub fn content_revision(value: &impl Serialize) -> String {
use sha2::Digest as _;
let mut hasher = sha2::Sha256::new();
let _ = serde_json::to_writer(DigestWriter(&mut hasher), value);
hasher.finalize()[..16]
.iter()
.map(|byte| format!("{byte:02x}"))
.collect()
}
struct DigestWriter<'a>(&'a mut sha2::Sha256);
impl std::io::Write for DigestWriter<'_> {
fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
sha2::Digest::update(self.0, bytes);
Ok(bytes.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ReadySection {
Config,
Bots,
Sessions,
}
pub const READY_SECTIONS: [ReadySection; 3] = [
ReadySection::Config,
ReadySection::Bots,
ReadySection::Sessions,
];
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct CatalogHint {
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub known: BTreeMap<ReadySection, String>,
#[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
pub skip: BTreeSet<ReadySection>,
}
impl CatalogHint {
#[must_use]
pub fn is_empty(&self) -> bool {
self.known.is_empty() && self.skip.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SessionSlot {
Unchanged(String),
Changed(Box<SessionRecord>),
}
pub fn apply_session_changes(sessions: &mut Vec<SessionRecord>, changes: Vec<SessionSlot>) {
let mut held: Vec<_> = std::mem::take(sessions).into_iter().map(Some).collect();
*sessions = changes
.into_iter()
.filter_map(|slot| match slot {
SessionSlot::Changed(session) => Some(*session),
SessionSlot::Unchanged(id) => held
.iter_mut()
.find(|held| held.as_ref().is_some_and(|held| held.session_id == id))
.and_then(Option::take),
})
.collect();
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct DiffTotals {
pub additions: u64,
pub deletions: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BackgroundApproval {
pub session_id: String,
pub bot_id: String,
pub turn_id: String,
pub request_id: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SessionReadyPayload {
pub active_turn_ids: Vec<String>,
pub pending_approvals: Vec<mobius::protocol::ExecApprovalRequestEvent>,
pub latest_sequence: u64,
pub next_before_sequence: Option<u64>,
pub workspace: Option<WorkspaceInfo>,
pub attached_folders: Vec<PathBuf>,
pub git: Option<GitStatus>,
pub session: SessionConfiguredEvent,
pub contributions: Vec<FrontendContribution>,
pub widgets: Vec<SessionWidget>,
pub tool_count: usize,
pub compaction_count: u64,
pub context_limit_tokens: Option<i64>,
pub active_message_delivery: mobius::protocol::ActiveMessageDelivery,
pub run_stats: RunStats,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SessionWidget {
pub capability: String,
pub item: FrontendWidget,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SessionRecord {
pub session_id: String,
pub session_context: mobius::protocol::SessionContext,
pub parent_session_id: Option<String>,
pub parent_sequence: Option<u64>,
pub sequence: u64,
pub first_user_message: Option<String>,
pub execution_stats: mobius::backend::checkpoint::ExecutionStats,
pub title: Option<String>,
pub pinned: bool,
pub activity: SessionActivity,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SessionActivity {
pub state: SessionActivityState,
pub turn_id: Option<String>,
pub approval_request_id: Option<String>,
pub started_at: Option<i64>,
pub last_outcome: Option<mobius::backend::checkpoint::ExecutionOutcome>,
pub message: Option<String>,
}
impl Default for SessionActivity {
fn default() -> Self {
Self {
state: SessionActivityState::Idle,
turn_id: None,
approval_request_id: None,
started_at: None,
last_outcome: None,
message: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SessionActivityState {
Idle,
Running,
AwaitingApproval,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WorkspaceInfo {
pub id: String,
pub path: PathBuf,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GitStatus {
pub current_branch: String,
pub branches: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SshIdentityRecord {
pub label: String,
pub algorithm: String,
pub fingerprint: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GitDiffScope {
Staged,
Unstaged,
Committed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WorkspaceFileScope {
Modified,
All,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WorkspaceFileRecord {
pub path: String,
pub size: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DirectoryListing {
pub path: PathBuf,
pub parent: Option<PathBuf>,
pub entries: Vec<DirectoryEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DirectoryEntry {
pub name: String,
pub path: PathBuf,
pub is_directory: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct VersionedAgentConfig {
pub revision: u64,
pub config: AgentComposition,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AgentComposition {
pub provider: ProviderConfig,
pub realtime_voice: Option<String>,
pub middleware: MiddlewareConfig,
pub extensions: BTreeSet<String>,
pub system_prompt: String,
pub max_model_steps: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ExtensionKind {
Skill,
Plugin,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ExtensionHookRecord {
pub event: String,
pub matcher: Option<String>,
pub command: String,
pub timeout_seconds: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ExtensionRecord {
pub id: String,
pub capability: String,
pub kind: ExtensionKind,
pub name: String,
pub description: String,
pub version: Option<String>,
pub source: String,
pub reference: Option<String>,
pub subdirectory: Option<String>,
pub resolved_revision: String,
pub digest: String,
pub skills: Vec<String>,
pub hooks: Vec<ExtensionHookRecord>,
pub hooks_trusted: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProviderConfig {
pub instance: String,
pub provider: String,
pub model: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub base_url: Option<String>,
pub endpoint_auth: ProviderEndpointAuth,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reasoning_effort: Option<String>,
pub web_search: HostedWebSearch,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProviderEndpointAuth {
ProviderDefault,
Credentialless,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProviderStatus {
pub provider: String,
pub label: String,
pub symbol: FrontendSymbol,
pub description: String,
pub model_ids_configurable: bool,
pub auth: ProviderAuthKind,
pub default_base_url: Option<String>,
pub default_api_key_env: Option<String>,
pub models: Vec<ProviderModel>,
pub web_search: Vec<FrontendSettingOption>,
pub tool_discovery: ToolDiscoveryMode,
pub custom_endpoint_tool_discovery: Option<ToolDiscoveryMode>,
pub realtime_voices: Vec<String>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProviderTint {
#[default]
Blue,
Teal,
Green,
Yellow,
Orange,
Red,
Purple,
White,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BotShape {
Circle,
Squircle,
Triangle,
Diamond,
Hexagon,
Star,
Flower,
}
impl BotShape {
pub const ALL: [Self; 7] = [
Self::Circle,
Self::Squircle,
Self::Triangle,
Self::Diamond,
Self::Hexagon,
Self::Star,
Self::Flower,
];
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProviderInstance {
pub label: String,
pub tint: ProviderTint,
pub configured: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub credential_hint: Option<String>,
pub selection: ProviderConfig,
pub model_ids: Vec<String>,
pub reasoning_efforts: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ClientKind {
Cli,
Macos,
Ios,
Ipados,
GatewayDashboard,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ClientStatus {
pub client_id: String,
pub label: String,
pub kinds: Vec<ClientKind>,
pub connections: usize,
}
impl ProviderStatus {
#[must_use]
pub fn realtime_voices(&self, base_url: Option<&str>) -> &[String] {
if mobius::backend::model::provider::uses_default_endpoint(
self.default_base_url.as_deref(),
base_url,
) {
&self.realtime_voices
} else {
&[]
}
}
#[must_use]
pub fn configurable_base_url(&self) -> bool {
self.default_base_url.is_some()
}
#[must_use]
pub fn default_model(&self) -> Option<&ProviderModel> {
self.models.first()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProviderModel {
pub id: String,
pub label: String,
pub description: String,
pub context_window: i64,
pub reasoning: Vec<ReasoningChoice>,
pub default_reasoning: Option<String>,
pub tool_discovery: ToolDiscoveryMode,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReasoningChoice {
pub id: String,
pub label: String,
pub description: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProviderAuthKind {
ApiKey,
DeviceCode,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MiddlewareConfig {
pub(crate) enabled: BTreeSet<String>,
pub settings: BTreeMap<String, BTreeMap<String, FrontendSettingValue>>,
}
impl MiddlewareConfig {
#[must_use]
pub fn disabled_by<'a>(
&self,
features: &'a [mobius::protocol::MiddlewareFeature],
id: &str,
selected_model: Option<&mobius::protocol::ModelChoice>,
) -> Option<&'a str> {
if let Some(capability) = features
.iter()
.find(|feature| feature.id == id)
.and_then(|feature| feature.required_model_capability)
&& selected_model.is_some_and(|model| !model.supports(capability))
{
return Some(match capability {
mobius::protocol::ModelCapability::ImageGeneration => {
"a model without image generation"
}
mobius::protocol::ModelCapability::RealtimeVoice => {
"a model without realtime voice"
}
});
}
features
.iter()
.filter(|feature| feature.required || self.enabled(&feature.id))
.find_map(|feature| {
feature.settings.iter().find_map(|setting| {
let mobius::protocol::FrontendSettingKind::Select { options, .. } =
&setting.kind
else {
return None;
};
let Some(FrontendSettingValue::String(value)) =
self.setting(&feature.id, &setting.id)
else {
return None;
};
options
.iter()
.find(|option| {
option.value == *value
&& option.disables.iter().any(|disabled| disabled == id)
})
.map(|option| option.label.as_str())
})
})
}
pub fn reconcile(
&mut self,
features: &[mobius::protocol::MiddlewareFeature],
selected_model: Option<&mobius::protocol::ModelChoice>,
) {
let excluded = self
.enabled
.iter()
.filter(|id| self.disabled_by(features, id, selected_model).is_some())
.cloned()
.collect::<Vec<_>>();
for id in excluded {
self.enabled.remove(&id);
}
}
#[must_use]
pub fn enabled(&self, id: &str) -> bool {
self.enabled.contains(id)
}
pub fn set_enabled(&mut self, id: impl Into<String>, enabled: bool) {
let id = id.into();
if enabled {
self.enabled.insert(id);
} else {
self.enabled.remove(&id);
}
}
#[must_use]
pub fn setting(&self, middleware: &str, setting: &str) -> Option<&FrontendSettingValue> {
self.settings.get(middleware)?.get(setting)
}
pub fn set_setting(
&mut self,
middleware: impl Into<String>,
setting: impl Into<String>,
value: Option<FrontendSettingValue>,
) {
let middleware = middleware.into();
let setting = setting.into();
if let Some(value) = value {
self.settings
.entry(middleware)
.or_default()
.insert(setting, value);
} else if let Some(settings) = self.settings.get_mut(&middleware) {
settings.remove(&setting);
if settings.is_empty() {
self.settings.remove(&middleware);
}
}
}
pub(crate) fn entries(&self) -> impl Iterator<Item = &str> {
self.enabled.iter().map(String::as_str)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RenderedPreview {
pub symbol: Option<FrontendSymbol>,
pub duration_ms: Option<u64>,
pub started_at_ms: Option<i64>,
pub id: String,
pub title: String,
pub subtitle: String,
pub page_id: String,
pub update: FrontendPreviewUpdate,
pub events: Vec<RenderedEvent>,
pub next: Option<Op>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RenderedEvent {
pub submission_id: Option<String>,
pub recorded_at_ms: i64,
pub event: EventMsg,
pub blocks: Vec<RenderedBlock>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RecordedEvent {
pub sequence: u64,
pub recorded_at_ms: i64,
pub event: Event,
pub stream_metrics: Vec<StreamMetrics>,
pub blocks: Vec<RenderedBlock>,
pub preview: Option<RenderedPreview>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProfileSnapshot {
pub user_name: Option<String>,
pub daily_usage: Vec<DailyUsage>,
pub provider_usage: Vec<ProviderUsage>,
pub run_stats: RunStats,
pub recent_run_groups: Vec<SessionRunGroup>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProviderUsage {
pub provider: String,
pub limits: Option<Vec<UsageLimit>>,
pub error: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SessionRunGroup {
pub session_id: String,
pub title: String,
pub runs: Vec<RunSummary>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RunStats {
#[serde(flatten)]
pub completed: mobius::backend::checkpoint::ExecutionStats,
pub active: Option<RunSummary>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RunSummary {
pub session_id: String,
pub submission_id: String,
pub turn_id: String,
pub started_at_ms: i64,
pub finished_at_ms: Option<i64>,
pub elapsed_ms: u64,
pub outcome: Option<mobius::backend::checkpoint::ExecutionOutcome>,
pub model_calls: u64,
pub tool_calls: u64,
pub failed_tool_calls: u64,
pub usage: TokenUsage,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DailyUsage {
pub unix_day: u64,
pub provider: String,
pub usage: TokenUsage,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BotRecord {
pub conversation_session_id: String,
pub id: String,
pub handle: String,
pub name: String,
pub description: String,
pub tint: ProviderTint,
pub shape: BotShape,
pub config: VersionedAgentConfig,
pub accepts_file_attachments: bool,
pub routine_interaction_policy: RoutineInteractionPolicy,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RoutineInteractionPolicy {
Unattended,
MayPauseForApproval,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Routine {
pub id: String,
pub bot_id: String,
pub workspace: PathBuf,
pub instructions: String,
pub bindings: Vec<RoutineBinding>,
pub enabled: bool,
pub finished: bool,
pub next_run_at: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RoutineSchedule {
pub kind: RoutineScheduleKind,
pub at: Option<i64>,
pub every_seconds: Option<u64>,
pub expression: Option<String>,
pub time_zone: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RoutineScheduleKind {
Once,
Interval,
Cron,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RoutineRunPreview {
pub routine: Routine,
pub run: RoutineRun,
pub records: Vec<RecordedEvent>,
pub next_before_sequence: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RoutineRun {
pub id: String,
pub routine_id: String,
pub bot_id: String,
pub started_at: i64,
pub finished_at: Option<i64>,
pub status: RoutineRunStatus,
pub session_id: Option<String>,
pub message: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RoutineRunStatus {
Running,
Succeeded,
Failed,
Skipped,
Cancelled,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RoutineDefinition {
pub workspace: PathBuf,
pub instructions: String,
pub bindings: Vec<RoutineBinding>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct HookBinding<A> {
pub id: String,
pub on: HookSelector,
pub action: A,
}
pub type RoutineBinding = HookBinding<RoutineAction>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
pub enum BotAction {
Report {
instruction: String,
},
Routine {
command: RoutineCommand,
},
Session {
session_id: String,
op: Box<mobius::protocol::Op>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
pub enum RoutineAction {
Start,
Stop {
run_id: String,
},
Pause,
Resume,
Delete,
Update {
definition: RoutineDefinition,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RoutineCommand {
pub routine_id: String,
pub action: RoutineAction,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
pub enum HookSource {
Bot {
bot_id: String,
},
Routine {
routine_id: String,
},
Session {
session_id: String,
},
Schedule {
routine_id: String,
binding_id: String,
},
Gateway,
Client {
client_id: String,
},
Custom {
source_id: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
pub enum HookSelector {
Schedule {
schedule: RoutineSchedule,
ends_at: Option<i64>,
},
Event {
source: HookSource,
kind: HookKind,
routine_outcome: Option<RoutineRunStatus>,
session_outcome: Option<mobius::backend::checkpoint::ExecutionOutcome>,
custom_name: Option<String>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum HookKind {
RoutineCreated,
RoutineUpdated,
RoutinePaused,
RoutineResumed,
RoutineDeleted,
RunStarted,
RunFinished,
RunSkipped,
ScheduleDue,
SessionCreated,
SessionTurnStarted,
SessionTurnFinished,
SessionApproval,
SessionDeleted,
SessionOwnerChanged,
ClientConnected,
ClientDisconnected,
CustomReceived,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct HookEvent {
pub id: String,
pub source: HookSource,
pub cause_id: Option<String>,
pub ancestry: Vec<String>,
pub bot_id: String,
pub occurred_at: i64,
pub data: HookData,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
pub enum HookData {
RoutineCreated {
routine_id: String,
},
RoutineUpdated {
routine_id: String,
},
RoutinePaused {
routine_id: String,
},
RoutineResumed {
routine_id: String,
},
RoutineDeleted {
routine_id: String,
},
RunStarted {
routine_id: String,
run_id: String,
session_id: Option<String>,
},
RunFinished {
routine_id: String,
run_id: String,
status: RoutineRunStatus,
session_id: Option<String>,
reason: Option<String>,
},
RunSkipped {
routine_id: String,
run_id: String,
reason: String,
},
ScheduleDue {
binding_id: String,
},
SessionCreated {
session_id: String,
},
SessionTurnStarted {
session_id: String,
turn_id: String,
},
SessionTurnFinished {
session_id: String,
turn_id: String,
outcome: mobius::backend::checkpoint::ExecutionOutcome,
},
SessionApproval {
session_id: String,
turn_id: String,
request_id: String,
},
SessionDeleted {
session_id: String,
},
SessionOwnerChanged {
session_id: String,
previous_bot_id: String,
},
ClientConnected {
client_id: String,
},
ClientDisconnected {
client_id: String,
},
CustomReceived {
name: String,
data: serde_json::Value,
},
}
impl HookData {
#[must_use]
pub fn kind(&self) -> HookKind {
match self {
Self::RoutineCreated { .. } => HookKind::RoutineCreated,
Self::RoutineUpdated { .. } => HookKind::RoutineUpdated,
Self::RoutinePaused { .. } => HookKind::RoutinePaused,
Self::RoutineResumed { .. } => HookKind::RoutineResumed,
Self::RoutineDeleted { .. } => HookKind::RoutineDeleted,
Self::RunStarted { .. } => HookKind::RunStarted,
Self::RunFinished { .. } => HookKind::RunFinished,
Self::RunSkipped { .. } => HookKind::RunSkipped,
Self::ScheduleDue { .. } => HookKind::ScheduleDue,
Self::SessionCreated { .. } => HookKind::SessionCreated,
Self::SessionTurnStarted { .. } => HookKind::SessionTurnStarted,
Self::SessionTurnFinished { .. } => HookKind::SessionTurnFinished,
Self::SessionApproval { .. } => HookKind::SessionApproval,
Self::SessionDeleted { .. } => HookKind::SessionDeleted,
Self::SessionOwnerChanged { .. } => HookKind::SessionOwnerChanged,
Self::ClientConnected { .. } => HookKind::ClientConnected,
Self::ClientDisconnected { .. } => HookKind::ClientDisconnected,
Self::CustomReceived { .. } => HookKind::CustomReceived,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BotSubscription {
pub bot_id: String,
pub binding: HookBinding<BotAction>,
pub enabled: bool,
}