consciousness/src/agent/tools/edit.rs
ProofOfConcept 53ad8cc9df tools: static string definitions, no runtime JSON construction
Tool definitions are now &'static str (name, description,
parameters_json) instead of runtime-constructed serde_json::Value.
No more json!() macro, no more ToolDef::new() for tool definitions.

The JSON schema strings are written directly as string literals.
When sent to the API, they can be interpolated without
serialization/deserialization.

Multi-tool modules return fixed-size arrays instead of Vecs:
- memory: [Tool; 12], journal: [Tool; 3]
- channels: [Tool; 4]
- control: [Tool; 3]
- web: [Tool; 2]

ToolDef/FunctionDef remain for backward compat (API wire format,
summarize_args) but are no longer used in tool definitions.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
2026-04-04 18:19:21 -04:00

41 lines
2.2 KiB
Rust

// tools/edit.rs — Search-and-replace file editing
use anyhow::{Context, Result};
use serde::Deserialize;
pub fn tool() -> super::Tool {
super::Tool {
name: "edit_file",
description: "Perform exact string replacement in a file. The old_string must appear exactly once (unless replace_all is true). Use read_file first to see current contents.",
parameters_json: r#"{"type":"object","properties":{"file_path":{"type":"string","description":"Absolute path to the file to edit"},"old_string":{"type":"string","description":"The exact text to find and replace"},"new_string":{"type":"string","description":"The replacement text"},"replace_all":{"type":"boolean","description":"Replace all occurrences (default false)"}},"required":["file_path","old_string","new_string"]}"#,
handler: |_a, v| Box::pin(async move { edit_file(&v) }),
}
}
#[derive(Deserialize)]
struct Args { file_path: String, old_string: String, new_string: String, #[serde(default)] replace_all: bool }
fn edit_file(args: &serde_json::Value) -> Result<String> {
let a: Args = serde_json::from_value(args.clone()).context("invalid edit_file arguments")?;
if a.old_string == a.new_string { anyhow::bail!("old_string and new_string are identical"); }
let content = std::fs::read_to_string(&a.file_path)
.with_context(|| format!("Failed to read {}", a.file_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 {
if count > 1 {
anyhow::bail!("old_string appears {} times in {} — use replace_all or provide more context", count, a.file_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))
}
}