use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum ModelType {
#[default]
Sonnet,
Opus,
Haiku,
Inherit,
}
impl ModelType {
pub fn as_str(&self) -> &'static str {
match self {
ModelType::Sonnet => "sonnet",
ModelType::Opus => "opus",
ModelType::Haiku => "haiku",
ModelType::Inherit => "inherit",
}
}
}
impl std::fmt::Display for ModelType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum AgentCost {
Free,
Cheap,
Expensive,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AgentCategory {
Exploration,
Specialist,
Advisor,
Utility,
Orchestration,
Planner,
Reviewer,
}
impl AgentCategory {
pub fn default_model(&self) -> ModelType {
match self {
AgentCategory::Exploration => ModelType::Haiku, AgentCategory::Specialist => ModelType::Sonnet, AgentCategory::Advisor => ModelType::Opus, AgentCategory::Utility => ModelType::Haiku, AgentCategory::Orchestration => ModelType::Sonnet, AgentCategory::Planner => ModelType::Opus, AgentCategory::Reviewer => ModelType::Opus, }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DelegationTrigger {
pub domain: String,
pub trigger: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentPromptMetadata {
pub category: AgentCategory,
pub cost: AgentCost,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt_alias: Option<String>,
#[serde(default)]
pub triggers: Vec<DelegationTrigger>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub use_when: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub avoid_when: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt_description: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tools: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentConfig {
pub name: String,
pub description: String,
pub prompt: String,
pub tools: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<ModelType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_model: Option<ModelType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<AgentPromptMetadata>,
}
impl AgentConfig {
pub fn new(
name: impl Into<String>,
description: impl Into<String>,
prompt: impl Into<String>,
) -> Self {
Self {
name: name.into(),
description: description.into(),
prompt: prompt.into(),
tools: vec![],
model: None,
default_model: None,
metadata: None,
}
}
pub fn with_tools(mut self, tools: Vec<String>) -> Self {
self.tools = tools;
self
}
pub fn with_model(mut self, model: ModelType) -> Self {
self.model = Some(model);
self
}
pub fn with_metadata(mut self, metadata: AgentPromptMetadata) -> Self {
self.metadata = Some(metadata);
self
}
pub fn effective_model(&self) -> ModelType {
self.model
.or(self.default_model)
.unwrap_or(ModelType::Sonnet)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AgentOverrideConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt_append: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
}
pub type AgentOverrides = HashMap<String, AgentOverrideConfig>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum RoutingTier {
Low,
Medium,
High,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_model_type_serialize() {
let model = ModelType::Sonnet;
let json = serde_json::to_string(&model).unwrap();
assert_eq!(json, r#""sonnet""#);
}
#[test]
fn test_agent_category_default_model() {
assert_eq!(AgentCategory::Exploration.default_model(), ModelType::Haiku);
assert_eq!(AgentCategory::Advisor.default_model(), ModelType::Opus);
assert_eq!(AgentCategory::Specialist.default_model(), ModelType::Sonnet);
}
#[test]
fn test_agent_config_builder() {
let agent = AgentConfig::new(
"explore",
"Fast codebase search",
"You are an explore agent...",
)
.with_tools(vec![
"Read".to_string(),
"Glob".to_string(),
"Grep".to_string(),
])
.with_model(ModelType::Haiku);
assert_eq!(agent.name, "explore");
assert_eq!(agent.effective_model(), ModelType::Haiku);
assert_eq!(agent.tools.len(), 3);
}
}