94 lines
3 KiB
Python
94 lines
3 KiB
Python
"""ReadTool — read file contents with line numbers (cat -n format).
|
|
|
|
Returns lines from offset to offset+limit, formatted with right-aligned
|
|
line numbers and tab-separated content.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from typing import Any, ClassVar
|
|
|
|
from ..base import Tool, ToolParameter, ToolResult
|
|
|
|
|
|
class ReadTool(Tool):
|
|
"""Read file contents with line numbers.
|
|
|
|
Returns lines in ``cat -n`` format: `` 1\\tline content``
|
|
Supports offset and limit for paginating through large files.
|
|
"""
|
|
|
|
name: ClassVar[str] = "read"
|
|
description: ClassVar[str] = (
|
|
"Read a file and return its contents with line numbers. "
|
|
"Supports offset and limit for large files."
|
|
)
|
|
parameters: ClassVar[list[ToolParameter]] = [
|
|
ToolParameter(
|
|
name="file_path",
|
|
type="string",
|
|
description="Absolute path to the file to read",
|
|
),
|
|
ToolParameter(
|
|
name="offset",
|
|
type="integer",
|
|
description="Line number to start reading from (0-based, default 0)",
|
|
required=False,
|
|
default=0,
|
|
),
|
|
ToolParameter(
|
|
name="limit",
|
|
type="integer",
|
|
description="Maximum number of lines to return (default 2000)",
|
|
required=False,
|
|
default=2000,
|
|
),
|
|
]
|
|
|
|
async def execute(self, **kwargs: Any) -> ToolResult:
|
|
file_path = Path(kwargs["file_path"])
|
|
offset: int = kwargs.get("offset", 0)
|
|
limit: int = kwargs.get("limit", 2000)
|
|
|
|
if not file_path.is_absolute():
|
|
return ToolResult.fail(f"Path must be absolute: {file_path}")
|
|
|
|
if not file_path.exists():
|
|
return ToolResult.fail(f"File not found: {file_path}")
|
|
|
|
if not file_path.is_file():
|
|
return ToolResult.fail(f"Not a file: {file_path}")
|
|
|
|
try:
|
|
text = file_path.read_text(encoding="utf-8")
|
|
except UnicodeDecodeError:
|
|
return ToolResult.fail(f"Cannot read binary file: {file_path}")
|
|
except PermissionError:
|
|
return ToolResult.fail(f"Permission denied: {file_path}")
|
|
|
|
all_lines = text.splitlines()
|
|
total_lines = len(all_lines)
|
|
selected = all_lines[offset : offset + limit]
|
|
|
|
if not selected and total_lines > 0:
|
|
return ToolResult.fail(
|
|
f"Offset {offset} is beyond end of file ({total_lines} lines)"
|
|
)
|
|
|
|
# Format with right-aligned line numbers (cat -n style)
|
|
# Line numbers are 1-based
|
|
max_num_width = len(str(offset + len(selected))) if selected else 1
|
|
formatted_lines: list[str] = []
|
|
for i, line in enumerate(selected):
|
|
line_num = offset + i + 1 # 1-based
|
|
formatted_lines.append(f"{line_num:>{max_num_width}}\t{line}")
|
|
|
|
output = "\n".join(formatted_lines)
|
|
|
|
return ToolResult.success(
|
|
output,
|
|
total_lines=total_lines,
|
|
lines_returned=len(selected),
|
|
offset=offset,
|
|
)
|