File size: 2,152 Bytes
e3a472a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
"""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,
    )