"""EditTool — replace exact string occurrences in a file. Supports single (unique) replacement or replace-all mode. """ from __future__ import annotations from pathlib import Path from typing import Any, ClassVar from ..base import Tool, ToolParameter, ToolResult class EditTool(Tool): """Replace old_string with new_string in a file. By default, ``old_string`` must appear exactly once. Set ``replace_all`` to True to replace every occurrence. """ name: ClassVar[str] = "edit" description: ClassVar[str] = ( "Perform exact string replacement in a file. " "Fails if old_string is not found or is ambiguous (unless replace_all is True)." ) parameters: ClassVar[list[ToolParameter]] = [ ToolParameter( name="file_path", type="string", description="Absolute path to the file to edit", ), ToolParameter( name="old_string", type="string", description="The exact text to find and replace", ), ToolParameter( name="new_string", type="string", description="The replacement text", ), ToolParameter( name="replace_all", type="boolean", description="Replace all occurrences instead of requiring uniqueness", required=False, default=False, ), ] async def execute(self, **kwargs: Any) -> ToolResult: file_path = Path(kwargs["file_path"]) old_string: str = kwargs["old_string"] new_string: str = kwargs["new_string"] replace_all: bool = kwargs.get("replace_all", False) if not file_path.is_absolute(): return ToolResult.fail(f"Path must be absolute: {file_path}") if not file_path.exists(): return ToolResult.fail(f"File not found: {file_path}") if not file_path.is_file(): return ToolResult.fail(f"Not a file: {file_path}") if old_string == new_string: return ToolResult.fail("old_string and new_string are identical") try: content = file_path.read_text(encoding="utf-8") except UnicodeDecodeError: return ToolResult.fail(f"Cannot read binary file: {file_path}") except PermissionError: return ToolResult.fail(f"Permission denied: {file_path}") occurrence_count = content.count(old_string) if occurrence_count == 0: return ToolResult.fail( f"old_string not found in {file_path}. " "Verify the exact text including whitespace and indentation." ) if not replace_all and occurrence_count > 1: return ToolResult.fail( f"old_string found {occurrence_count} times in {file_path}. " "Provide more context to make it unique, or set replace_all=True." ) if replace_all: new_content = content.replace(old_string, new_string) else: new_content = content.replace(old_string, new_string, 1) try: file_path.write_text(new_content, encoding="utf-8") except PermissionError: return ToolResult.fail(f"Permission denied writing to: {file_path}") return ToolResult.success( f"Replaced {occurrence_count} occurrence(s) in {file_path}", file_path=str(file_path), replacements=occurrence_count, )