codei-tools 0.0.10

终端优先的 AI 编程 Agent,用自然语言在本地仓库中读代码、改代码、跑命令、调试问题。
Documentation
use async_trait::async_trait;
use serde_json::{json, Value};

use crate::path_util::resolve_workspace_path;
use crate::{Tool, ToolContext, ToolError, ToolResult};

pub struct EditTool;

#[async_trait]
impl Tool for EditTool {
    fn name(&self) -> &str {
        "edit"
    }

    fn description(&self) -> &str {
        "Replace an exact unique string in a file with new content."
    }

    fn parameters_schema(&self) -> Value {
        json!({
            "type": "object",
            "properties": {
                "path": { "type": "string" },
                "old_string": { "type": "string" },
                "new_string": { "type": "string" }
            },
            "required": ["path", "old_string", "new_string"]
        })
    }

    fn requires_approval(&self) -> bool {
        true
    }

    async fn execute(&self, ctx: &ToolContext, args: Value) -> Result<ToolResult, ToolError> {
        let path = args
            .get("path")
            .and_then(|v| v.as_str())
            .ok_or_else(|| ToolError::InvalidArgs("missing path".into()))?;
        let old_string = args
            .get("old_string")
            .and_then(|v| v.as_str())
            .ok_or_else(|| ToolError::InvalidArgs("missing old_string".into()))?;
        let new_string = args
            .get("new_string")
            .and_then(|v| v.as_str())
            .ok_or_else(|| ToolError::InvalidArgs("missing new_string".into()))?;

        let full = resolve_workspace_path(&ctx.cwd, path)?;
        let content = std::fs::read_to_string(&full)?;
        let count = content.matches(old_string).count();
        if count == 0 {
            return Err(ToolError::Failed {
                name: self.name().into(),
                message: "old_string not found".into(),
            });
        }
        if count > 1 {
            return Err(ToolError::Failed {
                name: self.name().into(),
                message: format!("old_string matched {count} times; must be unique"),
            });
        }
        let updated = content.replacen(old_string, new_string, 1);
        std::fs::write(&full, updated)?;

        Ok(ToolResult {
            content: format!("Updated {path}"),
            is_error: false,
        })
    }
}