# ════════════════════════════════════════════════════════════════ # tools/file_reader.py # ════════════════════════════════════════════════════════════════ """文件读取工具(从配置读取 allowed_root / max_file_size_kb)""" from pathlib import Path from tools.base_tool import BaseTool, ToolResult from config.settings import settings class FileReaderTool(BaseTool): name = "file_reader" description = "读取本地文件内容,仅限配置的 allowed_root 目录" parameters = { "path": {"type": "string", "description": "文件路径(相对于 allowed_root)"}, "encoding": {"type": "string", "description": "文件编码,默认 utf-8"}, } def __init__(self): super().__init__() cfg = settings.tools['file_reader'] self._allowed_root = Path(cfg['allowed_root']) self._max_size_kb = cfg['max_file_size_kb'] self.logger.debug( f"⚙️ FileReader root={self._allowed_root}, " f"max_size={self._max_size_kb}KB" ) def execute(self, path: str, encoding: str = "utf-8", **_) -> ToolResult: self._allowed_root.mkdir(parents=True, exist_ok=True) target = (self._allowed_root / path).resolve() if not str(target).startswith(str(self._allowed_root.resolve())): return ToolResult(success=False, output="❌ 拒绝访问: 路径超出允许范围") if not target.exists(): self._create_demo_file(target) size_kb = target.stat().st_size / 1024 if size_kb > self._max_size_kb: return ToolResult( success=False, output=f"❌ 文件过大: {size_kb:.1f}KB > 限制 {self._max_size_kb}KB", ) try: content = target.read_text(encoding=encoding) return ToolResult( success=True, output=f"文件 [{path}] ({size_kb:.1f}KB):\n{content}", metadata={"path": str(target), "size_kb": size_kb}, ) except OSError as exc: return ToolResult(success=False, output=f"读取失败: {exc}") @staticmethod def _create_demo_file(path: Path) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text( '{\n "app": "AgentDemo",\n "version": "1.0.0",\n' ' "llm": "claude-sonnet-4-6",\n "tools": ["calculator","web_search"]\n}\n', encoding="utf-8", )