diff --git a/openhands/runtime/plugins/agent_skills/file_editor/__init__.py b/openhands/runtime/plugins/agent_skills/file_editor/__init__.py
index f6d3eb39a019..06d5bcca6325 100644
--- a/openhands/runtime/plugins/agent_skills/file_editor/__init__.py
+++ b/openhands/runtime/plugins/agent_skills/file_editor/__init__.py
@@ -1,60 +1,8 @@
-"""This file contains a global singleton of the `EditTool` class as well as raw functions that expose its __call__."""
-
-from .base import CLIResult, ToolError, ToolResult
-from .impl import Command, EditTool
-
-_GLOBAL_EDITOR = EditTool()
-
-
-def _make_api_tool_result(
- result: ToolResult,
-) -> str:
- """Convert an agent ToolResult to an API ToolResultBlockParam."""
- tool_result_content: str = ''
- is_error = False
- if result.error:
- is_error = True
- tool_result_content = _maybe_prepend_system_tool_result(result, result.error)
- else:
- assert result.output, 'Expecting output in file_editor'
- tool_result_content = _maybe_prepend_system_tool_result(result, result.output)
- assert (
- not result.base64_image
- ), 'Not expecting base64_image as output in file_editor'
- if is_error:
- return f'ERROR:\n{tool_result_content}'
- else:
- return tool_result_content
-
-
-def _maybe_prepend_system_tool_result(result: ToolResult, result_text: str) -> str:
- if result.system:
- result_text = f'{result.system}\n{result_text}'
- return result_text
-
-
-def file_editor(
- command: Command,
- path: str,
- file_text: str | None = None,
- view_range: list[int] | None = None,
- old_str: str | None = None,
- new_str: str | None = None,
- insert_line: int | None = None,
-) -> str:
- try:
- result: CLIResult = _GLOBAL_EDITOR(
- command=command,
- path=path,
- file_text=file_text,
- view_range=view_range,
- old_str=old_str,
- new_str=new_str,
- insert_line=insert_line,
- )
- except ToolError as e:
- return _make_api_tool_result(ToolResult(error=e.message))
- return _make_api_tool_result(result)
+"""This file imports a global singleton of the `EditTool` class as well as raw functions that expose
+its __call__.
+The implementation of the `EditTool` class can be found at: https://github.com/All-Hands-AI/openhands-aci/.
+"""
+from openhands_aci.editor import file_editor
__all__ = ['file_editor']
diff --git a/openhands/runtime/plugins/agent_skills/file_editor/base.py b/openhands/runtime/plugins/agent_skills/file_editor/base.py
deleted file mode 100644
index 6ad2a4b5b69c..000000000000
--- a/openhands/runtime/plugins/agent_skills/file_editor/base.py
+++ /dev/null
@@ -1,50 +0,0 @@
-from dataclasses import dataclass, fields, replace
-
-
-@dataclass(kw_only=True, frozen=True)
-class ToolResult:
- """Represents the result of a tool execution."""
-
- output: str | None = None
- error: str | None = None
- base64_image: str | None = None
- system: str | None = None
-
- def __bool__(self):
- return any(getattr(self, field.name) for field in fields(self))
-
- def __add__(self, other: 'ToolResult'):
- def combine_fields(
- field: str | None, other_field: str | None, concatenate: bool = True
- ):
- if field and other_field:
- if concatenate:
- return field + other_field
- raise ValueError('Cannot combine tool results')
- return field or other_field
-
- return ToolResult(
- output=combine_fields(self.output, other.output),
- error=combine_fields(self.error, other.error),
- base64_image=combine_fields(self.base64_image, other.base64_image, False),
- system=combine_fields(self.system, other.system),
- )
-
- def replace(self, **kwargs):
- """Returns a new ToolResult with the given fields replaced."""
- return replace(self, **kwargs)
-
-
-class CLIResult(ToolResult):
- """A ToolResult that can be rendered as a CLI output."""
-
-
-class ToolFailure(ToolResult):
- """A ToolResult that represents a failure."""
-
-
-class ToolError(Exception):
- """Raised when a tool encounters an error."""
-
- def __init__(self, message):
- self.message = message
diff --git a/openhands/runtime/plugins/agent_skills/file_editor/impl.py b/openhands/runtime/plugins/agent_skills/file_editor/impl.py
deleted file mode 100644
index 613c550e7a80..000000000000
--- a/openhands/runtime/plugins/agent_skills/file_editor/impl.py
+++ /dev/null
@@ -1,279 +0,0 @@
-from collections import defaultdict
-from pathlib import Path
-from typing import Literal, get_args
-
-from .base import CLIResult, ToolError, ToolResult
-from .run import maybe_truncate, run
-
-Command = Literal[
- 'view',
- 'create',
- 'str_replace',
- 'insert',
- 'undo_edit',
-]
-SNIPPET_LINES: int = 4
-
-
-class EditTool:
- """
- An filesystem editor tool that allows the agent to view, create, and edit files.
- The tool parameters are defined by Anthropic and are not editable.
-
- Original implementation: https://github.com/anthropics/anthropic-quickstarts/blob/main/computer-use-demo/computer_use_demo/tools/edit.py
- """
-
- _file_history: dict[Path, list[str]]
-
- def __init__(self):
- self._file_history = defaultdict(list)
- super().__init__()
-
- def __call__(
- self,
- *,
- command: Command,
- path: str,
- file_text: str | None = None,
- view_range: list[int] | None = None,
- old_str: str | None = None,
- new_str: str | None = None,
- insert_line: int | None = None,
- **kwargs,
- ):
- _path = Path(path)
- self.validate_path(command, _path)
- if command == 'view':
- return self.view(_path, view_range)
- elif command == 'create':
- if file_text is None:
- raise ToolError('Parameter `file_text` is required for command: create')
- self.write_file(_path, file_text)
- self._file_history[_path].append(file_text)
- return ToolResult(output=f'File created successfully at: {_path}')
- elif command == 'str_replace':
- if old_str is None:
- raise ToolError(
- 'Parameter `old_str` is required for command: str_replace'
- )
- return self.str_replace(_path, old_str, new_str)
- elif command == 'insert':
- if insert_line is None:
- raise ToolError(
- 'Parameter `insert_line` is required for command: insert'
- )
- if new_str is None:
- raise ToolError('Parameter `new_str` is required for command: insert')
- return self.insert(_path, insert_line, new_str)
- elif command == 'undo_edit':
- return self.undo_edit(_path)
- raise ToolError(
- f'Unrecognized command {command}. The allowed commands for the {self.name} tool are: {", ".join(get_args(Command))}'
- )
-
- def validate_path(self, command: str, path: Path):
- """
- Check that the path/command combination is valid.
- """
- # Check if its an absolute path
- if not path.is_absolute():
- suggested_path = Path('') / path
- raise ToolError(
- f'The path {path} is not an absolute path, it should start with `/`. Maybe you meant {suggested_path}?'
- )
- # Check if path exists
- if not path.exists() and command != 'create':
- raise ToolError(
- f'The path {path} does not exist. Please provide a valid path.'
- )
- if path.exists() and command == 'create':
- raise ToolError(
- f'File already exists at: {path}. Cannot overwrite files using command `create`.'
- )
- # Check if the path points to a directory
- if path.is_dir():
- if command != 'view':
- raise ToolError(
- f'The path {path} is a directory and only the `view` command can be used on directories'
- )
-
- def view(self, path: Path, view_range: list[int] | None = None):
- """Implement the view command"""
- if path.is_dir():
- if view_range:
- raise ToolError(
- 'The `view_range` parameter is not allowed when `path` points to a directory.'
- )
-
- _, stdout, stderr = run(rf"find {path} -maxdepth 2 -not -path '*/\.*'")
- if not stderr:
- stdout = f"Here's the files and directories up to 2 levels deep in {path}, excluding hidden items:\n{stdout}\n"
- return CLIResult(output=stdout, error=stderr)
-
- file_content = self.read_file(path)
- init_line = 1
- if view_range:
- if len(view_range) != 2 or not all(isinstance(i, int) for i in view_range):
- raise ToolError(
- 'Invalid `view_range`. It should be a list of two integers.'
- )
- file_lines = file_content.split('\n')
- n_lines_file = len(file_lines)
- init_line, final_line = view_range
- if init_line < 1 or init_line > n_lines_file:
- raise ToolError(
- f"Invalid `view_range`: {view_range}. It's first element `{init_line}` should be within the range of lines of the file: {[1, n_lines_file]}"
- )
- if final_line > n_lines_file:
- raise ToolError(
- f"Invalid `view_range`: {view_range}. It's second element `{final_line}` should be smaller than the number of lines in the file: `{n_lines_file}`"
- )
- if final_line != -1 and final_line < init_line:
- raise ToolError(
- f"Invalid `view_range`: {view_range}. It's second element `{final_line}` should be larger or equal than its first `{init_line}`"
- )
-
- if final_line == -1:
- file_content = '\n'.join(file_lines[init_line - 1 :])
- else:
- file_content = '\n'.join(file_lines[init_line - 1 : final_line])
-
- return CLIResult(
- output=self._make_output(file_content, str(path), init_line=init_line)
- )
-
- def str_replace(self, path: Path, old_str: str, new_str: str | None):
- """Implement the str_replace command, which replaces old_str with new_str in the file content"""
- # Read the file content
- file_content = self.read_file(path).expandtabs()
- old_str = old_str.expandtabs()
- new_str = new_str.expandtabs() if new_str is not None else ''
-
- # Check if old_str is unique in the file
- occurrences = file_content.count(old_str)
- if occurrences == 0:
- raise ToolError(
- f'No replacement was performed, old_str `{old_str}` did not appear verbatim in {path}.'
- )
- elif occurrences > 1:
- file_content_lines = file_content.split('\n')
- lines = [
- idx + 1
- for idx, line in enumerate(file_content_lines)
- if old_str in line
- ]
- raise ToolError(
- f'No replacement was performed. Multiple occurrences of old_str `{old_str}` in lines {lines}. Please ensure it is unique'
- )
-
- # Replace old_str with new_str
- new_file_content = file_content.replace(old_str, new_str)
-
- # Write the new content to the file
- self.write_file(path, new_file_content)
-
- # Save the content to history
- self._file_history[path].append(file_content)
-
- # Create a snippet of the edited section
- replacement_line = file_content.split(old_str)[0].count('\n')
- start_line = max(0, replacement_line - SNIPPET_LINES)
- end_line = replacement_line + SNIPPET_LINES + new_str.count('\n')
- snippet = '\n'.join(new_file_content.split('\n')[start_line : end_line + 1])
-
- # Prepare the success message
- success_msg = f'The file {path} has been edited. '
- success_msg += self._make_output(
- snippet, f'a snippet of {path}', start_line + 1
- )
- success_msg += 'Review the changes and make sure they are as expected. Edit the file again if necessary.'
-
- return CLIResult(output=success_msg)
-
- def insert(self, path: Path, insert_line: int, new_str: str):
- """Implement the insert command, which inserts new_str at the specified line in the file content."""
- file_text = self.read_file(path).expandtabs()
- new_str = new_str.expandtabs()
- file_text_lines = file_text.split('\n')
- n_lines_file = len(file_text_lines)
-
- if insert_line < 0 or insert_line > n_lines_file:
- raise ToolError(
- f'Invalid `insert_line` parameter: {insert_line}. It should be within the range of lines of the file: {[0, n_lines_file]}'
- )
-
- new_str_lines = new_str.split('\n')
- new_file_text_lines = (
- file_text_lines[:insert_line]
- + new_str_lines
- + file_text_lines[insert_line:]
- )
- snippet_lines = (
- file_text_lines[max(0, insert_line - SNIPPET_LINES) : insert_line]
- + new_str_lines
- + file_text_lines[insert_line : insert_line + SNIPPET_LINES]
- )
-
- new_file_text = '\n'.join(new_file_text_lines)
- snippet = '\n'.join(snippet_lines)
-
- self.write_file(path, new_file_text)
- self._file_history[path].append(file_text)
-
- success_msg = f'The file {path} has been edited. '
- success_msg += self._make_output(
- snippet,
- 'a snippet of the edited file',
- max(1, insert_line - SNIPPET_LINES + 1),
- )
- success_msg += 'Review the changes and make sure they are as expected (correct indentation, no duplicate lines, etc). Edit the file again if necessary.'
- return CLIResult(output=success_msg)
-
- def undo_edit(self, path: Path):
- """Implement the undo_edit command."""
- if not self._file_history[path]:
- raise ToolError(f'No edit history found for {path}.')
-
- old_text = self._file_history[path].pop()
- self.write_file(path, old_text)
-
- return CLIResult(
- output=f'Last edit to {path} undone successfully. {self._make_output(old_text, str(path))}'
- )
-
- def read_file(self, path: Path):
- """Read the content of a file from a given path; raise a ToolError if an error occurs."""
- try:
- return path.read_text()
- except Exception as e:
- raise ToolError(f'Ran into {e} while trying to read {path}') from None
-
- def write_file(self, path: Path, file: str):
- """Write the content of a file to a given path; raise a ToolError if an error occurs."""
- try:
- path.write_text(file)
- except Exception as e:
- raise ToolError(f'Ran into {e} while trying to write to {path}') from None
-
- def _make_output(
- self,
- file_content: str,
- file_descriptor: str,
- init_line: int = 1,
- expand_tabs: bool = True,
- ):
- """Generate output for the CLI based on the content of a file."""
- file_content = maybe_truncate(file_content)
- if expand_tabs:
- file_content = file_content.expandtabs()
- file_content = '\n'.join(
- [
- f'{i + init_line:6}\t{line}'
- for i, line in enumerate(file_content.split('\n'))
- ]
- )
- return (
- f"Here's the result of running `cat -n` on {file_descriptor}:\n"
- + file_content
- + '\n'
- )
diff --git a/openhands/runtime/plugins/agent_skills/file_editor/run.py b/openhands/runtime/plugins/agent_skills/file_editor/run.py
deleted file mode 100644
index 29c604256f80..000000000000
--- a/openhands/runtime/plugins/agent_skills/file_editor/run.py
+++ /dev/null
@@ -1,44 +0,0 @@
-"""Utility to run shell commands asynchronously with a timeout."""
-
-import subprocess
-import time
-
-TRUNCATED_MESSAGE: str = 'To save on context only part of this file has been shown to you. You should retry this tool after you have searched inside the file with `grep -n` in order to find the line numbers of what you are looking for.'
-MAX_RESPONSE_LEN: int = 16000
-
-
-def maybe_truncate(content: str, truncate_after: int | None = MAX_RESPONSE_LEN):
- """Truncate content and append a notice if content exceeds the specified length."""
- return (
- content
- if not truncate_after or len(content) <= truncate_after
- else content[:truncate_after] + TRUNCATED_MESSAGE
- )
-
-
-def run(
- cmd: str,
- timeout: float | None = 120.0, # seconds
- truncate_after: int | None = MAX_RESPONSE_LEN,
-):
- """Run a shell command synchronously with a timeout."""
- start_time = time.time()
-
- try:
- process = subprocess.Popen(
- cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
- )
-
- stdout, stderr = process.communicate(timeout=timeout)
-
- return (
- process.returncode or 0,
- maybe_truncate(stdout, truncate_after=truncate_after),
- maybe_truncate(stderr, truncate_after=truncate_after),
- )
- except subprocess.TimeoutExpired:
- process.kill()
- elapsed_time = time.time() - start_time
- raise TimeoutError(
- f"Command '{cmd}' timed out after {elapsed_time:.2f} seconds"
- )
diff --git a/poetry.lock b/poetry.lock
index b32d2c7753db..0e03acf5e0ce 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1562,6 +1562,17 @@ files = [
{file = "dirtyjson-1.0.8.tar.gz", hash = "sha256:90ca4a18f3ff30ce849d100dcf4a003953c79d3a2348ef056f1d9c22231a25fd"},
]
+[[package]]
+name = "diskcache"
+version = "5.6.3"
+description = "Disk Cache -- Disk and file backed persistent cache."
+optional = false
+python-versions = ">=3"
+files = [
+ {file = "diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19"},
+ {file = "diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc"},
+]
+
[[package]]
name = "distlib"
version = "0.3.9"
@@ -5629,6 +5640,28 @@ files = [
[package.dependencies]
numpy = {version = ">=1.26.0", markers = "python_version >= \"3.12\""}
+[[package]]
+name = "openhands-aci"
+version = "0.1.0"
+description = "An Agent-Computer Interface (ACI) designed for software development agents OpenHands."
+optional = false
+python-versions = "<4.0,>=3.12"
+files = [
+ {file = "openhands_aci-0.1.0-py3-none-any.whl", hash = "sha256:f28e5a32e394d1e643f79bf8af27fe44d039cb71729d590f9f3ee0c23c075f00"},
+ {file = "openhands_aci-0.1.0.tar.gz", hash = "sha256:babc55f516efbb27eb7e528662e14b75c902965c48a110408fda824b83ea4461"},
+]
+
+[package.dependencies]
+diskcache = ">=5.6.3,<6.0.0"
+gitpython = "*"
+grep-ast = "0.3.3"
+litellm = "*"
+networkx = "*"
+numpy = "*"
+pandas = "*"
+scipy = "*"
+tree-sitter = "0.21.3"
+
[[package]]
name = "opentelemetry-api"
version = "1.25.0"
@@ -10178,4 +10211,4 @@ testing = ["coverage[toml]", "zope.event", "zope.testing"]
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
-content-hash = "245fd4cd56a3c95b2dd4f3a06251f7de82ad0300de7349f0710aac1f92a151b7"
+content-hash = "a552f630dfdb9221eda6932e71e67a935c52ebfe4388ec9ef4b3245e7df2f82b"
diff --git a/pyproject.toml b/pyproject.toml
index 6430f70d720d..a121ec388e23 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -63,6 +63,7 @@ opentelemetry-exporter-otlp-proto-grpc = "1.25.0"
modal = "^0.64.145"
runloop-api-client = "0.7.0"
pygithub = "^2.5.0"
+openhands-aci = "^0.1.0"
[tool.poetry.group.llama-index.dependencies]
llama-index = "*"
diff --git a/tests/unit/test_agent_skill.py b/tests/unit/test_agent_skill.py
index f619bc00bff0..6079eb659aea 100644
--- a/tests/unit/test_agent_skill.py
+++ b/tests/unit/test_agent_skill.py
@@ -5,7 +5,6 @@
import docx
import pytest
-from openhands.runtime.plugins.agent_skills.agentskills import file_editor
from openhands.runtime.plugins.agent_skills.file_ops.file_ops import (
WINDOW,
_print_window,
@@ -781,7 +780,7 @@ def test_file_editor_create(tmp_path):
assert result is not None
assert (
result
- == f'ERROR:\nThe path {random_file} does not exist. Please provide a valid path.'
+ == f'ERROR:\nInvalid `path` parameter: {random_file}. The path {random_file} does not exist. Please provide a valid path.'
)
# create a file
@@ -800,218 +799,3 @@ def test_file_editor_create(tmp_path):
1\tLine 6
""".strip().split('\n')
)
-
-
-@pytest.fixture
-def setup_file(tmp_path):
- random_dir = tmp_path / 'dir_1'
- random_dir.mkdir()
- random_file = random_dir / 'a.txt'
- return random_file
-
-
-def test_file_editor_create_and_view(setup_file):
- random_file = setup_file
-
- # Test create command
- result = file_editor(
- command='create', path=str(random_file), file_text='Line 1\nLine 2\nLine 3'
- )
- print(result)
- assert result == f'File created successfully at: {random_file}'
-
- # Test view command for file
- result = file_editor(command='view', path=str(random_file))
- print(result)
- assert (
- result.strip().split('\n')
- == f"""Here's the result of running `cat -n` on {random_file}:
- 1\tLine 1
- 2\tLine 2
- 3\tLine 3
-""".strip().split('\n')
- )
-
- # Test view command for directory
- result = file_editor(command='view', path=str(random_file.parent))
- assert f'{random_file.parent}' in result
- assert f'{random_file.name}' in result
-
-
-def test_file_editor_view_nonexistent(setup_file):
- random_file = setup_file
-
- # Test view command for non-existent file
- result = file_editor(command='view', path=str(random_file))
- assert (
- result
- == f'ERROR:\nThe path {random_file} does not exist. Please provide a valid path.'
- )
-
-
-def test_file_editor_str_replace(setup_file):
- random_file = setup_file
- file_editor(
- command='create', path=str(random_file), file_text='Line 1\nLine 2\nLine 3'
- )
-
- # Test str_replace command
- result = file_editor(
- command='str_replace',
- path=str(random_file),
- old_str='Line 2',
- new_str='New Line 2',
- )
- print(result)
- assert (
- result
- == f"""The file {random_file} has been edited. Here's the result of running `cat -n` on a snippet of {random_file}:
- 1\tLine 1
- 2\tNew Line 2
- 3\tLine 3
-Review the changes and make sure they are as expected. Edit the file again if necessary."""
- )
-
- # View the file after str_replace
- result = file_editor(command='view', path=str(random_file))
- print(result)
- assert (
- result.strip().split('\n')
- == f"""Here's the result of running `cat -n` on {random_file}:
- 1\tLine 1
- 2\tNew Line 2
- 3\tLine 3
-""".strip().split('\n')
- )
-
-
-def test_file_editor_str_replace_non_existent(setup_file):
- random_file = setup_file
- file_editor(
- command='create', path=str(random_file), file_text='Line 1\nLine 2\nLine 3'
- )
-
- # Test str_replace with non-existent string
- result = file_editor(
- command='str_replace',
- path=str(random_file),
- old_str='Non-existent Line',
- new_str='New Line',
- )
- print(result)
- assert (
- result
- == f'ERROR:\nNo replacement was performed, old_str `Non-existent Line` did not appear verbatim in {random_file}.'
- )
-
-
-def test_file_editor_insert(setup_file):
- random_file = setup_file
- file_editor(
- command='create', path=str(random_file), file_text='Line 1\nLine 2\nLine 3'
- )
-
- # Test insert command
- result = file_editor(
- command='insert', path=str(random_file), insert_line=2, new_str='Inserted Line'
- )
- print(result)
- assert (
- result
- == f"""The file {random_file} has been edited. Here's the result of running `cat -n` on a snippet of the edited file:
- 1\tLine 1
- 2\tLine 2
- 3\tInserted Line
- 4\tLine 3
-Review the changes and make sure they are as expected (correct indentation, no duplicate lines, etc). Edit the file again if necessary."""
- )
-
- # View the file after insert
- result = file_editor(command='view', path=str(random_file))
- assert (
- result.strip().split('\n')
- == f"""Here's the result of running `cat -n` on {random_file}:
- 1\tLine 1
- 2\tLine 2
- 3\tInserted Line
- 4\tLine 3
-""".strip().split('\n')
- )
-
-
-def test_file_editor_insert_invalid_line(setup_file):
- random_file = setup_file
- file_editor(
- command='create', path=str(random_file), file_text='Line 1\nLine 2\nLine 3'
- )
-
- # Test insert with invalid line number
- result = file_editor(
- command='insert',
- path=str(random_file),
- insert_line=10,
- new_str='Invalid Insert',
- )
- assert (
- result
- == 'ERROR:\nInvalid `insert_line` parameter: 10. It should be within the range of lines of the file: [0, 3]'
- )
-
-
-def test_file_editor_undo_edit(setup_file):
- random_file = setup_file
- result = file_editor(
- command='create', path=str(random_file), file_text='Line 1\nLine 2\nLine 3'
- )
- print(result)
- assert result == f"""File created successfully at: {random_file}"""
-
- # Make an edit
- result = file_editor(
- command='str_replace',
- path=str(random_file),
- old_str='Line 2',
- new_str='New Line 2',
- )
- print(result)
- assert (
- result
- == f"""The file {random_file} has been edited. Here's the result of running `cat -n` on a snippet of {random_file}:
- 1\tLine 1
- 2\tNew Line 2
- 3\tLine 3
-Review the changes and make sure they are as expected. Edit the file again if necessary."""
- )
-
- # Test undo_edit command
- result = file_editor(command='undo_edit', path=str(random_file))
- print(result)
- assert (
- result
- == f"""Last edit to {random_file} undone successfully. Here's the result of running `cat -n` on {random_file}:
- 1\tLine 1
- 2\tLine 2
- 3\tLine 3
-"""
- )
-
- # View the file after undo_edit
- result = file_editor(command='view', path=str(random_file))
- assert (
- result.strip().split('\n')
- == f"""Here's the result of running `cat -n` on {random_file}:
- 1\tLine 1
- 2\tLine 2
- 3\tLine 3
-""".strip().split('\n')
- )
-
-
-def test_file_editor_undo_edit_no_edits(tmp_path):
- random_file = tmp_path / 'a.txt'
- random_file.touch()
-
- # Test undo_edit when no edits have been made
- result = file_editor(command='undo_edit', path=str(random_file))
- print(result)
- assert result == f'ERROR:\nNo edit history found for {random_file}.'