Add EditMemoryTool

Implements #738
This commit is contained in:
Dominik Jain authored and Dominik Jain committed 2025-11-10 17:09:58 +01:00
1 parent 43457db31d
commit e2775684c4
3 files changed
+45 -6

No files matched your search

+4 -4
View File
@@ -26,21 +26,21 @@ class MemoriesManager:
self._memory_dir.mkdir(parents=True, exist_ok=True)
self._encoding = SERENA_FILE_ENCODING
def _get_memory_file_path(self, name: str) -> Path:
def get_memory_file_path(self, name: str) -> Path:
# strip all .md from the name. Models tend to get confused, sometimes passing the .md extension and sometimes not.
name = name.replace(".md", "")
filename = f"{name}.md"
return self._memory_dir / filename
def load_memory(self, name: str) -> str:
memory_file_path = self._get_memory_file_path(name)
memory_file_path = self.get_memory_file_path(name)
if not memory_file_path.exists():
return f"Memory file {name} not found, consider creating it with the `write_memory` tool if you need it."
with open(memory_file_path, encoding=self._encoding) as f:
return f.read()
def save_memory(self, name: str, content: str) -> str:
memory_file_path = self._get_memory_file_path(name)
memory_file_path = self.get_memory_file_path(name)
with open(memory_file_path, "w", encoding=self._encoding) as f:
f.write(content)
return f"Memory {name} written."
@@ -49,7 +49,7 @@ class MemoriesManager:
return [f.name.replace(".md", "") for f in self._memory_dir.iterdir() if f.is_file()]
def delete_memory(self, name: str) -> str:
memory_file_path = self._get_memory_file_path(name)
memory_file_path = self.get_memory_file_path(name)
memory_file_path.unlink()
return f"Memory {name} deleted."
+17 -1
View File
@@ -195,7 +195,23 @@ class ReplaceRegexTool(Tool, ToolMarkerCanEdit):
If this is set to False and the regex matches multiple occurrences, an error will be returned
(and you may retry with a revised, more specific regex).
"""
self.project.validate_relative_path(relative_path, require_not_ignored=True)
return self.replace_regex(
relative_path, regex, repl, allow_multiple_occurrences=allow_multiple_occurrences, require_not_ignored=True
)
def replace_regex(
self,
relative_path: str,
regex: str,
repl: str,
allow_multiple_occurrences: bool = False,
require_not_ignored: bool = True,
) -> str:
"""
Performs the regex replacement, with additional options not exposed in the tool.
This function can be used internally by other tools.
"""
self.project.validate_relative_path(relative_path, require_not_ignored=require_not_ignored)
with EditedFileContext(relative_path, self.agent) as context:
original_content = context.get_original_content()
updated_content, n = re.subn(regex, repl, original_content, flags=re.DOTALL | re.MULTILINE)
+24 -1
View File
@@ -1,6 +1,6 @@
import json
from serena.tools import Tool
from serena.tools import ReplaceRegexTool, Tool
class WriteMemoryTool(Tool):
@@ -64,3 +64,26 @@ class DeleteMemoryTool(Tool):
or no longer relevant for the project.
"""
return self.memories_manager.delete_memory(memory_file_name)
class EditMemoryTool(Tool):
def apply(
self,
memory_file_name: str,
regex: str,
repl: str,
) -> str:
r"""
Replaces content matching a regular expression in a memory.
:param memory_file_name: the name of the memory
:param regex: a Python-style regular expression (flags DOTALL and MULTILINE enabled, i.e.
'.' matches all characters, multi-line matching is enabled).
Apply the usual escaping as needed for reserved characters in Python-style regex.
:param repl: the string to replace the matched content with, which may contain
backreferences like \1, \2, etc. for groups matched by the regex.
Insert new content verbatim, except for backslashes, which have to be escaped.
"""
replace_regex_tool = self.agent.get_tool(ReplaceRegexTool)
rel_path = self.memories_manager.get_memory_file_path(memory_file_name).relative_to(self.get_project_root())
return replace_regex_tool.replace_regex(str(rel_path), regex, repl, require_not_ignored=False)