use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Subagent {
pub name: String,
pub description: String,
pub instructions: String,
pub allowed_tools: Vec<String>,
pub max_turns: Option<u32>,
pub model: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubagentConfig {
pub subagents: Vec<Subagent>,
pub delegation_strategy: DelegationStrategy,
}
impl SubagentConfig {
pub fn new(delegation_strategy: DelegationStrategy) -> Self {
Self {
subagents: Vec::new(),
delegation_strategy,
}
}
pub fn add_subagent(&mut self, subagent: Subagent) {
self.subagents.push(subagent);
}
pub fn get_subagent(&self, name: &str) -> Option<&Subagent> {
self.subagents.iter().find(|s| s.name == name)
}
pub fn to_map(&self) -> HashMap<String, Subagent> {
self.subagents
.iter()
.map(|s| (s.name.clone(), s.clone()))
.collect()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DelegationStrategy {
Auto,
Manual,
ToolCall,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubagentCall {
pub subagent_name: String,
pub input: String,
pub output: Option<String>,
}
impl SubagentCall {
pub fn new(subagent_name: impl Into<String>, input: impl Into<String>) -> Self {
Self {
subagent_name: subagent_name.into(),
input: input.into(),
output: None,
}
}
pub fn is_executed(&self) -> bool {
self.output.is_some()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubagentOutput {
pub subagent_name: String,
pub messages: Vec<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SubagentError {
NotFound(String),
AlreadyExists(String),
ExecutionFailed(String),
InvalidInput(String),
}
impl std::fmt::Display for SubagentError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SubagentError::NotFound(name) => write!(f, "Subagent not found: {}", name),
SubagentError::AlreadyExists(name) => write!(f, "Subagent already exists: {}", name),
SubagentError::ExecutionFailed(msg) => write!(f, "Execution failed: {}", msg),
SubagentError::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
}
}
}
impl std::error::Error for SubagentError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_subagent_creation() {
let subagent = Subagent {
name: "test-agent".to_string(),
description: "Test".to_string(),
instructions: "Instructions".to_string(),
allowed_tools: vec!["Read".to_string()],
max_turns: Some(5),
model: Some("claude-sonnet-4".to_string()),
};
assert_eq!(subagent.name, "test-agent");
assert_eq!(subagent.max_turns, Some(5));
}
#[test]
fn test_subagent_config_new() {
let config = SubagentConfig::new(DelegationStrategy::Auto);
assert!(config.subagents.is_empty());
assert_eq!(config.delegation_strategy, DelegationStrategy::Auto);
}
#[test]
fn test_subagent_config_add() {
let mut config = SubagentConfig::new(DelegationStrategy::Manual);
let subagent = Subagent {
name: "agent".to_string(),
description: "Description".to_string(),
instructions: "Instructions".to_string(),
allowed_tools: vec![],
max_turns: None,
model: None,
};
config.add_subagent(subagent);
assert_eq!(config.subagents.len(), 1);
}
#[test]
fn test_subagent_config_get() {
let mut config = SubagentConfig::new(DelegationStrategy::Auto);
let subagent = Subagent {
name: "agent".to_string(),
description: "Description".to_string(),
instructions: "Instructions".to_string(),
allowed_tools: vec![],
max_turns: None,
model: None,
};
config.add_subagent(subagent);
assert!(config.get_subagent("agent").is_some());
assert!(config.get_subagent("nonexistent").is_none());
}
#[test]
fn test_subagent_config_to_map() {
let mut config = SubagentConfig::new(DelegationStrategy::ToolCall);
config.add_subagent(Subagent {
name: "agent1".to_string(),
description: "Agent 1".to_string(),
instructions: "Instructions 1".to_string(),
allowed_tools: vec![],
max_turns: None,
model: None,
});
config.add_subagent(Subagent {
name: "agent2".to_string(),
description: "Agent 2".to_string(),
instructions: "Instructions 2".to_string(),
allowed_tools: vec![],
max_turns: None,
model: None,
});
let map = config.to_map();
assert_eq!(map.len(), 2);
assert!(map.contains_key("agent1"));
assert!(map.contains_key("agent2"));
}
#[test]
fn test_delegation_strategy_equality() {
assert_eq!(DelegationStrategy::Auto, DelegationStrategy::Auto);
assert_ne!(DelegationStrategy::Auto, DelegationStrategy::Manual);
}
#[test]
fn test_subagent_call_new() {
let call = SubagentCall::new("agent", "input");
assert_eq!(call.subagent_name, "agent");
assert_eq!(call.input, "input");
assert!(!call.is_executed());
}
#[test]
fn test_subagent_call_executed() {
let mut call = SubagentCall::new("agent", "input");
assert!(!call.is_executed());
call.output = Some("output".to_string());
assert!(call.is_executed());
}
#[test]
fn test_subagent_error_display() {
let error = SubagentError::NotFound("agent".to_string());
assert_eq!(format!("{}", error), "Subagent not found: agent");
let error = SubagentError::AlreadyExists("agent".to_string());
assert_eq!(format!("{}", error), "Subagent already exists: agent");
}
#[test]
fn test_subagent_output() {
let output = SubagentOutput {
subagent_name: "agent".to_string(),
messages: vec![],
};
assert_eq!(output.subagent_name, "agent");
assert!(output.messages.is_empty());
}
}