From a8f3a874c8aa9b87f73feceed6090afeabb26ac4 Mon Sep 17 00:00:00 2001 From: code-server Date: Sat, 28 Feb 2026 06:41:48 +0000 Subject: [PATCH] feat(memory): implement str_replace command --- nanobot/agent/tools/anthropic/memory.py | 51 ++++++++++++++++++++-- tests/test_memory_tool.py | 58 +++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/nanobot/agent/tools/anthropic/memory.py b/nanobot/agent/tools/anthropic/memory.py index 6bddc11..e0d2404 100644 --- a/nanobot/agent/tools/anthropic/memory.py +++ b/nanobot/agent/tools/anthropic/memory.py @@ -367,10 +367,55 @@ class MemoryTool20250818(BaseAnthropicTool): ) async def _str_replace( - self, path: str | None, old_str: str | None, new_str: str | None + self, + path: str | None, + old_str: str | None, + new_str: str | None, ) -> CLIResult: - """Placeholder for str_replace command.""" - return CLIResult(exit_code=1, output="", error="Not implemented yet") + """Replace unique occurrence of old_str with new_str.""" + if path is None or old_str is None or new_str is None: + return CLIResult( + exit_code=1, + output="", + error="Error: old_str and new_str are required for str_replace command" + ) + + path_str = path + validated_path = self._validate_memory_path(path) + + if not validated_path.exists() or validated_path.is_dir(): + return CLIResult( + exit_code=1, + output="", + error=f"Error: The path {path_str} does not exist. Please provide a valid path." + ) + + content = validated_path.read_text() + count = content.count(old_str) + + if count == 0: + return CLIResult( + exit_code=1, + output="", + error=f"No replacement was performed, old_str `{old_str}` did not appear verbatim in {path_str}." + ) + elif count > 1: + lines = content.splitlines() + line_nums = [i + 1 for i, line in enumerate(lines) if old_str in line] + return CLIResult( + exit_code=1, + output="", + error=f"No replacement was performed. Multiple occurrences of old_str `{old_str}` in lines: {line_nums}. Please ensure it is unique" + ) + + new_content = content.replace(old_str, new_str, 1) + validated_path.write_text(new_content) + + return CLIResult( + exit_code=0, + output="The memory file has been edited.", + error="" + ) async def _insert( self, path: str | None, insert_line: int | None, insert_text: str | None diff --git a/tests/test_memory_tool.py b/tests/test_memory_tool.py index 8a08f28..189b2f7 100644 --- a/tests/test_memory_tool.py +++ b/tests/test_memory_tool.py @@ -179,3 +179,61 @@ async def test_create_file_missing_text(memory_tool): assert result.exit_code == 1 assert result.output == "" assert "Error: file_text is required for create command" in result.error + + +@pytest.mark.asyncio +async def test_str_replace_success(memory_tool, temp_workspace): + """Test replacing unique string in a file.""" + test_file = temp_workspace / "memories" / "config.txt" + test_file.write_text("color: blue\nsize: large\n") + + result = await memory_tool( + command="str_replace", + path="/memories/config.txt", + old_str="blue", + new_str="green" + ) + + assert result.exit_code == 0 + assert result.error == "" + assert "The memory file has been edited." in result.output + + # Verify file was modified + assert test_file.read_text() == "color: green\nsize: large\n" + + +@pytest.mark.asyncio +async def test_str_replace_not_found(memory_tool, temp_workspace): + """Test replacing string that doesn't exist.""" + test_file = temp_workspace / "memories" / "config.txt" + test_file.write_text("color: blue\n") + + result = await memory_tool( + command="str_replace", + path="/memories/config.txt", + old_str="red", + new_str="green" + ) + + assert result.exit_code == 1 + assert result.output == "" + assert "No replacement was performed, old_str `red` did not appear verbatim" in result.error + + +@pytest.mark.asyncio +async def test_str_replace_duplicate(memory_tool, temp_workspace): + """Test replacing string that appears multiple times.""" + test_file = temp_workspace / "memories" / "config.txt" + test_file.write_text("color: blue\nbackground: blue\n") + + result = await memory_tool( + command="str_replace", + path="/memories/config.txt", + old_str="blue", + new_str="green" + ) + + assert result.exit_code == 1 + assert result.output == "" + assert "Multiple occurrences of old_str `blue`" in result.error + assert "Please ensure it is unique" in result.error