forked from kent/consciousness
refactor: use typed Deserialize structs for tool arguments
Convert read_file, write_file, edit_file, and glob from manual args["key"].as_str() parsing to serde_json::from_value with typed Args structs. Gives type safety, default values via serde attributes, and clearer error messages on missing/wrong-type arguments. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
29db4ff409
commit
74f05924ff
4 changed files with 77 additions and 57 deletions
|
|
@ -8,10 +8,20 @@
|
|||
// Supports replace_all for bulk renaming (e.g. variable renames).
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::types::ToolDef;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Args {
|
||||
file_path: String,
|
||||
old_string: String,
|
||||
new_string: String,
|
||||
#[serde(default)]
|
||||
replace_all: bool,
|
||||
}
|
||||
|
||||
pub fn definition() -> ToolDef {
|
||||
ToolDef::new(
|
||||
"edit_file",
|
||||
|
|
@ -44,49 +54,37 @@ pub fn definition() -> ToolDef {
|
|||
}
|
||||
|
||||
pub fn edit_file(args: &serde_json::Value) -> Result<String> {
|
||||
let path = args["file_path"]
|
||||
.as_str()
|
||||
.context("file_path is required")?;
|
||||
let old_string = args["old_string"]
|
||||
.as_str()
|
||||
.context("old_string is required")?;
|
||||
let new_string = args["new_string"]
|
||||
.as_str()
|
||||
.context("new_string is required")?;
|
||||
let replace_all = args["replace_all"].as_bool().unwrap_or(false);
|
||||
let a: Args = serde_json::from_value(args.clone())
|
||||
.context("invalid edit_file arguments")?;
|
||||
|
||||
if old_string == new_string {
|
||||
if a.old_string == a.new_string {
|
||||
anyhow::bail!("old_string and new_string are identical");
|
||||
}
|
||||
|
||||
let content =
|
||||
std::fs::read_to_string(path).with_context(|| format!("Failed to read {}", path))?;
|
||||
let content = std::fs::read_to_string(&a.file_path)
|
||||
.with_context(|| format!("Failed to read {}", a.file_path))?;
|
||||
|
||||
if replace_all {
|
||||
let count = content.matches(old_string).count();
|
||||
if count == 0 {
|
||||
anyhow::bail!("old_string not found in {}", path);
|
||||
}
|
||||
let new_content = content.replace(old_string, new_string);
|
||||
std::fs::write(path, &new_content)
|
||||
.with_context(|| format!("Failed to write {}", path))?;
|
||||
Ok(format!("Replaced {} occurrences in {}", count, path))
|
||||
let count = content.matches(&*a.old_string).count();
|
||||
if count == 0 {
|
||||
anyhow::bail!("old_string not found in {}", a.file_path);
|
||||
}
|
||||
|
||||
if a.replace_all {
|
||||
let new_content = content.replace(&*a.old_string, &a.new_string);
|
||||
std::fs::write(&a.file_path, &new_content)
|
||||
.with_context(|| format!("Failed to write {}", a.file_path))?;
|
||||
Ok(format!("Replaced {} occurrences in {}", count, a.file_path))
|
||||
} else {
|
||||
let count = content.matches(old_string).count();
|
||||
if count == 0 {
|
||||
anyhow::bail!("old_string not found in {}", path);
|
||||
}
|
||||
if count > 1 {
|
||||
anyhow::bail!(
|
||||
"old_string appears {} times in {} — use replace_all or provide more context \
|
||||
to make it unique",
|
||||
count,
|
||||
path
|
||||
count, a.file_path
|
||||
);
|
||||
}
|
||||
let new_content = content.replacen(old_string, new_string, 1);
|
||||
std::fs::write(path, &new_content)
|
||||
.with_context(|| format!("Failed to write {}", path))?;
|
||||
Ok(format!("Edited {}", path))
|
||||
let new_content = content.replacen(&*a.old_string, &a.new_string, 1);
|
||||
std::fs::write(&a.file_path, &new_content)
|
||||
.with_context(|| format!("Failed to write {}", a.file_path))?;
|
||||
Ok(format!("Edited {}", a.file_path))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue