# Copyright (c) 2025
# SPDX-License-Identifier: MIT
"""File reader tool - reads files from the workspace directory."""
from __future__ import annotations
from pathlib import Path
from typing import Any
from ..security.path_validator import PathValidator, PathValidationError
from ..security.file_validator import FileValidator, FileValidationError
from ..utils.logging import get_logger
from .base import BaseTool, ToolError
logger = get_logger("tools.file_reader")
class FileReaderTool(BaseTool):
"""Tool for reading files from the workspace directory.
This tool allows reading files that have been created by skills
or modified by the user.
"""
def __init__(
self,
workspace_dir: Path,
file_validator: FileValidator,
) -> None:
"""Initialize the tool.
Args:
workspace_dir: Path to the workspace directory.
file_validator: FileValidator for checking file restrictions.
"""
self.workspace_dir = workspace_dir
self.path_validator = PathValidator(workspace_dir)
self.file_validator = file_validator
@property
def name(self) -> str:
return "file_read"
@property
def description(self) -> str:
return (
"Read a file from the workspace directory. "
"Use this tool to read files generated by skills or modified by the user."
)
@property
def input_schema(self) -> dict[str, Any]:
return {
"type": "object",
"properties": {
"file_path": {
"type": "string",
"description": "Path to the file, relative to the workspace directory",
},
},
"required": ["file_path"],
}
def execute(self, file_path: str = "", **kwargs: Any) -> str:
"""Read a file from the workspace.
Args:
file_path: Relative path to the file.
Returns:
File content.
"""
logger.info(f"Reading file: {file_path}")
if not file_path:
raise ToolError("file_path is required")
# Validate path
try:
target_path = self.path_validator.validate_file(file_path)
except PathValidationError as e:
return f"Error: {e}"
# Validate file type and size
try:
file_size = self.file_validator.validate_for_read(target_path)
except FileValidationError as e:
return f"Error: {e}"
# Read the file
try:
content = target_path.read_text(encoding="utf-8")
except UnicodeDecodeError:
return f"Error: Cannot read file '{file_path}' - not a valid text file"
except Exception as e:
return f"Error reading file: {e}"
line_count = len(content.splitlines())
return (
f"## File: {file_path}\n"
f"**Workspace**: {self.workspace_dir}\n"
f"**Size**: {file_size} bytes ({line_count} lines)\n\n"
f"---\n\n{content}"
)