vgtc-api / src /hermes /tools /filesystem.py
vora-sonnet's picture
Upload folder using huggingface_hub
0d3f7cc verified
Raw
History Blame Contribute Delete
5.67 kB
"""Filesystem tool implementation."""
from __future__ import annotations
import logging
import os
from pathlib import Path
from typing import Any
from hermes.tools.base.tool import BaseTool, ToolSchema
logger = logging.getLogger(__name__)
class FilesystemTool(BaseTool):
"""Tool for filesystem operations."""
def __init__(self, base_path: str | None = None) -> None:
super().__init__()
self.base_path = Path(base_path) if base_path else Path.cwd()
def _define_schema(self) -> ToolSchema:
return ToolSchema(
name="file_reader",
description="Read, write, and list files on the filesystem",
parameters={
"action": {
"type": "string",
"description": "Action to perform: read, write, list, exists, mkdir",
},
"path": {
"type": "string",
"description": "File or directory path",
},
"content": {
"type": "string",
"description": "Content to write (for write action)",
},
"pattern": {
"type": "string",
"description": "Glob pattern for listing files",
"default": "*",
},
},
required=["action", "path"],
category="filesystem",
tags=["file", "read", "write", "filesystem"],
)
def _resolve_path(self, path: str) -> Path:
"""Resolve and validate path with symlink protection."""
base = self.base_path.resolve(strict=False)
candidate = (self.base_path / path).resolve(strict=False)
try:
candidate.relative_to(base)
except ValueError:
raise ValueError(f"Path escapes base directory: {path}") from None
if candidate.is_symlink():
raise ValueError(f"Symlinks not allowed: {path}")
return candidate
async def execute(self, **kwargs: Any) -> dict[str, Any]:
"""Execute filesystem operation."""
action = kwargs["action"]
path = kwargs["path"]
try:
if action == "read":
return await self._read_file(path)
elif action == "write":
return await self._write_file(path, kwargs.get("content", ""))
elif action == "list":
return await self._list_directory(path, kwargs.get("pattern", "*"))
elif action == "exists":
return await self._check_exists(path)
elif action == "mkdir":
return await self._make_directory(path)
else:
return {"error": f"Unknown action: {action}"}
except Exception as e:
logger.error(f"Filesystem error: {e}")
return {"error": str(e)}
async def _read_file(self, path: str) -> dict[str, Any]:
"""Read a file."""
resolved = self._resolve_path(path)
if not resolved.exists():
return {"error": f"File not found: {path}"}
if not resolved.is_file():
return {"error": f"Not a file: {path}"}
content = resolved.read_text(encoding="utf-8", errors="replace")
return {
"path": str(resolved),
"content": content,
"size": resolved.stat().st_size,
"modified": os.path.getmtime(resolved),
}
async def _write_file(self, path: str, content: str) -> dict[str, Any]:
"""Write to a file."""
resolved = self._resolve_path(path)
resolved.parent.mkdir(parents=True, exist_ok=True)
resolved.write_text(content, encoding="utf-8")
return {
"path": str(resolved),
"size": len(content),
"success": True,
}
async def _list_directory(self, path: str, pattern: str) -> dict[str, Any]:
"""List directory contents."""
resolved = self._resolve_path(path)
if not resolved.exists():
return {"error": f"Directory not found: {path}"}
if not resolved.is_dir():
return {"error": f"Not a directory: {path}"}
base_resolved = self.base_path.resolve()
items = []
for item in sorted(resolved.glob(pattern)):
try:
rel_path = str(item.relative_to(base_resolved))
except ValueError:
rel_path = item.name
items.append(
{
"name": item.name,
"path": rel_path,
"type": "directory" if item.is_dir() else "file",
"size": item.stat().st_size if item.is_file() else 0,
}
)
return {"path": str(resolved), "items": items, "count": len(items)}
async def _check_exists(self, path: str) -> dict[str, Any]:
"""Check if path exists."""
resolved = self._resolve_path(path)
return {
"path": str(resolved),
"exists": resolved.exists(),
"is_file": resolved.is_file() if resolved.exists() else False,
"is_dir": resolved.is_dir() if resolved.exists() else False,
}
async def _make_directory(self, path: str) -> dict[str, Any]:
"""Create a directory."""
resolved = self._resolve_path(path)
resolved.mkdir(parents=True, exist_ok=True)
return {"path": str(resolved), "created": True}