1use std::collections::HashSet;
2use std::sync::Arc;
3
4use crate::agents::catalog::AgentCatalog;
5use crate::chat::events::{ToolExecutionResult, ToolRequest as ToolRequestEvent, ToolRequestType};
6use crate::tools::r#trait::{
7 ContinuationPreference, ToolCallHandle, ToolCategory, ToolExecutor, ToolOutput, ToolRequest,
8};
9use crate::tools::ToolName;
10use anyhow::Result;
11use serde::{Deserialize, Serialize};
12use serde_json::{json, Value};
13
14#[derive(Debug, Serialize, Deserialize)]
15struct SpawnAgentParams {
16 task: String,
17 agent_type: String,
18}
19
20pub struct SpawnAgent {
21 catalog: Arc<AgentCatalog>,
22 allowed_agents: HashSet<String>,
23 current_agent: String,
24}
25
26impl SpawnAgent {
27 pub fn tool_name() -> ToolName {
28 ToolName::new("spawn_agent")
29 }
30
31 pub fn new(
32 catalog: Arc<AgentCatalog>,
33 allowed_agents: HashSet<String>,
34 current_agent: String,
35 ) -> Self {
36 Self {
37 catalog,
38 allowed_agents,
39 current_agent,
40 }
41 }
42}
43
44struct SpawnAgentHandle {
45 catalog: Arc<AgentCatalog>,
46 allowed_agents: HashSet<String>,
47 current_agent: String,
48 agent_type: String,
49 task: String,
50 tool_use_id: String,
51}
52
53#[async_trait::async_trait(?Send)]
54impl ToolCallHandle for SpawnAgentHandle {
55 fn tool_request(&self) -> ToolRequestEvent {
56 ToolRequestEvent {
57 tool_call_id: self.tool_use_id.clone(),
58 tool_name: "spawn_agent".to_string(),
59 tool_type: ToolRequestType::Other {
60 args: json!({
61 "agent_type": self.agent_type,
62 "task": self.task
63 }),
64 },
65 }
66 }
67
68 async fn execute(self: Box<Self>) -> ToolOutput {
69 if self.current_agent == self.agent_type {
71 return ToolOutput::Result {
72 content: format!(
73 "Cannot spawn agent of type '{}' from the same agent type. Use complete_task with failure instead.",
74 self.agent_type
75 ),
76 is_error: true,
77 continuation: ContinuationPreference::Continue,
78 ui_result: ToolExecutionResult::Error {
79 short_message: format!("Cannot spawn self ({})", self.agent_type),
80 detailed_message: format!(
81 "Agent '{}' cannot spawn another '{}'. Use complete_task with failure instead.",
82 self.agent_type, self.agent_type
83 ),
84 },
85 };
86 }
87
88 if !self.allowed_agents.contains(&self.agent_type) {
89 return ToolOutput::Result {
90 content: format!(
91 "Agent type '{}' not allowed. Allowed types: {:?}",
92 self.agent_type, self.allowed_agents
93 ),
94 is_error: true,
95 continuation: ContinuationPreference::Continue,
96 ui_result: ToolExecutionResult::Error {
97 short_message: format!("Agent type '{}' not allowed", self.agent_type),
98 detailed_message: format!(
99 "Cannot spawn '{}'. Allowed agent types: {:?}",
100 self.agent_type, self.allowed_agents
101 ),
102 },
103 };
104 }
105
106 match self.catalog.create_agent(&self.agent_type) {
107 Some(agent) => ToolOutput::PushAgent {
108 agent,
109 task: self.task,
110 },
111 None => ToolOutput::Result {
112 content: format!("Unknown agent type: {}", self.agent_type),
113 is_error: true,
114 continuation: ContinuationPreference::Continue,
115 ui_result: ToolExecutionResult::Error {
116 short_message: format!("Unknown agent: {}", self.agent_type),
117 detailed_message: format!(
118 "Agent type '{}' not found in catalog. Available: {:?}",
119 self.agent_type,
120 self.catalog.get_agent_names()
121 ),
122 },
123 },
124 }
125 }
126}
127
128#[async_trait::async_trait(?Send)]
129impl ToolExecutor for SpawnAgent {
130 fn name(&self) -> String {
131 "spawn_agent".to_string()
132 }
133
134 fn description(&self) -> String {
135 let mut agents: Vec<&str> = self.allowed_agents.iter().map(|s| s.as_str()).collect();
136 agents.sort();
137 format!(
138 "Spawn a sub-agent to handle a specific task. Available agent types: {}. The sub-agent starts with fresh context and runs to completion. Use this to break complex tasks into focused subtasks. WARNING: Never use this to work around failures - if you're a sub-agent and get stuck, use complete_task with failure instead to let the parent handle it.",
139 agents.join(", ")
140 )
141 }
142
143 fn input_schema(&self) -> Value {
144 json!({
145 "type": "object",
146 "required": ["task", "agent_type"],
147 "properties": {
148 "task": {
149 "type": "string",
150 "description": "Clear, specific description of what the sub-agent should accomplish. Include any relevant context, constraints, or guidance."
151 },
152 "agent_type": {
153 "type": "string",
154 "description": "Type of agent to spawn"
155 }
156 }
157 })
158 }
159
160 fn category(&self) -> ToolCategory {
161 ToolCategory::Meta
162 }
163
164 async fn process(&self, request: &ToolRequest) -> Result<Box<dyn ToolCallHandle>> {
165 let params: SpawnAgentParams = serde_json::from_value(request.arguments.clone())?;
166
167 Ok(Box::new(SpawnAgentHandle {
168 catalog: self.catalog.clone(),
169 allowed_agents: self.allowed_agents.clone(),
170 current_agent: self.current_agent.clone(),
171 agent_type: params.agent_type,
172 task: params.task,
173 tool_use_id: request.tool_use_id.clone(),
174 }))
175 }
176}