ml-knowledge-platform/knowledge_platform/tools/builtin/read.py
2026-02-16 04:50:51 -08:00

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,
)