Skip to main content

matrixcode_core/tools/
mod.rs

1pub mod ask;
2pub mod bash;
3pub mod browser;
4pub mod codegraph;
5pub mod edit;
6pub mod glob;
7pub mod grep;
8pub mod ls;
9pub mod monitor;
10pub mod multi_edit;
11pub mod plan_mode;
12pub mod toolproxy;  // 代理工具模块
13pub mod read;
14pub mod registry;  // 工具注册中心
15pub mod search;
16pub mod skill;
17pub mod task;
18pub mod todo_write;
19pub mod webfetch;
20pub mod websearch;
21pub mod workflow;
22pub mod write;
23
24// Re-export proxy types for convenience
25pub use toolproxy::{ProxyToolExecutor, ProxyToolDef, ProxyMetadata, ProxyTool, ProxyToolResponse, ProxyToolRequest};
26
27use std::sync::Arc;
28
29use anyhow::Result;
30use async_trait::async_trait;
31use serde::{Deserialize, Serialize};
32use serde_json::{Value, json};
33
34use crate::approval::RiskLevel;
35use crate::skills::Skill;
36use std::path::PathBuf;
37
38/// Type alias for boxed tool
39pub type BoxedTool = Box<dyn Tool>;
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct ToolDefinition {
43    pub name: String,
44    pub description: String,
45    pub parameters: Value,
46    /// 是否为优先工具。true 时会在描述前添加 "[优先]" 提示,
47    /// 让 LLM 更倾向选择此工具。默认 false。
48    #[serde(default)]
49    pub is_priority: bool,
50}
51
52impl Default for ToolDefinition {
53    fn default() -> Self {
54        Self {
55            name: String::new(),
56            description: String::new(),
57            parameters: json!({"type": "object"}),
58            is_priority: false,
59        }
60    }
61}
62
63impl ToolDefinition {
64    /// 获取发送给 LLM 的描述(带优先标记)
65    pub fn description_for_llm(&self) -> String {
66        if self.is_priority {
67            format!("[优先] {}", self.description)
68        } else {
69            self.description.clone()
70        }
71    }
72}
73
74#[async_trait]
75pub trait Tool: Send + Sync {
76    fn definition(&self) -> ToolDefinition;
77    async fn execute(&self, params: Value) -> Result<String>;
78
79    /// Risk level of this tool. Defaults to Safe (read-only).
80    /// Override in tools that modify state.
81    fn risk_level(&self) -> RiskLevel {
82        RiskLevel::Safe
83    }
84}
85
86/// Default toolset without any skill integration. Kept for callers
87/// (and the existing tests) that don't care about skills.
88pub fn all_tools() -> Vec<Box<dyn Tool>> {
89    all_tools_with_skills(Arc::new(Vec::new()))
90}
91
92/// Base toolset without workflow tools (to avoid duplicates).
93fn base_tools(skills: Arc<Vec<Skill>>) -> Vec<Box<dyn Tool>> {
94    vec![
95        Box::new(ask::AskTool),
96        Box::new(read::ReadTool),
97        Box::new(write::WriteTool),
98        Box::new(edit::EditTool),
99        Box::new(multi_edit::MultiEditTool),
100        Box::new(search::SearchTool),
101        Box::new(grep::GrepTool),
102        Box::new(glob::GlobTool),
103        Box::new(ls::LsTool),
104        Box::new(bash::BashTool),
105        Box::new(browser::BrowserOpenTool),
106        Box::new(todo_write::TodoWriteTool),
107        Box::new(websearch::WebSearchTool::new()),
108        Box::new(webfetch::WebFetchTool),
109        Box::new(skill::SkillTool::new(skills)),
110        Box::new(task::TaskTool),
111        Box::new(task::TaskCreateTool),
112        Box::new(task::TaskGetTool),
113        Box::new(task::TaskListTool),
114        Box::new(task::TaskStopTool),
115        Box::new(plan_mode::EnterPlanModeTool),
116        Box::new(plan_mode::ExitPlanModeTool),
117        Box::new(monitor::MonitorTool),
118    ]
119}
120
121/// Build the toolset with skill support but without provider.
122pub fn all_tools_with_skills(skills: Arc<Vec<Skill>>) -> Vec<Box<dyn Tool>> {
123    let mut tools = base_tools(skills);
124    // Add workflow tools without provider
125    tools.extend(workflow::workflow_tools());
126    tools
127}
128
129/// Build toolset with Provider for AI-powered tools.
130pub fn all_tools_with_provider(
131    skills: Arc<Vec<Skill>>,
132    provider: Arc<dyn crate::providers::Provider>,
133) -> Vec<Box<dyn Tool>> {
134    let mut tools = base_tools(skills);
135    // Add AI-powered workflow tools (with provider)
136    tools.extend(workflow::workflow_tools_with_provider(provider));
137    tools
138}
139
140/// Generate tools description for system prompt
141pub fn generate_tools_prompt() -> String {
142    generate_tools_prompt_with_path(None)
143}
144
145/// Generate tools description with optional CodeGraph support
146pub fn generate_tools_prompt_with_path(project_path: Option<&PathBuf>) -> String {
147    let mut tools = base_tools(Arc::new(Vec::new()));
148
149    // Add CodeGraph tools only if initialized (CLI installed + .codegraph exists)
150    if let Some(path) = project_path
151        && codegraph::should_inject_codegraph_tools(path) {
152        tools.extend(codegraph::codegraph_tools_with_auto_detect(path));
153    }
154
155    // Add workflow tools
156    tools.extend(workflow::workflow_tools());
157
158    let mut lines = vec!["可用工具:".to_string()];
159
160    for tool in tools {
161        let def = tool.definition();
162        // Extract brief description (first sentence or up to 50 chars)
163        let brief = def
164            .description
165            .split('.')
166            .next()
167            .or_else(|| def.description.split('\n').next())
168            .unwrap_or(&def.description);
169        let brief = if brief.len() > 60 {
170            format!("{}...", brief.chars().take(57).collect::<String>())
171        } else {
172            brief.to_string()
173        };
174        lines.push(format!("- {}: {}", def.name, brief));
175    }
176
177    lines.join("\n")
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183    use std::path::PathBuf;
184
185    #[test]
186    fn test_all_tools_includes_workflow_tools() {
187        let tools = all_tools();
188        let tool_names: Vec<String> = tools.iter().map(|t| t.definition().name).collect();
189
190        // Verify workflow tools are present
191        assert!(tool_names.contains(&"workflow_discover".to_string()), "workflow_discover should be in tools");
192        assert!(tool_names.contains(&"workflow_run".to_string()), "workflow_run should be in tools");
193        assert!(tool_names.contains(&"workflow_match".to_string()), "workflow_match should be in tools");
194    }
195
196    #[test]
197    fn test_generate_tools_prompt_includes_workflow() {
198        let prompt = generate_tools_prompt();
199
200        // Verify workflow tools appear in prompt
201        assert!(prompt.contains("workflow_discover"), "prompt should mention workflow_discover");
202        assert!(prompt.contains("workflow_run"), "prompt should mention workflow_run");
203        assert!(prompt.contains("workflow_match"), "prompt should mention workflow_match");
204    }
205
206    #[test]
207    fn test_generate_tools_prompt_with_path_includes_codegraph() {
208        let path = PathBuf::from(".");
209        let prompt = generate_tools_prompt_with_path(Some(&path));
210
211        // CodeGraph tools are only included when:
212        // 1. CodeGraph CLI is installed
213        // 2. Project has .codegraph directory
214        // So we check based on actual conditions
215        if codegraph::should_inject_codegraph_tools(&path) {
216            assert!(prompt.contains("code_search"), "prompt should mention code_search when conditions met");
217            assert!(prompt.contains("code_callers"), "prompt should mention code_callers when conditions met");
218        } else {
219            // When conditions not met, codegraph tools should NOT appear
220            assert!(!prompt.contains("code_search"), "prompt should NOT mention code_search without .codegraph");
221        }
222    }
223
224    #[test]
225    fn test_generate_tools_prompt_without_path_excludes_codegraph() {
226        let prompt = generate_tools_prompt();
227
228        // Verify codegraph tools NOT in prompt without path
229        assert!(!prompt.contains("code_search"), "prompt should NOT mention code_search without path");
230    }
231}
232
233/// Build toolset with Arc Provider (preferred method)
234pub fn all_tools_with_arc_provider(
235    skills: Arc<Vec<Skill>>,
236    provider: Arc<dyn crate::providers::Provider>,
237) -> Vec<Box<dyn Tool>> {
238    all_tools_with_provider(skills, provider)
239}
240
241/// Build toolset with Box Provider (for CLI compatibility - safe implementation)
242/// Uses clone_arc to safely convert Box to Arc without unsafe code.
243pub fn all_tools_with_box_provider(
244    skills: Arc<Vec<Skill>>,
245    boxed_provider: Box<dyn crate::providers::Provider>,
246) -> Vec<Box<dyn Tool>> {
247    // Safe conversion: clone_arc creates a new Arc without unsafe pointer manipulation
248    let arc_provider = boxed_provider.clone_arc();
249    all_tools_with_provider(skills, arc_provider)
250}
251
252/// Build toolset with project path for CodeGraph integration.
253pub fn all_tools_with_project_path(
254    skills: Arc<Vec<Skill>>,
255    project_path: PathBuf,
256) -> Vec<Box<dyn Tool>> {
257    let mut tools = base_tools(skills);
258    // Add CodeGraph tools
259    tools.extend(codegraph::codegraph_tools(&project_path));
260    // Add workflow tools
261    tools.extend(workflow::workflow_tools());
262    tools
263}
264
265/// Build full toolset with provider and project path.
266pub fn all_tools_full(
267    skills: Arc<Vec<Skill>>,
268    provider: Arc<dyn crate::providers::Provider>,
269    project_path: PathBuf,
270) -> Vec<Box<dyn Tool>> {
271    let mut tools = base_tools(skills);
272    // Add CodeGraph tools only if initialized (CLI installed + .codegraph exists)
273    if codegraph::should_inject_codegraph_tools(&project_path) {
274        tools.extend(codegraph::codegraph_tools(&project_path));
275    }
276    // Add AI-powered workflow tools
277    tools.extend(workflow::workflow_tools_with_provider(provider));
278    tools
279}