deepseek/agent/builtin_tools/
edit.rs1use async_trait::async_trait;
2use serde_json::{json, Value};
3
4use crate::agent::tool::{Tool, ToolDefinition};
5
6pub struct EditTool;
7
8#[async_trait]
9impl Tool for EditTool {
10 fn name(&self) -> &str {
11 "Edit"
12 }
13
14 fn definition(&self) -> ToolDefinition {
15 ToolDefinition {
16 name: self.name().to_string(),
17 description: "Replace one occurrence (or all, with `replace_all`) of an exact \
18 string in a file. Errors if `old_string` is missing or appears \
19 multiple times unless `replace_all` is set."
20 .into(),
21 parameters: 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 }
33
34 async fn call_json(&self, args: Value) -> Result<String, String> {
35 let path = args
36 .get("path")
37 .and_then(Value::as_str)
38 .ok_or_else(|| "Edit: missing string `path`".to_string())?;
39 let old = args
40 .get("old_string")
41 .and_then(Value::as_str)
42 .ok_or_else(|| "Edit: missing string `old_string`".to_string())?;
43 let new = args
44 .get("new_string")
45 .and_then(Value::as_str)
46 .ok_or_else(|| "Edit: missing string `new_string`".to_string())?;
47 let replace_all = args
48 .get("replace_all")
49 .and_then(Value::as_bool)
50 .unwrap_or(false);
51
52 let body = tokio::fs::read_to_string(path)
53 .await
54 .map_err(|e| format!("Edit({path}): {e}"))?;
55
56 let count = body.matches(old).count();
57 if count == 0 {
58 return Err(format!("Edit({path}): `old_string` not found"));
59 }
60 if count > 1 && !replace_all {
61 return Err(format!(
62 "Edit({path}): `old_string` matches {count} times; \
63 set `replace_all=true` or supply more context"
64 ));
65 }
66 let updated = if replace_all {
67 body.replace(old, new)
68 } else {
69 body.replacen(old, new, 1)
70 };
71 tokio::fs::write(path, updated)
72 .await
73 .map_err(|e| format!("Edit({path}): write: {e}"))?;
74 Ok(format!(
75 "Edited {path}: replaced {} occurrence(s)",
76 if replace_all { count } else { 1 }
77 ))
78 }
79}