"""read_file.py – tool for reading files inside the sandbox.""" from __future__ import annotations from shlex import quote from typing import TYPE_CHECKING from langchain_core.tools import BaseTool, tool from loguru import logger if TYPE_CHECKING: from docker_agent_sandbox.sandbox import DockerSandbox def make_read_file_tool(sandbox: "DockerSandbox") -> BaseTool: """Return a read_file tool bound to *sandbox*.""" @tool def read_file(path: str, offset: int = 0, length: int = 5000) -> str: """ Read a file at *path*. *path* can be absolute (``/tmp/re-agent/result.csv``) or relative to the working directory. *offset* is the number of bytes to skip from the start of the file. *length* is the maximum number of bytes to return. If the file is longer than ``offset + length``, the output is trimmed and a summary line is appended showing how many bytes were omitted. Returns the (possibly trimmed) file contents as text, or an error message. """ logger.debug( "Reading file inside sandbox: {} offset={} length={}", path, offset, length ) exit_code, wc_out = sandbox.exec(f"wc -c -- {quote(path)}") if exit_code != 0: return f"[ERROR reading {path!r}] {wc_out.strip()}" try: total = int(wc_out.split()[0]) except (ValueError, IndexError): return f"[ERROR parsing file size for {path!r}] {wc_out.strip()}" exit_code, chunk = sandbox.exec( f"dd if={quote(path)} iflag=skip_bytes,count_bytes" f" skip={offset} count={length} 2>/dev/null" ) if exit_code != 0: return f"[ERROR reading {path!r}] {chunk.strip()}" suffix = "" if offset + length < total: remaining = total - (offset + length) suffix = f"\n[... {remaining} more bytes not shown (total {total} bytes). Use offset/length to read further.]" elif offset > 0 or total > length: suffix = f"\n[File total: {total} bytes, showing {len(chunk)} chars from offset {offset}.]" return chunk + suffix return read_file