oclaw/runtime/tools/public/read_file_tool.py
oliver 420abac9f1 refactor: root-package imports (svc/runtime/interfaces) and fix PYTHONPATH
- Rename platform/ to svc/ to avoid shadowing stdlib platform.
- Replace from oclaw.* with from svc/runtime/interfaces; update -m CLI paths.
- tests/conftest: prepend repo root to sys.path (no parent-folder package name).
- CI: paths and offline_eval script under repo root.
- Ops scripts: PYTHONPATH must be repo root for python -m runtime.* (fixes gateway/WhatsApp sidecar startup).
- Fix default oclaw.json path in tabular/file attachment limits; stabilize attachment test config.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 14:51:17 +08:00

60 lines
2.1 KiB
Python

from __future__ import annotations
import hashlib
from typing import Any
from runtime.tools.base import ToolSpec
from runtime.tools.path_guard import resolve_workspace_path
def read_file_tool() -> ToolSpec:
def _handler(args: dict[str, Any]) -> dict[str, Any]:
path = str(args.get("path") or "").strip()
offset = int(args.get("offset") or 1)
limit = int(args.get("limit") or 400)
if offset == 0:
offset = 1
if limit <= 0:
limit = 1
p = resolve_workspace_path(path)
if not p.exists() or not p.is_file():
return {"ok": False, "error": "file_not_found", "path": str(p)}
text = p.read_text(encoding="utf-8", errors="replace").splitlines()
if offset < 0:
start = max(0, len(text) + offset)
else:
start = max(0, offset - 1)
end = min(len(text), start + min(limit, 2000))
out_lines = [f"{i + 1}|{text[i]}" for i in range(start, end)]
sha = hashlib.sha256(p.read_bytes()).hexdigest()
return {
"ok": True,
"path": str(p),
"start_line": start + 1,
"end_line": end,
"total_lines": len(text),
"sha256": sha,
"content": "\n".join(out_lines),
}
return ToolSpec(
name="read_file",
description="Read a text file from the workspace with line numbers.",
parameters={
"type": "object",
"properties": {
"path": {"type": "string", "description": "File path, relative to workspace root."},
"offset": {"type": "integer", "description": "1-indexed start line; negative counts from end.", "default": 1},
"limit": {"type": "integer", "description": "Max lines to return (capped).", "default": 400},
},
"required": ["path"],
"additionalProperties": False,
},
handler=_handler,
tags=frozenset({"public", "workspace"}),
read_only=True,
risk_level="low",
)
__all__ = ["read_file_tool"]