1use std::sync::Arc;
7
8use anyhow::Result;
9use async_trait::async_trait;
10use serde_json::{Value, json};
11
12use super::{Tool, ToolDefinition, subagent_tools_arc};
13use crate::tools::subagent_executor::{SubagentExecutor, SubagentTask, SubagentConfig};
14use crate::event::AgentEvent;
15use crate::skills::Skill;
16use tokio::sync::mpsc;
17
18pub struct AgentTool {
20 event_tx: Option<mpsc::Sender<AgentEvent>>,
22 skills: Arc<Vec<Skill>>,
24}
25
26impl AgentTool {
27 pub fn new() -> Self {
29 Self {
30 event_tx: None,
31 skills: Arc::new(Vec::new()),
32 }
33 }
34
35 pub fn with_event_tx(event_tx: mpsc::Sender<AgentEvent>) -> Self {
37 Self {
38 event_tx: Some(event_tx),
39 skills: Arc::new(Vec::new()),
40 }
41 }
42
43 pub fn with_skills(skills: Arc<Vec<Skill>>) -> Self {
45 Self {
46 event_tx: None,
47 skills,
48 }
49 }
50
51 pub fn with_event_tx_and_skills(
53 event_tx: mpsc::Sender<AgentEvent>,
54 skills: Arc<Vec<Skill>>,
55 ) -> Self {
56 Self {
57 event_tx: Some(event_tx),
58 skills,
59 }
60 }
61}
62
63impl Default for AgentTool {
64 fn default() -> Self {
65 Self::new()
66 }
67}
68
69#[async_trait]
70impl Tool for AgentTool {
71 fn definition(&self) -> ToolDefinition {
72 ToolDefinition {
73 name: "Agent".to_string(),
74 description: "启动一个新的子代理来处理复杂、多步骤任务。每个子代理类型有特定能力和工具。
75
76可用的子代理类型:
77- claude: 通用代理,适合任何任务(默认)
78- Explore: 只读搜索代理,用于广泛搜索文件/目录/命名约定
79- Plan: 软件架构代理,用于设计实现计划
80- general-purpose: 通用代理,用于复杂搜索和多步骤任务
81
82使用场景:
83- 当任务匹配可用代理类型时
84- 当有独立工作可并行运行时
85- 当回答需要跨多个文件搜索时
86
87重要:
88- 代理的最终消息作为工具结果返回
89- 子代理类型通过 subagent_type 参数选择
90- 可选 isolation: \"worktree\" 创建独立 git 工树
91- 可选 run_in_background: true 异步运行代理".to_string(),
92 parameters: json!({
93 "type": "object",
94 "properties": {
95 "subagent_type": {
96 "type": "string",
97 "description": "子代理类型(默认 general-purpose)",
98 "enum": ["claude", "Explore", "Plan", "general-purpose"],
99 "default": "general-purpose"
100 },
101 "description": {
102 "type": "string",
103 "description": "任务的简短描述(3-5词)"
104 },
105 "prompt": {
106 "type": "string",
107 "description": "子代理要执行的任务"
108 },
109 "isolation": {
110 "type": "string",
111 "enum": ["worktree"],
112 "description": "隔离模式。\"worktree\" 创建临时 git 工树"
113 },
114 "run_in_background": {
115 "type": "boolean",
116 "default": false,
117 "description": "设为 true 异步运行代理"
118 }
119 },
120 "required": ["description", "prompt"]
121 }),
122 ..Default::default()
123 }
124 }
125
126 async fn execute(&self, params: Value) -> Result<String> {
127 let subagent_type = params["subagent_type"]
128 .as_str()
129 .unwrap_or("general-purpose");
130
131 let description = params["description"]
132 .as_str()
133 .ok_or_else(|| anyhow::anyhow!("missing 'description'"))?;
134
135 let prompt = params["prompt"]
136 .as_str()
137 .ok_or_else(|| anyhow::anyhow!("missing 'prompt'"))?;
138
139 let isolation = params["isolation"]
140 .as_str()
141 .unwrap_or("none");
142
143 let run_in_background = params["run_in_background"]
144 .as_bool()
145 .unwrap_or(false);
146
147 let task = SubagentTask {
149 id: uuid::Uuid::new_v4().to_string(),
150 description: description.to_string(),
151 prompt: prompt.to_string(),
152 subagent_type: subagent_type.to_string(),
153 isolation: isolation.to_string(),
154 work_path: None,
155 };
156
157 let (event_tx, _event_rx) = mpsc::channel::<AgentEvent>(100);
159 let tx = self.event_tx.clone().unwrap_or(event_tx);
160
161 let config = match subagent_type {
163 "Explore" => SubagentConfig {
164 model_name: "claude-sonnet-4-20250514".to_string(),
165 max_tokens: 4096,
166 system_prompt_prefix: Some("You are a fast, read-only search agent.".to_string()),
167 think: false,
168 tool_names: Some(vec![
169 "read".to_string(), "grep".to_string(), "glob".to_string(),
170 "ls".to_string(), "search".to_string(),
171 "code_search".to_string(), "code_callers".to_string(),
172 "code_callees".to_string(), "code_status".to_string(),
173 ]),
174 },
175 "Plan" => SubagentConfig {
176 model_name: "claude-sonnet-4-20250514".to_string(),
177 max_tokens: 8192,
178 system_prompt_prefix: Some("You are a software architecture planning agent.".to_string()),
179 think: true,
180 tool_names: Some(vec![
181 "read".to_string(), "grep".to_string(), "glob".to_string(),
182 "ls".to_string(), "search".to_string(),
183 "code_search".to_string(), "code_callers".to_string(),
184 "code_callees".to_string(), "code_status".to_string(),
185 "enter_plan_mode".to_string(), "exit_plan_mode".to_string(),
186 "todo_write".to_string(),
187 ]),
188 },
189 _ => SubagentConfig {
190 model_name: "claude-sonnet-4-20250514".to_string(),
191 max_tokens: 4096,
192 system_prompt_prefix: None,
193 think: false,
194 tool_names: None,
195 },
196 };
197
198 if run_in_background {
199 let task_clone = task.clone();
201 let config_clone = config.clone();
202 let tools = subagent_tools_arc(self.skills.clone());
203
204 tokio::spawn(async move {
205 let mut executor = SubagentExecutor::new(config_clone, tx, tools);
206 let result = executor.execute(task_clone).await;
207
208 if let Ok(r) = result {
209 log::info!("Background agent task {} completed: success={}",
210 r.task_id, r.success);
211 }
212 });
213
214 Ok(format!("Agent task '{}' started in background. Task ID: {}",
215 description, task.id))
216 } else {
217 let tools = subagent_tools_arc(self.skills.clone());
219 let mut executor = SubagentExecutor::new(config, tx, tools);
220 let result = executor.execute(task).await?;
221
222 if result.success {
223 Ok(result.content)
224 } else {
225 Ok(format!("Agent task failed: {}", result.content))
226 }
227 }
228 }
229
230 fn risk_level(&self) -> crate::approval::RiskLevel {
231 crate::approval::RiskLevel::Mutating
233 }
234}