from pathlib import Path from personal_agent.tools.base import Tool, ToolError, ToolResult class FileSystemListTool(Tool): @property def name(self) -> str: return "filesystem_list" @property def description(self) -> str: return "List files and directories inside the agent's sandbox." @property def schema(self) -> dict[str, object]: return { "type": "object", "properties": { "path": { "type": "string", "description": ( "Relative path inside the filesystem sandbox. " "Use '.' for the sandbox root." ), }, }, "required": ["path"], } def execute(self, arguments: dict[str, object]) -> ToolResult: path = arguments.get("path") if not isinstance(path, str): raise ToolError("path must be a string") sandbox = Path.home() / "Projects" / "personal-agent" / "data" target = (sandbox / path).resolve() if not target.is_relative_to(sandbox.resolve()): raise ToolError("path is outside the filesystem sandbox") if not target.exists(): raise ToolError(f"path does not exist: {path}") if not target.is_dir(): raise ToolError(f"path is not a directory: {path}") entries = [] for entry in sorted(target.iterdir()): kind = "directory" if entry.is_dir() else "file" entries.append(f"{kind}: {entry.name}") if not entries: return ToolResult(content="Directory is empty.") return ToolResult(content="\n".join(entries)) class FileSystemReadTool(Tool): MAX_FILE_SIZE = 1024 * 1024 # 1 MiB @property def name(self) -> str: return "filesystem_read" @property def description(self) -> str: return "Read the contents of a file inside the agent's sandbox." @property def schema(self) -> dict[str, object]: return { "type": "object", "properties": { "path": { "type": "string", "description": ("Relative path inside the filesystem sandbox."), }, }, "required": ["path"], } def execute(self, arguments: dict[str, object]) -> ToolResult: path = arguments.get("path") if not isinstance(path, str): raise ToolError("path must be a string") sandbox = Path.home() / "Projects" / "personal-agent" / "data" sandbox = sandbox.resolve() target = (sandbox / path).resolve() if not target.is_relative_to(sandbox): raise ToolError("path is outside the filesystem sandbox") if not target.exists(): raise ToolError(f"path does not exist: {path}") if not target.is_file(): raise ToolError(f"path is not a file: {path}") if target.stat().st_size > self.MAX_FILE_SIZE: raise ToolError("file is too large to read") try: content = target.read_text(encoding="utf-8") except ToolError as error: raise ToolError("file is not valid UTF-8 text") from error return ToolResult(content=content)