matrixcode_core/tools/
edit.rs1use anyhow::Result;
2use async_trait::async_trait;
3use serde_json::{Value, json};
4
5use super::{Tool, ToolDefinition};
6use crate::approval::RiskLevel;
7
8pub struct EditTool;
9
10#[async_trait]
11impl Tool for EditTool {
12 fn definition(&self) -> ToolDefinition {
13 ToolDefinition {
14 name: "edit".to_string(),
15 description: "在文件中查找精确匹配的字符串并替换为新内容".to_string(),
16 parameters: json!({
17 "type": "object",
18 "properties": {
19 "path": {
20 "type": "string",
21 "description": "要编辑的文件路径"
22 },
23 "old_string": {
24 "type": "string",
25 "description": "要查找并替换的原始字符串(必须精确匹配)"
26 },
27 "new_string": {
28 "type": "string",
29 "description": "替换后的新字符串"
30 }
31 },
32 "required": ["path", "old_string", "new_string"]
33 }),
34 }
35 }
36
37 async fn execute(&self, params: Value) -> Result<String> {
38 let path = params["path"]
42 .as_str()
43 .ok_or_else(|| anyhow::anyhow!("missing 'path'"))?;
44 let old_string = params["old_string"]
45 .as_str()
46 .ok_or_else(|| anyhow::anyhow!("missing 'old_string'"))?;
47 let new_string = params["new_string"]
48 .as_str()
49 .ok_or_else(|| anyhow::anyhow!("missing 'new_string'"))?;
50
51 let content = tokio::fs::read_to_string(path).await?;
55
56 let count = content.matches(old_string).count();
57 if count == 0 {
58 anyhow::bail!("old_string not found in {}", path);
60 }
61 if count > 1 {
62 anyhow::bail!(
64 "old_string found {} times in {} — must be unique",
65 count,
66 path
67 );
68 }
69
70 let new_content = content.replacen(old_string, new_string, 1);
71 tokio::fs::write(path, &new_content).await?;
72
73 let old_lines: Vec<&str> = old_string.lines().collect();
75 let new_lines: Vec<&str> = new_string.lines().collect();
76 let mut diff = format!("Successfully edited {}\n", path);
77 for line in &old_lines {
78 diff.push_str(&format!("- {}\n", line));
79 }
80 for line in &new_lines {
81 diff.push_str(&format!("+ {}\n", line));
82 }
83 Ok(diff)
85 }
86
87 fn risk_level(&self) -> RiskLevel {
88 RiskLevel::Mutating
89 }
90}