mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-11 14:50:14 +00:00
Imported from andrewyng/aisuite@1b4bbf303e (contents of its platform/ directory, hoisted to the repo root). Development history prior to this commit lives in that repository. Co-authored-by: Devika <devikaverma11@gmail.com>
114 lines
3.8 KiB
Python
114 lines
3.8 KiB
Python
"""Line-numbered file reading (`read_file`) — replaces the aisuite toolkit's reader.
|
|
|
|
The toolkit's `read_file` returns raw text (the agent can't cite path:line without
|
|
counting) and raises outright on large files (the agent errors and guesses). This one
|
|
returns `cat -n`-style numbered lines, windows big files instead of failing, and tells
|
|
the agent how to continue reading. Read-only, workspace-scoped.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import aisuite as ai
|
|
|
|
_DEFAULT_MAX_LINES = 2000
|
|
_MAX_LINE_CHARS = 500
|
|
|
|
_SCHEMA = {
|
|
"type": "function",
|
|
"function": {
|
|
"name": "read_file",
|
|
"description": (
|
|
"Read a text file, returning numbered lines (' 12\\ttext') so code can be "
|
|
"referenced as path:line. Large files are windowed: pass start_line to continue "
|
|
"where the previous read stopped. Read-only."
|
|
),
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"path": {
|
|
"type": "string",
|
|
"description": "File path, relative to the workspace.",
|
|
},
|
|
"start_line": {
|
|
"type": "integer",
|
|
"description": "First line to read, 1-based (default 1).",
|
|
},
|
|
"max_lines": {
|
|
"type": "integer",
|
|
"description": f"How many lines (default {_DEFAULT_MAX_LINES}).",
|
|
},
|
|
},
|
|
"required": ["path"],
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
def file_tools(workspace: str) -> list:
|
|
root = Path(workspace).resolve()
|
|
|
|
def read_file(
|
|
path: str,
|
|
start_line: int = 1,
|
|
max_lines: int = _DEFAULT_MAX_LINES,
|
|
) -> dict[str, Any]:
|
|
start = start_line if isinstance(start_line, int) and start_line > 0 else 1
|
|
n = (
|
|
max_lines
|
|
if isinstance(max_lines, int) and max_lines > 0
|
|
else _DEFAULT_MAX_LINES
|
|
)
|
|
n = min(n, _DEFAULT_MAX_LINES)
|
|
target = (root / path).resolve()
|
|
try:
|
|
target.relative_to(root) # keep reads inside the workspace
|
|
except ValueError:
|
|
return {"error": "path escapes the workspace"}
|
|
if not target.is_file():
|
|
return {"error": f"not a file: {path}"}
|
|
|
|
selected: list[str] = []
|
|
total = 0
|
|
try:
|
|
with open(target, "r", encoding="utf-8", errors="replace") as fh:
|
|
for i, line in enumerate(fh, 1):
|
|
total = i
|
|
if i < start or len(selected) >= n:
|
|
continue
|
|
text = line.rstrip("\n")
|
|
if len(text) > _MAX_LINE_CHARS:
|
|
text = text[:_MAX_LINE_CHARS] + "… (line truncated)"
|
|
selected.append(f"{i:>6}\t{text}")
|
|
except OSError as exc:
|
|
return {"error": f"read failed: {exc}"}
|
|
|
|
end = start + len(selected) - 1 if selected else start - 1
|
|
result: dict[str, Any] = {
|
|
"path": str(target.relative_to(root)),
|
|
"start_line": start,
|
|
"end_line": end,
|
|
"total_lines": total,
|
|
"content": "\n".join(selected),
|
|
}
|
|
if end < total:
|
|
result["note"] = (
|
|
f"showing lines {start}-{end} of {total}; "
|
|
f"call again with start_line={end + 1} to continue"
|
|
)
|
|
return result
|
|
|
|
read_file.__name__ = "read_file"
|
|
read_file.__doc__ = _SCHEMA["function"]["description"]
|
|
read_file.__aisuite_tool_metadata__ = ai.ToolMetadata(
|
|
name="read_file",
|
|
category="filesystem",
|
|
risk_level="low",
|
|
capabilities=["read"],
|
|
requires_approval=False,
|
|
)
|
|
read_file.__coworker_schema__ = _SCHEMA
|
|
return [read_file]
|