Source code for folderbot.tools.read_file

"""Read file tool."""

from pydantic import BaseModel, Field

from ..bot import BotContext
from .base import ToolResult
from .constants import MAX_FILE_READ_CHARS
from .registry import folder_bot, get_services


[docs] class ReadFileRequest(BaseModel, frozen=True): """Request for reading a file.""" path: str = Field( description="File path relative to the root folder (e.g., 'notes/todo.md')" )
[docs] @folder_bot.tool( name="read_file", request_type=ReadFileRequest, response_type=ToolResult, ) async def read_file( request: ReadFileRequest, _context: BotContext | None = None ) -> ToolResult: """Read the contents of a specific file. The path must be relative to the root folder. Use list_files first to discover available files. """ services = get_services(_context) if services is None: return ToolResult(content="Services not available", is_error=True) file_path = services.validate_path(request.path) if file_path is None: return ToolResult(content="Invalid path: access denied", is_error=True) if not file_path.exists(): return ToolResult(content=f"File not found: {request.path}", is_error=True) if not file_path.is_file(): return ToolResult(content=f"Not a file: {request.path}", is_error=True) rel_path = str(file_path.relative_to(services.root)) if not services.is_file_allowed(rel_path): return ToolResult(content=f"File not accessible: {request.path}", is_error=True) try: content = file_path.read_text(encoding="utf-8", errors="replace") if len(content) > MAX_FILE_READ_CHARS: content = ( content[:MAX_FILE_READ_CHARS] + f"\n\n[Truncated at {MAX_FILE_READ_CHARS} characters]" ) return ToolResult(content=content) except Exception as e: return ToolResult(content=f"Error reading file: {e}", is_error=True)