Skip to main content

llama_cpp_v3_agent_sdk/tools/
edit_file.rs

1use crate::error::AgentError;
2use crate::tool::{Tool, ToolResult};
3
4/// Search-and-replace editing in files.
5pub struct EditFileTool;
6
7impl Tool for EditFileTool {
8    fn name(&self) -> &str {
9        "edit"
10    }
11
12    fn description(&self) -> &str {
13        "Edit a file by searching for an exact string and replacing it. \
14         The 'old' string must match exactly (including whitespace). \
15         Only the first occurrence is replaced unless 'all' is set to true."
16    }
17
18    fn parameters_schema(&self) -> serde_json::Value {
19        serde_json::json!({
20            "type": "object",
21            "properties": {
22                "path": {
23                    "type": "string",
24                    "description": "Path to the file to edit"
25                },
26                "old": {
27                    "type": "string",
28                    "description": "The exact text to search for"
29                },
30                "new": {
31                    "type": "string",
32                    "description": "The replacement text"
33                },
34                "all": {
35                    "type": "boolean",
36                    "description": "If true, replace all occurrences. Default: false."
37                }
38            },
39            "required": ["path", "old", "new"]
40        })
41    }
42
43    fn execute(&self, args: &serde_json::Value) -> Result<ToolResult, AgentError> {
44        let path = args["path"].as_str().ok_or_else(|| AgentError::Tool {
45            tool: "edit".to_string(),
46            message: "Missing 'path' argument".to_string(),
47        })?;
48
49        let old = args["old"].as_str().ok_or_else(|| AgentError::Tool {
50            tool: "edit".to_string(),
51            message: "Missing 'old' argument".to_string(),
52        })?;
53
54        let new = args["new"].as_str().ok_or_else(|| AgentError::Tool {
55            tool: "edit".to_string(),
56            message: "Missing 'new' argument".to_string(),
57        })?;
58
59        let replace_all = args["all"].as_bool().unwrap_or(false);
60
61        let content = match std::fs::read_to_string(path) {
62            Ok(c) => c,
63            Err(e) => {
64                return Ok(ToolResult::err(format!(
65                    "Failed to read '{}': {}",
66                    path, e
67                )))
68            }
69        };
70
71        if !content.contains(old) {
72            return Ok(ToolResult::err(format!(
73                "Search string not found in '{}'. Make sure the 'old' text matches exactly (including whitespace and newlines).",
74                path
75            )));
76        }
77
78        let (new_content, count) = if replace_all {
79            let count = content.matches(old).count();
80            (content.replace(old, new), count)
81        } else {
82            (content.replacen(old, new, 1), 1)
83        };
84
85        match std::fs::write(path, &new_content) {
86            Ok(_) => Ok(ToolResult::ok(format!(
87                "Replaced {} occurrence(s) in '{}'",
88                count, path
89            ))),
90            Err(e) => Ok(ToolResult::err(format!(
91                "Failed to write '{}': {}",
92                path, e
93            ))),
94        }
95    }
96
97    fn requires_permission(&self) -> bool {
98        true
99    }
100}