ml-knowledge-platform/knowledge_platform/tools/builtin/edit.py
2026-02-16 04:50:51 -08:00

104 lines
3.4 KiB
Python

"""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,
)