ReMe/reme/tool/fs/fs_memory_get.py

89 lines
3.2 KiB
Python

"""Memory get tool for reading specific snippets from memory files."""
import os
from pathlib import Path
from reme.core.schema import ToolCall
from .base_fs_tool import BaseFsTool
class FsMemoryGet(BaseFsTool):
"""Read specific snippets from memory files."""
def __init__(self, cwd: str | None = None, **kwargs):
"""Initialize memory get tool."""
kwargs.setdefault("name", "memory_get")
super().__init__(**kwargs)
self.cwd = cwd or os.getcwd()
def _build_tool_call(self) -> ToolCall:
return ToolCall(
**{
"description": (
"Safe snippet read from MEMORY.md, memory/*.md with optional offset/limit; "
"use after memory_search to pull only the needed lines and keep context small."
),
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Path to the memory file to read (relative or absolute)",
},
"offset": {
"type": "integer",
"description": "Starting line number (1-indexed, optional)",
},
"limit": {
"type": "integer",
"description": "Number of lines to read from the starting line (optional)",
},
},
"required": ["path"],
},
},
)
async def execute(self) -> str:
"""Execute the memory get operation."""
raw_path: str = self.context.path.strip()
offset: int | None = self.context.get("offset", None)
limit: int | None = self.context.get("limit", None)
if os.path.isabs(raw_path):
abs_path = os.path.abspath(raw_path)
else:
abs_path = os.path.abspath(os.path.join(self.cwd, raw_path))
assert abs_path.lower().endswith(".md")
# Check file exists, is not a symlink, and is a regular file
file_path = Path(abs_path)
assert (
file_path.exists() and not file_path.is_symlink() and file_path.is_file()
), f"File not found or not a regular file: {abs_path}"
with open(abs_path, "r", encoding="utf-8") as f:
content = f.read()
if offset is None and limit is None:
return content
lines = content.split("\n")
total_lines = len(lines)
# Validate and normalize offset (1-indexed)
start = offset if offset is not None else 1
assert start >= 1, f"offset must be >= 1, got {start}"
assert start <= total_lines, f"offset {start} exceeds total lines {total_lines}"
# Validate and calculate count
if limit is not None:
assert limit > 0, f"limit must be positive, got {limit}"
count = limit
else:
# Read from start to end of file
count = total_lines - start + 1
# Extract slice (1-indexed to 0-indexed conversion)
selected = lines[start - 1 : start - 1 + count]
return "\n".join(selected)