1pub mod ask;
2pub mod bash;
3pub mod browser;
4pub mod codegraph;
5pub mod code_quality_hook; pub mod edit;
7pub mod glob;
8pub mod grep;
9pub mod ls;
10pub mod monitor;
11pub mod multi_edit;
12pub mod plan_mode;
13pub mod read;
14pub mod read_history; pub mod registry; pub mod search;
17pub mod skill;
18pub mod subagent_executor; pub mod task;
20pub mod todo_write;
21pub mod tool_hooks; pub mod toolproxy; pub mod verify; pub mod webfetch;
25pub mod websearch;
26pub mod workflow;
27pub mod write;
28
29pub use read_history::{MustReadFirstError, ReadHistoryTracker};
31
32pub use toolproxy::{
34 ProxyMetadata, ProxyTool, ProxyToolDef, ProxyToolExecutor, ProxyToolRequest, ProxyToolResponse,
35};
36
37use std::sync::Arc;
38
39use anyhow::Result;
40use async_trait::async_trait;
41use serde::{Deserialize, Serialize};
42use serde_json::{Value, json};
43
44use crate::approval::RiskLevel;
45use crate::skills::Skill;
46use std::path::PathBuf;
47
48#[derive(Debug, Clone, Default)]
51pub struct ToolContext {
52 pub codegraph_available: bool,
54}
55
56pub type BoxedTool = Box<dyn Tool>;
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct ToolDefinition {
61 pub name: String,
62 pub description: String,
63 pub parameters: Value,
64 #[serde(default)]
67 pub is_priority: bool,
68}
69
70impl Default for ToolDefinition {
71 fn default() -> Self {
72 Self {
73 name: String::new(),
74 description: String::new(),
75 parameters: json!({"type": "object"}),
76 is_priority: false,
77 }
78 }
79}
80
81impl ToolDefinition {
82 pub fn description_for_llm(&self) -> String {
84 if self.is_priority {
85 format!("[优先] {}", self.description)
86 } else {
87 self.description.clone()
88 }
89 }
90}
91
92#[async_trait]
93pub trait Tool: Send + Sync {
94 fn definition(&self) -> ToolDefinition;
96
97 fn definition_with_context(&self, _ctx: &ToolContext) -> ToolDefinition {
103 self.definition()
104 }
105
106 async fn execute(&self, params: Value) -> Result<String>;
107
108 fn risk_level(&self) -> RiskLevel {
111 RiskLevel::Safe
112 }
113}
114
115pub fn all_tools() -> Vec<Box<dyn Tool>> {
118 all_tools_with_skills(Arc::new(Vec::new()))
119}
120
121fn base_tools(skills: Arc<Vec<Skill>>) -> Vec<Box<dyn Tool>> {
123 vec![
124 Box::new(ask::AskTool),
125 Box::new(read::ReadTool),
126 Box::new(write::WriteTool::new()),
127 Box::new(edit::EditTool),
128 Box::new(multi_edit::MultiEditTool),
129 Box::new(search::SearchTool),
130 Box::new(grep::GrepTool),
131 Box::new(glob::GlobTool),
132 Box::new(ls::LsTool),
133 Box::new(bash::BashTool),
134 Box::new(browser::BrowserOpenTool),
135 Box::new(todo_write::TodoWriteTool),
136 Box::new(websearch::WebSearchTool::new()),
137 Box::new(webfetch::WebFetchTool),
138 Box::new(skill::SkillTool::new(skills)),
139 Box::new(task::TaskTool),
140 Box::new(task::TaskCreateTool),
141 Box::new(task::TaskGetTool),
142 Box::new(task::TaskListTool),
143 Box::new(task::TaskStopTool),
144 Box::new(plan_mode::EnterPlanModeTool),
145 Box::new(plan_mode::ExitPlanModeTool),
146 Box::new(monitor::MonitorTool),
147 ]
148}
149
150pub fn all_tools_with_skills(skills: Arc<Vec<Skill>>) -> Vec<Box<dyn Tool>> {
152 let mut tools = base_tools(skills);
153 tools.extend(workflow::workflow_tools());
155 tools
156}
157
158pub fn all_tools_with_provider(
160 skills: Arc<Vec<Skill>>,
161 provider: Arc<dyn crate::providers::Provider>,
162) -> Vec<Box<dyn Tool>> {
163 let mut tools = base_tools(skills);
164 tools.extend(workflow::workflow_tools_with_provider(provider));
166 tools
167}
168
169pub fn generate_tools_prompt() -> String {
171 generate_tools_prompt_with_path_and_lsp(None, None)
172}
173
174pub fn generate_tools_prompt_with_path(project_path: Option<&PathBuf>) -> String {
176 generate_tools_prompt_with_path_and_lsp(project_path, None)
177}
178
179pub fn generate_tools_prompt_with_path_and_lsp(
181 project_path: Option<&PathBuf>,
182 lsp_registry: Option<Arc<crate::lsp::LspClientRegistry>>,
183) -> String {
184 let ctx = ToolContext {
186 codegraph_available: project_path
187 .map(|p| codegraph::should_inject_codegraph_tools(p))
188 .unwrap_or(false),
189 };
190
191 let mut tools = base_tools(Arc::new(Vec::new()));
192
193 if ctx.codegraph_available {
195 if let Some(path) = project_path {
196 tools.extend(codegraph::codegraph_tools_with_auto_detect(path));
197 }
198 }
199
200 if let Some(registry) = lsp_registry {
202 tools.extend(crate::lsp::tools::lsp_tools(registry));
203 }
204
205 tools.extend(workflow::workflow_tools());
207
208 let mut priority_tools = Vec::new();
210 let mut normal_tools = Vec::new();
211
212 for tool in tools {
213 let def = tool.definition_with_context(&ctx);
215 if def.is_priority {
216 priority_tools.push(def);
217 } else {
218 normal_tools.push(def);
219 }
220 }
221
222 let mut lines = vec!["可用工具:".to_string()];
223
224 if !priority_tools.is_empty() {
226 lines.push("\n【优先工具 - 必须优先考虑】".to_string());
227 for def in priority_tools {
228 let full_desc = def.description_for_llm();
230 let desc = full_desc.split('\n').next().unwrap_or(&full_desc);
232 if desc.len() > 150 {
233 lines.push(format!(
234 " {}: {}...",
235 def.name,
236 desc.chars().take(147).collect::<String>()
237 ));
238 } else {
239 lines.push(format!(" {}: {}", def.name, desc));
240 }
241 }
242 }
243
244 if !normal_tools.is_empty() {
246 lines.push("\n【其他工具】".to_string());
247 for def in normal_tools {
248 let desc = def
250 .description
251 .split('.')
252 .next()
253 .or_else(|| def.description.split('\n').next())
254 .unwrap_or(&def.description);
255 if desc.len() > 60 {
256 lines.push(format!(
257 " {}: {}...",
258 def.name,
259 desc.chars().take(57).collect::<String>()
260 ));
261 } else {
262 lines.push(format!(" {}: {}", def.name, desc));
263 }
264 }
265 }
266
267 lines.join("\n")
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273 use std::path::PathBuf;
274
275 #[test]
276 fn test_all_tools_includes_workflow_tools() {
277 let tools = all_tools();
278 let tool_names: Vec<String> = tools.iter().map(|t| t.definition().name).collect();
279
280 assert!(
282 tool_names.contains(&"workflow_discover".to_string()),
283 "workflow_discover should be in tools"
284 );
285 assert!(
286 tool_names.contains(&"workflow_run".to_string()),
287 "workflow_run should be in tools"
288 );
289 assert!(
290 tool_names.contains(&"workflow_match".to_string()),
291 "workflow_match should be in tools"
292 );
293 }
294
295 #[test]
296 fn test_generate_tools_prompt_includes_workflow() {
297 let prompt = generate_tools_prompt();
298
299 assert!(
301 prompt.contains("workflow_discover"),
302 "prompt should mention workflow_discover"
303 );
304 assert!(
305 prompt.contains("workflow_run"),
306 "prompt should mention workflow_run"
307 );
308 assert!(
309 prompt.contains("workflow_match"),
310 "prompt should mention workflow_match"
311 );
312 }
313
314 #[test]
315 fn test_generate_tools_prompt_with_path_includes_codegraph() {
316 let path = PathBuf::from(".");
317 let prompt = generate_tools_prompt_with_path(Some(&path));
318
319 if codegraph::should_inject_codegraph_tools(&path) {
324 assert!(
325 prompt.contains("code_search"),
326 "prompt should mention code_search when conditions met"
327 );
328 assert!(
329 prompt.contains("code_callers"),
330 "prompt should mention code_callers when conditions met"
331 );
332 } else {
333 assert!(
335 !prompt.contains("code_search"),
336 "prompt should NOT mention code_search without .codegraph"
337 );
338 }
339 }
340
341 #[test]
342 fn test_generate_tools_prompt_without_path_excludes_codegraph() {
343 let prompt = generate_tools_prompt();
344
345 assert!(
347 !prompt.contains("code_search"),
348 "prompt should NOT mention code_search without path"
349 );
350 }
351
352 #[test]
353 fn test_tool_context_affects_grep_description() {
354 use crate::tools::grep::GrepTool;
355
356 let ctx_no_codegraph = ToolContext {
358 codegraph_available: false,
359 };
360 let def_no_cg = GrepTool.definition_with_context(&ctx_no_codegraph);
361 assert!(
362 def_no_cg.description.contains("用 grep 搜索"),
363 "Without CodeGraph, grep should suggest using grep for definitions"
364 );
365 assert!(
366 !def_no_cg.description.contains("code_search"),
367 "Without CodeGraph, grep description should not mention code_search"
368 );
369
370 let ctx_with_codegraph = ToolContext {
372 codegraph_available: true,
373 };
374 let def_with_cg = GrepTool.definition_with_context(&ctx_with_codegraph);
375 assert!(
376 def_with_cg.description.contains("code_search"),
377 "With CodeGraph, grep should recommend code_search"
378 );
379 assert!(
380 def_with_cg.description.contains("快10-100倍"),
381 "With CodeGraph, grep should mention speed advantage"
382 );
383 }
384
385 #[test]
386 fn test_tool_context_affects_search_description() {
387 use crate::tools::search::SearchTool;
388
389 let ctx_no_codegraph = ToolContext {
391 codegraph_available: false,
392 };
393 let def_no_cg = SearchTool.definition_with_context(&ctx_no_codegraph);
394 assert!(
395 def_no_cg.description.contains("search 的适用场景"),
396 "Without CodeGraph, search should show its own applicable scenarios"
397 );
398
399 let ctx_with_codegraph = ToolContext {
401 codegraph_available: true,
402 };
403 let def_with_cg = SearchTool.definition_with_context(&ctx_with_codegraph);
404 assert!(
405 def_with_cg.description.contains("优先使用 code_search"),
406 "With CodeGraph, search should mention code_search priority"
407 );
408 }
409
410 #[test]
411 fn test_tool_context_affects_glob_description() {
412 use crate::tools::glob::GlobTool;
413
414 let ctx_no_codegraph = ToolContext {
416 codegraph_available: false,
417 };
418 let def_no_cg = GlobTool.definition_with_context(&ctx_no_codegraph);
419 assert!(
420 def_no_cg.description.contains("glob 的适用场景"),
421 "Without CodeGraph, glob should show its own applicable scenarios"
422 );
423
424 let ctx_with_codegraph = ToolContext {
426 codegraph_available: true,
427 };
428 let def_with_cg = GlobTool.definition_with_context(&ctx_with_codegraph);
429 assert!(
430 def_with_cg.description.contains("优先使用 code_files"),
431 "With CodeGraph, glob should mention code_files priority"
432 );
433 }
434
435 #[test]
436 fn test_generate_tools_prompt_dynamic_descriptions() {
437 let path = PathBuf::from(".");
438 let prompt = generate_tools_prompt_with_path(Some(&path));
439
440 if codegraph::should_inject_codegraph_tools(&path) {
442 assert!(
444 prompt.contains("code_search") || prompt.contains("grep"),
445 "Prompt should contain grep tool"
446 );
447 }
448
449 assert!(prompt.contains("grep"), "Prompt should contain grep tool");
451 assert!(
452 prompt.contains("search"),
453 "Prompt should contain search tool"
454 );
455 assert!(prompt.contains("glob"), "Prompt should contain glob tool");
456 }
457}
458
459pub fn all_tools_with_arc_provider(
461 skills: Arc<Vec<Skill>>,
462 provider: Arc<dyn crate::providers::Provider>,
463) -> Vec<Box<dyn Tool>> {
464 all_tools_with_provider(skills, provider)
465}
466
467pub fn all_tools_with_box_provider(
470 skills: Arc<Vec<Skill>>,
471 boxed_provider: Box<dyn crate::providers::Provider>,
472) -> Vec<Box<dyn Tool>> {
473 let arc_provider = boxed_provider.clone_arc();
475 all_tools_with_provider(skills, arc_provider)
476}
477
478pub fn all_tools_with_project_path(
480 skills: Arc<Vec<Skill>>,
481 project_path: PathBuf,
482) -> Vec<Box<dyn Tool>> {
483 all_tools_with_project_path_and_lsp(skills, project_path, None)
484}
485
486pub fn all_tools_with_project_path_and_lsp(
488 skills: Arc<Vec<Skill>>,
489 project_path: PathBuf,
490 lsp_registry: Option<Arc<crate::lsp::LspClientRegistry>>,
491) -> Vec<Box<dyn Tool>> {
492 let mut tools = base_tools(skills);
493 tools.extend(codegraph::codegraph_tools(&project_path));
495 if let Some(registry) = lsp_registry {
497 tools.extend(crate::lsp::tools::lsp_tools(registry));
498 }
499 tools.extend(workflow::workflow_tools());
501 tools
502}
503
504pub fn all_tools_full(
506 skills: Arc<Vec<Skill>>,
507 provider: Arc<dyn crate::providers::Provider>,
508 project_path: PathBuf,
509) -> Vec<Box<dyn Tool>> {
510 all_tools_full_with_lsp(skills, provider, project_path, None)
511}
512
513pub fn all_tools_full_with_lsp(
515 skills: Arc<Vec<Skill>>,
516 provider: Arc<dyn crate::providers::Provider>,
517 project_path: PathBuf,
518 lsp_registry: Option<Arc<crate::lsp::LspClientRegistry>>,
519) -> Vec<Box<dyn Tool>> {
520 let mut tools = base_tools(skills);
521 if codegraph::should_inject_codegraph_tools(&project_path) {
523 tools.extend(codegraph::codegraph_tools(&project_path));
524 }
525 if let Some(registry) = lsp_registry {
527 tools.extend(crate::lsp::tools::lsp_tools(registry));
528 }
529 tools.extend(workflow::workflow_tools_with_provider(provider));
531 tools
532}