(
&self,
_id: &str,
params: Value,
_cancel: CancellationToken,
_on_update: Option<AgentToolUpdate>,
)
| 21 | } |
| 22 | |
| 23 | async fn execute( |
| 24 | &self, |
| 25 | _id: &str, |
| 26 | params: Value, |
| 27 | _cancel: CancellationToken, |
| 28 | _on_update: Option<AgentToolUpdate>, |
| 29 | ) -> Result<AgentToolResult, AgentToolError> { |
| 30 | let path = params |
| 31 | .get("path") |
| 32 | .and_then(|v| v.as_str()) |
| 33 | .ok_or_else(|| AgentToolError::from("missing `path`"))?; |
| 34 | let old = params |
| 35 | .get("old_string") |
| 36 | .and_then(|v| v.as_str()) |
| 37 | .ok_or_else(|| AgentToolError::from("missing `old_string`"))?; |
| 38 | let new_ = params |
| 39 | .get("new_string") |
| 40 | .and_then(|v| v.as_str()) |
| 41 | .ok_or_else(|| AgentToolError::from("missing `new_string`"))?; |
| 42 | let replace_all = params |
| 43 | .get("replace_all") |
| 44 | .and_then(|v| v.as_bool()) |
| 45 | .unwrap_or(false); |
| 46 | if old == new_ { |
| 47 | return Err(AgentToolError::from( |
| 48 | "old_string must differ from new_string", |
| 49 | )); |
| 50 | } |
| 51 | |
| 52 | // Serialize the whole read-modify-write per file so a concurrent `edit`/`write` on the |
| 53 | // same path cannot read a stale body and clobber the other's change. See |
| 54 | // `tools::fs_guard`. |
| 55 | let occurrences = crate::tools::fs_guard::with_file_lock( |
| 56 | std::path::Path::new(path), |
| 57 | || async { |
| 58 | let body = tokio::fs::read_to_string(path) |
| 59 | .await |
| 60 | .map_err(|e| AgentToolError::from(format!("read {path}: {e}")))?; |
| 61 | |
| 62 | let occurrences = body.matches(old).count(); |
| 63 | if occurrences == 0 { |
| 64 | return Err(AgentToolError::from(format!( |
| 65 | "old_string not found in {path}" |
| 66 | ))); |
| 67 | } |
| 68 | if occurrences > 1 && !replace_all { |
| 69 | return Err(AgentToolError::from(format!( |
| 70 | "old_string matched {occurrences} times in {path}; pass replace_all=true to replace every occurrence, or include more surrounding context to make it unique" |
| 71 | ))); |
| 72 | } |
| 73 | |
| 74 | let new_body = if replace_all { |
| 75 | body.replace(old, new_) |
| 76 | } else { |
| 77 | body.replacen(old, new_, 1) |
| 78 | }; |
| 79 | tokio::fs::write(path, new_body.as_bytes()) |
| 80 | .await |
no test coverage detected