repomind / tools /read_file.py
ZeroR3's picture
REPOMIND v0.1 — repo-scale coding agent demo
e3a472a
"""read_file tool — read a slice of a file inside the ingested repo root."""
from __future__ import annotations
from pathlib import Path
from .base import ToolResult, ToolSpec
def make_tool(repo_root: str | Path, max_bytes: int = 200_000) -> ToolSpec:
root = Path(repo_root).resolve()
def run(path: str, start_line: int = 1, end_line: int = -1) -> ToolResult:
# security: only allow paths inside the ingested root
target = (root / path).resolve()
try:
target.relative_to(root)
except ValueError:
return ToolResult(ok=False, output="", error=f"path outside repo: {path}")
if not target.exists():
return ToolResult(ok=False, output="", error=f"not found: {path}")
try:
text = target.read_text(encoding="utf-8", errors="replace")
except OSError as e:
return ToolResult(ok=False, output="", error=str(e))
if len(text) > max_bytes:
text = text[:max_bytes] + f"\n[... truncated at {max_bytes} bytes]"
lines = text.split("\n")
start = max(1, start_line)
end = len(lines) if end_line in (-1, 0) else min(len(lines), end_line)
slice_text = "\n".join(lines[start - 1:end])
numbered = "\n".join(f"{i:>5} {l}" for i, l in enumerate(lines[start - 1:end], start=start))
return ToolResult(
ok=True,
output=numbered,
extra={"path": path, "lines": (start, end), "total_lines": len(lines)},
)
return ToolSpec(
name="read_file",
description="Read a file from the ingested repo. Optionally restrict to a line range.",
parameters={
"type": "object",
"properties": {
"path": {"type": "string", "description": "Path relative to repo root."},
"start_line": {"type": "integer", "description": "1-indexed inclusive start.", "default": 1},
"end_line": {"type": "integer", "description": "1-indexed inclusive end. -1 = end of file.", "default": -1},
},
"required": ["path"],
},
runner=run,
)