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