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