75 lines
2.8 KiB
Python
75 lines
2.8 KiB
Python
"""edit_file.py – tool for str_replace editing of files inside the sandbox."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import TYPE_CHECKING
|
||
|
||
from langchain_core.tools import BaseTool, tool
|
||
from loguru import logger
|
||
|
||
if TYPE_CHECKING:
|
||
from docker_agent_sandbox.sandbox import DockerSandbox
|
||
|
||
|
||
def make_edit_file_tool(sandbox: "DockerSandbox") -> BaseTool:
|
||
"""Return an edit_file tool bound to *sandbox*."""
|
||
|
||
@tool
|
||
def edit_file(path: str, old_str: str, new_str: str) -> str:
|
||
"""
|
||
Replace the first exact occurrence of *old_str* with *new_str* in *path*.
|
||
|
||
This is the standard ``str_replace`` editing primitive: read the file,
|
||
find the unique snippet you want to change, and supply the replacement.
|
||
|
||
Rules:
|
||
- *old_str* must match **exactly** (including whitespace and indentation).
|
||
- *old_str* must appear **at least once**; the tool returns an error if it
|
||
is not found.
|
||
- If *old_str* appears more than once the tool refuses and asks you to
|
||
provide more surrounding context to make it unique.
|
||
- To insert text without removing anything, set *old_str* to a line that
|
||
will remain and include it verbatim in *new_str* (i.e. keep the anchor
|
||
line and add your new lines around it).
|
||
- To delete a block, set *new_str* to an empty string ``""``.
|
||
|
||
Returns a confirmation with the number of lines affected, or an error.
|
||
"""
|
||
_MAX_EDIT_BYTES = 1_000_000 # 1 MB
|
||
|
||
logger.debug("Editing file inside sandbox: {!r}", path)
|
||
try:
|
||
data = sandbox.read_file(path)
|
||
except (FileNotFoundError, IsADirectoryError, RuntimeError) as exc:
|
||
return f"[ERROR reading {path!r} for edit] {exc}"
|
||
|
||
if len(data) > _MAX_EDIT_BYTES:
|
||
return (
|
||
f"[ERROR] {path!r} is {len(data)} bytes; edit_file only supports files "
|
||
f"up to {_MAX_EDIT_BYTES} bytes."
|
||
)
|
||
|
||
content = data.decode("utf-8", errors="replace")
|
||
count = content.count(old_str)
|
||
if count == 0:
|
||
return (
|
||
f"[ERROR] old_str not found in {path!r}. "
|
||
"Check that whitespace and indentation match exactly."
|
||
)
|
||
if count > 1:
|
||
return (
|
||
f"[ERROR] old_str appears {count} times in {path!r}. "
|
||
"Provide more surrounding context to make it unique."
|
||
)
|
||
|
||
new_content = content.replace(old_str, new_str, 1)
|
||
old_lines = old_str.count("\n") + 1
|
||
new_lines = new_str.count("\n") + 1 if new_str else 0
|
||
try:
|
||
sandbox.write_file(path, new_content)
|
||
except Exception as exc:
|
||
return f"[ERROR writing {path!r} after edit] {exc}"
|
||
return f"[OK] Replaced {old_lines} line(s) with {new_lines} line(s) in {path}"
|
||
|
||
return edit_file
|