ducklm/duck_core/tools/file_read.py

37 lines
1.4 KiB
Python

from pathlib import Path
from typing import Any
from duck_core.tools.base import ToolResult
from duck_core.tools.paths import WorkspacePathError, resolve_workspace_path
class FileReadTool:
name = "file_read"
risk_level = "low"
def __init__(self, workspace: str, max_bytes: int = 1_000_000):
self.workspace = workspace
self.max_bytes = max_bytes
async def run(self, args: dict[str, Any]) -> ToolResult:
raw_path = str(args.get("path", ""))
try:
path = resolve_workspace_path(self.workspace, raw_path)
except WorkspacePathError as exc:
return ToolResult(ok=False, error=str(exc))
if self._requires_approval(path):
return ToolResult(ok=False, error=f"Reading {raw_path} requires explicit approval")
if not path.is_file():
return ToolResult(ok=False, error=f"File not found: {raw_path}")
if path.stat().st_size > self.max_bytes:
return ToolResult(ok=False, error=f"File exceeds max size: {self.max_bytes}")
return ToolResult(
ok=True,
output=path.read_text(errors="replace"),
metadata={"path": str(path), "bytes_read": path.stat().st_size},
)
def _requires_approval(self, path: Path) -> bool:
parts = set(path.parts)
return path.name == ".env" or ".ssh" in parts or str(path) == "/etc/shadow"