mod types;
pub use types::{
DelegationStrategy, Subagent, SubagentCall, SubagentConfig, SubagentError,
SubagentOutput,
};
pub struct SubagentExecutor {
subagents: std::collections::HashMap<String, Subagent>,
strategy: DelegationStrategy,
}
impl SubagentExecutor {
pub fn new(strategy: DelegationStrategy) -> Self {
Self {
subagents: std::collections::HashMap::new(),
strategy,
}
}
pub fn register(&mut self, subagent: Subagent) -> Result<(), SubagentError> {
if self.subagents.contains_key(&subagent.name) {
return Err(SubagentError::AlreadyExists(subagent.name));
}
self.subagents.insert(subagent.name.clone(), subagent);
Ok(())
}
pub async fn execute(
&self,
name: &str,
input: &str,
) -> Result<SubagentOutput, SubagentError> {
let subagent = self
.subagents
.get(name)
.ok_or_else(|| SubagentError::NotFound(name.to_string()))?;
let system_prompt = format!(
"{}\n\nInstructions:\n{}",
subagent.description, subagent.instructions
);
let options = match (&subagent.model, subagent.max_turns) {
(Some(model), Some(max_turns)) => {
crate::types::config::ClaudeAgentOptions::builder()
.system_prompt(crate::types::config::SystemPrompt::Text(
system_prompt,
))
.allowed_tools(subagent.allowed_tools.clone())
.model(model.clone())
.max_turns(max_turns)
.build()
}
(Some(model), None) => {
crate::types::config::ClaudeAgentOptions::builder()
.system_prompt(crate::types::config::SystemPrompt::Text(
system_prompt,
))
.allowed_tools(subagent.allowed_tools.clone())
.model(model.clone())
.build()
}
(None, Some(max_turns)) => {
crate::types::config::ClaudeAgentOptions::builder()
.system_prompt(crate::types::config::SystemPrompt::Text(
system_prompt,
))
.allowed_tools(subagent.allowed_tools.clone())
.max_turns(max_turns)
.build()
}
(None, None) => {
crate::types::config::ClaudeAgentOptions::builder()
.system_prompt(crate::types::config::SystemPrompt::Text(
system_prompt,
))
.allowed_tools(subagent.allowed_tools.clone())
.build()
}
};
let messages = crate::query::query(input, Some(options))
.await
.map_err(|e| SubagentError::ExecutionFailed(format!("Query failed: {}", e)))?;
let json_messages = messages
.into_iter()
.map(|msg| serde_json::to_value(msg).map_err(|e| {
SubagentError::ExecutionFailed(format!("Failed to serialize message: {}", e))
}))
.collect::<Result<Vec<_>, _>>()?;
Ok(SubagentOutput {
subagent_name: name.to_string(),
messages: json_messages,
})
}
pub fn list_subagents(&self) -> Vec<String> {
self.subagents.keys().cloned().collect()
}
pub fn has_subagent(&self, name: &str) -> bool {
self.subagents.contains_key(name)
}
pub fn strategy(&self) -> &DelegationStrategy {
&self.strategy
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_executor_creation() {
let executor = SubagentExecutor::new(DelegationStrategy::Auto);
assert!(matches!(executor.strategy(), &DelegationStrategy::Auto));
}
#[test]
fn test_register_subagent() {
let mut executor = SubagentExecutor::new(DelegationStrategy::Auto);
let subagent = Subagent {
name: "test-agent".to_string(),
description: "Test agent".to_string(),
instructions: "Test instructions".to_string(),
allowed_tools: vec![],
max_turns: Some(5),
model: None,
};
assert!(executor.register(subagent).is_ok());
assert!(executor.has_subagent("test-agent"));
}
#[test]
fn test_register_duplicate_fails() {
let mut executor = SubagentExecutor::new(DelegationStrategy::Auto);
let subagent = Subagent {
name: "test-agent".to_string(),
description: "Test agent".to_string(),
instructions: "Test instructions".to_string(),
allowed_tools: vec![],
max_turns: Some(5),
model: None,
};
assert!(executor.register(subagent.clone()).is_ok());
assert!(matches!(
executor.register(subagent),
Err(SubagentError::AlreadyExists(_))
));
}
#[test]
fn test_list_subagents() {
let mut executor = SubagentExecutor::new(DelegationStrategy::Auto);
let subagent1 = Subagent {
name: "agent1".to_string(),
description: "Agent 1".to_string(),
instructions: "Instructions 1".to_string(),
allowed_tools: vec![],
max_turns: Some(5),
model: None,
};
let subagent2 = Subagent {
name: "agent2".to_string(),
description: "Agent 2".to_string(),
instructions: "Instructions 2".to_string(),
allowed_tools: vec![],
max_turns: Some(10),
model: Some("claude-sonnet-4".to_string()),
};
executor.register(subagent1).unwrap();
executor.register(subagent2).unwrap();
let names = executor.list_subagents();
assert_eq!(names.len(), 2);
assert!(names.contains(&"agent1".to_string()));
assert!(names.contains(&"agent2".to_string()));
}
#[test]
fn test_execute_not_found() {
let executor = SubagentExecutor::new(DelegationStrategy::Auto);
let result = tokio::runtime::Runtime::new()
.unwrap()
.block_on(executor.execute("nonexistent", "input"));
assert!(matches!(result, Err(SubagentError::NotFound(_))));
}
}