use std::path::PathBuf;
use oino_ai::{AgentMessage, ThinkingLevel};
use serde::{Deserialize, Serialize};
pub const DEFAULT_MAX_DEPTH: u8 = 2;
pub const HARD_MAX_DEPTH: u8 = 4;
pub const DEFAULT_MAX_CONCURRENT: usize = 8;
pub const HARD_MAX_CONCURRENT: usize = 16;
pub const DEFAULT_HIBERNATE_AFTER_SEC: u64 = 120;
pub const CLOSE_GRACE_CAP: std::time::Duration = std::time::Duration::from_secs(5);
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum SystemPromptSpec {
#[default]
Default,
Replace(String),
Append(String),
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", default)]
pub struct ToolPolicy {
pub builtin: Option<Vec<String>>,
pub extension_tools: Vec<String>,
pub deny: Vec<String>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ExtensionPolicy {
#[default]
None,
Inherit,
Only(Vec<String>),
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum Seed {
#[default]
None,
BranchCopy,
SessionFile(PathBuf),
Messages(Vec<AgentMessage>),
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", rename_all_fields = "camelCase")]
pub enum Persistence {
#[default]
Memory,
File {
#[serde(default)]
session_dir: Option<PathBuf>,
},
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", rename_all_fields = "camelCase")]
pub enum UiPolicy {
Forward,
AutoDeny {
note: String,
},
#[default]
None,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", default)]
pub struct AgentSpec {
pub label: String,
pub model: Option<String>,
pub thinking: Option<ThinkingLevel>,
pub system_prompt: SystemPromptSpec,
pub tools: ToolPolicy,
pub extensions: ExtensionPolicy,
pub seed: Seed,
pub persistence: Persistence,
pub cwd: Option<PathBuf>,
pub child_role: Option<String>,
pub ui: UiPolicy,
pub max_depth: Option<u8>,
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn parses_guest_specs() {
let spec: AgentSpec = serde_json::from_value(json!({
"label": "Subagent", "model": "a/b", "thinking": "high",
"systemPrompt": {"append": "extra"},
"tools": {"builtin": ["read"], "extensionTools": ["*"], "deny": ["run_subagent"]},
"extensions": "inherit", "seed": {"sessionFile": "/s.jsonl"},
"persistence": {"file": {"sessionDir": "/d"}}, "childRole": "subagent",
"ui": {"autoDeny": {"note": "no"}}, "maxDepth": 1
}))
.unwrap();
assert_eq!(spec.thinking, Some(ThinkingLevel::High));
assert_eq!(spec.system_prompt, SystemPromptSpec::Append("extra".into()));
assert_eq!(spec.tools.builtin, Some(vec!["read".to_owned()]));
assert_eq!(spec.extensions, ExtensionPolicy::Inherit);
assert_eq!(spec.seed, Seed::SessionFile("/s.jsonl".into()));
assert_eq!(
spec.persistence,
Persistence::File {
session_dir: Some("/d".into())
}
);
assert_eq!(spec.ui, UiPolicy::AutoDeny { note: "no".into() });
let minimal: AgentSpec = serde_json::from_value(json!({
"label": "btw", "extensions": {"only": ["acme.memory"]}, "seed": "branchCopy",
"persistence": "memory", "ui": "forward"
}))
.unwrap();
assert_eq!(
minimal.extensions,
ExtensionPolicy::Only(vec!["acme.memory".into()])
);
assert_eq!(minimal.seed, Seed::BranchCopy);
assert_eq!(minimal.ui, UiPolicy::Forward);
assert_eq!(minimal.tools.builtin, None);
}
}