Skip to main content

pi_agent/tools/
edit.rs

1use async_trait::async_trait;
2use serde_json::{json, Value};
3use tokio::fs;
4
5use crate::types::{AgentTool, AgentToolResult};
6
7pub struct EditTool;
8
9#[async_trait]
10impl AgentTool for EditTool {
11    fn name(&self) -> &str {
12        "edit"
13    }
14    fn requires_permission(&self) -> bool {
15        true
16    }
17    fn description(&self) -> &str {
18        "Replace one occurrence (or all, with replace_all) of old_string with new_string in the file at path."
19    }
20    fn parameters(&self) -> Value {
21        json!({
22            "type": "object",
23            "properties": {
24                "path": {"type": "string"},
25                "old_string": {"type": "string"},
26                "new_string": {"type": "string"},
27                "replace_all": {"type": "boolean", "default": false}
28            },
29            "required": ["path", "old_string", "new_string"]
30        })
31    }
32    async fn execute(&self, _id: &str, args: Value) -> Result<AgentToolResult, String> {
33        let path = args
34            .get("path")
35            .and_then(|v| v.as_str())
36            .ok_or("missing 'path'")?;
37        let old_s = args
38            .get("old_string")
39            .and_then(|v| v.as_str())
40            .ok_or("missing 'old_string'")?;
41        let new_s = args
42            .get("new_string")
43            .and_then(|v| v.as_str())
44            .ok_or("missing 'new_string'")?;
45        let replace_all = args
46            .get("replace_all")
47            .and_then(|v| v.as_bool())
48            .unwrap_or(false);
49
50        let text = fs::read_to_string(path)
51            .await
52            .map_err(|e| format!("read {path}: {e}"))?;
53        let count = text.matches(old_s).count();
54        if count == 0 {
55            return Err(format!("old_string not found in {path}"));
56        }
57        if count > 1 && !replace_all {
58            return Err(format!(
59                "old_string occurs {count} times in {path}; pass replace_all=true or expand the match"
60            ));
61        }
62        let updated = if replace_all {
63            text.replace(old_s, new_s)
64        } else {
65            text.replacen(old_s, new_s, 1)
66        };
67        fs::write(path, updated)
68            .await
69            .map_err(|e| format!("write {path}: {e}"))?;
70        let n = if replace_all { count } else { 1 };
71        Ok(AgentToolResult::text(format!(
72            "edited {path}: {n} replacement(s)"
73        )))
74    }
75}