1use serde::{Deserialize, Deserializer, Serialize};
4
5fn null_default<'de, D, T>(d: D) -> std::result::Result<T, D::Error>
7where
8 D: Deserializer<'de>,
9 T: Default + Deserialize<'de>,
10{
11 Ok(Option::<T>::deserialize(d)?.unwrap_or_default())
12}
13
14pub const BOSS_MAN: &str = "boss-man";
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
19#[serde(rename_all = "lowercase")]
20pub enum KernelPack {
21 Files,
22 Search,
23 Shell,
24 Web,
25 Memory,
26 Todo,
27 Agents,
28 Management,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
34#[serde(rename_all = "kebab-case")]
35pub enum PermissionProfile {
36 ReadOnly,
37 WorkspaceWrite,
38 DeveloperSafe,
39 Autonomous,
40 Manager,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
45pub enum BotStatus {
46 Ready,
47 Running,
48 Paused,
49 Archived,
50 Degraded,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
55pub enum TaskState {
56 Queued,
57 Preparing,
58 Running,
59 WaitingForTool,
60 WaitingForAgent,
61 WaitingForHuman,
62 Completed,
63 Failed,
64 Cancelled,
65 TimedOut,
66}
67
68impl TaskState {
69 pub fn is_terminal(self) -> bool {
71 matches!(
72 self,
73 Self::Completed | Self::Failed | Self::Cancelled | Self::TimedOut
74 )
75 }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
80pub enum AutoLearnMode {
81 Off,
82 MemoryOnly,
83 SuggestSkills,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
88pub enum ApprovalScope {
89 Once,
90 Session,
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
95pub enum ApprovalState {
96 Pending,
97 Approved,
98 Rejected,
99 Expired,
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
104pub enum MemoryKind {
105 Semantic,
106 Episodic,
107 Procedural,
108 Relational,
109 Artifact,
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
114pub enum EventType {
115 BotCreated,
116 BotUpdated,
117 BotDeleted,
118 BotStateChanged,
119 MessageAdded,
120 TaskCreated,
121 TaskStateChanged,
122 TaskDelegated,
123 TaskProgressed,
124 AgentThinkingStarted,
125 AgentThinkingCompleted,
126 ToolCallStarted,
127 ToolCallCompleted,
128 ApprovalRequested,
129 ApprovalResolved,
130 MemoryWritten,
131 SkillLoaded,
132 ContextCompacted,
133 AutoLearnCandidateCreated,
134 ScheduleTriggered,
135 HostConnected,
136 TodoUpdated,
137 SettingsChanged,
138 #[serde(other)]
139 Unknown,
140}
141
142pub struct ModelRef;
144
145impl ModelRef {
146 pub const DEFAULT: &'static str = "default";
148
149 pub fn of(provider: &str, model: &str) -> String {
154 assert!(
155 !provider.is_empty() && !model.is_empty() && !provider.contains('/'),
156 "provider and model are required; provider cannot contain '/'"
157 );
158 format!("{provider}/{model}")
159 }
160
161 pub fn profile(name: &str) -> String {
163 name.to_string()
164 }
165}
166
167#[derive(Debug, Clone, Deserialize)]
169#[serde(rename_all = "camelCase")]
170pub struct Bot {
171 pub id: String,
172 pub name: String,
173 #[serde(default, deserialize_with = "null_default")]
174 pub role: String,
175 #[serde(default, deserialize_with = "null_default")]
176 pub description: String,
177 #[serde(default, deserialize_with = "null_default")]
178 pub persona: String,
179 #[serde(default, deserialize_with = "null_default")]
180 pub color: String,
181 #[serde(rename = "modelProfile", default, deserialize_with = "null_default")]
183 pub model: String,
184 #[serde(default, deserialize_with = "null_default")]
185 pub kernel_functions: Vec<KernelPack>,
186 #[serde(default, deserialize_with = "null_default")]
187 pub skills: Vec<String>,
188 #[serde(default, deserialize_with = "null_default")]
189 pub mcp_servers: Vec<String>,
190 pub permission_profile: PermissionProfile,
191 pub auto_learn: AutoLearnMode,
192 #[serde(default)]
193 pub short_term_memory: bool,
194 #[serde(default)]
195 pub long_term_memory: bool,
196 #[serde(default)]
197 pub max_steps: u32,
198 pub status: BotStatus,
199 #[serde(default)]
200 pub is_system: bool,
201 pub template_id: Option<String>,
202}
203
204impl Bot {
205 pub fn uses_default_model(&self) -> bool {
207 self.model.is_empty() || self.model == ModelRef::DEFAULT
208 }
209}
210
211#[derive(Debug, Clone, Serialize)]
213#[serde(rename_all = "camelCase")]
214pub struct BotSpec {
215 #[serde(skip_serializing_if = "String::is_empty")]
216 id: String,
217 name: String,
218 role: String,
219 description: String,
220 persona: String,
221 color: String,
222 #[serde(rename = "modelProfile")]
223 model: String,
224 kernel_functions: Vec<KernelPack>,
225 skills: Vec<String>,
226 mcp_servers: Vec<String>,
227 permission_profile: PermissionProfile,
228 auto_learn: AutoLearnMode,
229 short_term_memory: bool,
230 long_term_memory: bool,
231 max_steps: u32,
232}
233
234impl BotSpec {
235 pub fn new(name: impl Into<String>) -> Self {
237 Self {
238 id: String::new(),
239 name: name.into(),
240 role: String::new(),
241 description: String::new(),
242 persona: String::new(),
243 color: "#2C3BA3".into(),
244 model: ModelRef::DEFAULT.into(),
245 kernel_functions: vec![
246 KernelPack::Files,
247 KernelPack::Search,
248 KernelPack::Web,
249 KernelPack::Memory,
250 KernelPack::Todo,
251 ],
252 skills: Vec::new(),
253 mcp_servers: Vec::new(),
254 permission_profile: PermissionProfile::DeveloperSafe,
255 auto_learn: AutoLearnMode::Off,
256 short_term_memory: true,
257 long_term_memory: true,
258 max_steps: 24,
259 }
260 }
261 pub fn role(mut self, v: impl Into<String>) -> Self {
262 self.role = v.into();
263 self
264 }
265 pub fn description(mut self, v: impl Into<String>) -> Self {
266 self.description = v.into();
267 self
268 }
269 pub fn persona(mut self, v: impl Into<String>) -> Self {
270 self.persona = v.into();
271 self
272 }
273 pub fn color(mut self, v: impl Into<String>) -> Self {
274 self.color = v.into();
275 self
276 }
277 pub fn model(mut self, v: impl Into<String>) -> Self {
279 self.model = v.into();
280 self
281 }
282 pub fn kernel_functions(mut self, v: impl IntoIterator<Item = KernelPack>) -> Self {
283 self.kernel_functions = v.into_iter().collect();
284 self
285 }
286 pub fn skills(mut self, v: impl IntoIterator<Item = impl Into<String>>) -> Self {
287 self.skills = v.into_iter().map(Into::into).collect();
288 self
289 }
290 pub fn mcp_servers(mut self, v: impl IntoIterator<Item = impl Into<String>>) -> Self {
291 self.mcp_servers = v.into_iter().map(Into::into).collect();
292 self
293 }
294 pub fn permission_profile(mut self, v: PermissionProfile) -> Self {
295 self.permission_profile = v;
296 self
297 }
298 pub fn auto_learn(mut self, v: AutoLearnMode) -> Self {
299 self.auto_learn = v;
300 self
301 }
302 pub fn memory(mut self, short_term: bool, long_term: bool) -> Self {
303 self.short_term_memory = short_term;
304 self.long_term_memory = long_term;
305 self
306 }
307 pub fn max_steps(mut self, v: u32) -> Self {
308 self.max_steps = v;
309 self
310 }
311 pub(crate) fn with_id(mut self, id: &str) -> Self {
312 self.id = id.to_string();
313 self
314 }
315}
316
317#[derive(Debug, Clone, Deserialize)]
319#[serde(rename_all = "camelCase")]
320pub struct BotTemplate {
321 pub id: String,
322 pub name: String,
323 pub category: String,
324 #[serde(default, deserialize_with = "null_default")]
325 pub role: String,
326 #[serde(default, deserialize_with = "null_default")]
327 pub description: String,
328 #[serde(rename = "modelProfile", default, deserialize_with = "null_default")]
329 pub model: String,
330 #[serde(default, deserialize_with = "null_default")]
331 pub skills: Vec<String>,
332 #[serde(default, deserialize_with = "null_default")]
333 pub kernel_functions: Vec<KernelPack>,
334 #[serde(default, deserialize_with = "null_default")]
335 pub tags: Vec<String>,
336 pub permission_profile: PermissionProfile,
337 #[serde(default)]
338 pub is_built_in: bool,
339}
340
341#[derive(Debug, Clone, Deserialize)]
343#[serde(rename_all = "camelCase")]
344pub struct BotModelInfo {
345 pub bot_id: String,
346 pub setting: String,
347 pub effective: String,
349 pub uses_default: bool,
350 pub warning: Option<String>,
351}
352
353#[derive(Debug, Clone, Deserialize)]
355pub struct ModelProfileInfo {
356 pub name: String,
357 pub provider: String,
358 pub model: String,
359 #[serde(default, deserialize_with = "null_default")]
360 pub fallbacks: Vec<String>,
361}
362
363#[derive(Debug, Clone, Deserialize)]
365pub struct ModelCatalog {
366 pub default: String,
368 pub choices: Vec<String>,
369 pub profiles: Vec<ModelProfileInfo>,
370}
371
372#[derive(Debug, Clone, Deserialize)]
374#[serde(rename_all = "camelCase")]
375pub struct ChatThread {
376 pub id: String,
377 pub title: String,
378 pub bot_id: String,
379 #[serde(default)]
380 pub pinned: bool,
381 #[serde(default)]
382 pub archived: bool,
383}
384
385#[derive(Debug, Clone, Deserialize)]
387pub struct ToolCall {
388 pub id: String,
389 pub name: String,
390 pub arguments: String,
391}
392
393#[derive(Debug, Clone, Deserialize)]
395#[serde(rename_all = "camelCase")]
396pub struct ChatMessage {
397 pub id: String,
398 pub thread_id: String,
399 pub seq: i64,
400 pub role: String,
401 pub author: String,
402 pub content: String,
403 #[serde(default, deserialize_with = "null_default")]
404 pub tool_calls: Vec<ToolCall>,
405 pub tool_name: Option<String>,
406 pub task_id: Option<String>,
407}
408
409#[derive(Debug, Clone, Deserialize)]
411#[serde(rename_all = "camelCase")]
412pub struct TaskRecord {
413 pub id: String,
414 pub parent_task_id: Option<String>,
415 pub thread_id: String,
416 pub bot_id: String,
417 #[serde(default)]
418 pub depth: u32,
419 pub objective: String,
420 pub state: TaskState,
421 pub result: Option<String>,
422 pub error: Option<String>,
423 pub current_activity: Option<String>,
424 pub model: Option<String>,
426 #[serde(default)]
427 pub steps: u32,
428 #[serde(default)]
429 pub input_tokens: u64,
430 #[serde(default)]
431 pub output_tokens: u64,
432 #[serde(default)]
433 pub cost_usd: f64,
434}
435
436#[derive(Debug, Clone, Deserialize)]
438pub struct SendResult {
439 pub task: TaskRecord,
440 pub reply: Option<ChatMessage>,
441}
442
443impl SendResult {
444 pub fn text(&self) -> &str {
446 self.reply
447 .as_ref()
448 .map(|r| r.content.as_str())
449 .or(self.task.result.as_deref())
450 .or(self.task.error.as_deref())
451 .unwrap_or("")
452 }
453}
454
455#[derive(Debug, Clone, Deserialize)]
457pub struct WorkspaceFile {
458 pub path: String,
459 pub size: u64,
460}
461
462#[derive(Debug, Clone, Deserialize)]
464#[serde(rename_all = "camelCase")]
465pub struct ApprovalRequest {
466 pub id: String,
467 pub task_id: String,
468 pub thread_id: String,
469 pub bot_id: String,
470 pub tool_name: String,
471 pub arguments: String,
472 pub reason: String,
473 pub state: ApprovalState,
474 pub resolved_by: Option<String>,
475}
476
477#[derive(Debug, Clone, Deserialize)]
479#[serde(rename_all = "camelCase")]
480pub struct AgentEvent {
481 pub id: i64,
482 #[serde(rename = "type")]
483 pub event_type: EventType,
484 pub timestamp: String,
485 pub thread_id: Option<String>,
486 pub task_id: Option<String>,
487 pub bot_id: Option<String>,
488 pub message: Option<String>,
489 pub data: Option<String>,
490}
491
492impl AgentEvent {
493 pub fn is_task_finished(&self) -> bool {
495 self.event_type == EventType::TaskStateChanged
496 && self
497 .data
498 .as_deref()
499 .and_then(|d| {
500 serde_json::from_value::<TaskState>(serde_json::Value::String(d.to_string()))
501 .ok()
502 })
503 .is_some_and(TaskState::is_terminal)
504 }
505}
506
507#[derive(Debug, Clone, Deserialize)]
509pub struct MemoryRecord {
510 pub id: String,
511 pub owner: String,
512 pub kind: MemoryKind,
513 pub content: String,
514 pub source: String,
515 pub confidence: f64,
516}
517
518#[derive(Debug, Clone, Deserialize)]
520pub struct SkillInfo {
521 pub name: String,
522 pub version: String,
523 pub description: String,
524 pub trust: String,
525 pub source: String,
526 #[serde(default)]
527 pub pending: bool,
528}
529
530#[derive(Debug, Clone, Deserialize)]
532#[serde(rename_all = "camelCase")]
533pub struct McpServer {
534 pub id: String,
535 pub name: String,
536 pub description: String,
537 pub transport: String,
538 pub command: Option<String>,
539 #[serde(default, deserialize_with = "null_default")]
540 pub args: Vec<String>,
541 pub url: Option<String>,
542 pub trust: String,
543 #[serde(default)]
544 pub is_catalog_entry: bool,
545}
546
547#[derive(Debug, Clone, Serialize)]
549#[serde(rename_all = "camelCase")]
550pub struct ScheduleSpec {
551 pub name: String,
552 pub bot_id: String,
553 pub prompt: String,
554 pub cron: String,
555 #[serde(skip_serializing_if = "Option::is_none")]
556 pub run_at: Option<String>,
557 pub time_zone: String,
558}
559
560impl ScheduleSpec {
561 pub fn cron(
563 name: impl Into<String>,
564 bot_id: impl Into<String>,
565 prompt: impl Into<String>,
566 cron: impl Into<String>,
567 ) -> Self {
568 Self {
569 name: name.into(),
570 bot_id: bot_id.into(),
571 prompt: prompt.into(),
572 cron: cron.into(),
573 run_at: None,
574 time_zone: "UTC".into(),
575 }
576 }
577}
578
579#[derive(Debug, Clone, Deserialize)]
581#[serde(rename_all = "camelCase")]
582pub struct ScheduleJob {
583 pub id: String,
584 pub name: String,
585 pub bot_id: String,
586 pub prompt: String,
587 pub cron: String,
588 pub time_zone: String,
589 pub enabled: bool,
590 pub next_run_at: Option<String>,
591 pub last_run_at: Option<String>,
592}
593
594#[derive(Debug, Clone, Deserialize)]
596#[serde(rename_all = "camelCase")]
597pub struct HostInfo {
598 pub id: String,
599 pub name: String,
600 pub kind: String,
601 pub os: String,
602 pub status: String,
603 pub processor_count: u32,
604}
605
606#[derive(Debug, Clone, Deserialize)]
608#[serde(rename_all = "camelCase")]
609pub struct SystemInfo {
610 pub product: String,
611 pub version: String,
612 pub credits: String,
613 pub credits_en: String,
614 pub model_configured: bool,
615}