361 lines
9.4 KiB
Python
361 lines
9.4 KiB
Python
"""File operation tools"""
|
|
import os
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
from backend.tools.factory import tool
|
|
|
|
|
|
# Base directory for file operations (sandbox)
|
|
# Set to None to allow any path, or set a specific directory for security
|
|
BASE_DIR = Path(__file__).parent.parent.parent.parent # project root
|
|
|
|
|
|
def _resolve_path(path: str) -> Path:
|
|
"""Resolve path and ensure it's within allowed directory"""
|
|
p = Path(path)
|
|
if not p.is_absolute():
|
|
p = BASE_DIR / p
|
|
p = p.resolve()
|
|
|
|
# Security check: ensure path is within BASE_DIR
|
|
if BASE_DIR:
|
|
try:
|
|
p.relative_to(BASE_DIR.resolve())
|
|
except ValueError:
|
|
raise ValueError(f"Path '{path}' is outside allowed directory")
|
|
|
|
return p
|
|
|
|
|
|
@tool(
|
|
name="file_read",
|
|
description="Read content from a file. Use when you need to read file content.",
|
|
parameters={
|
|
"type": "object",
|
|
"properties": {
|
|
"path": {
|
|
"type": "string",
|
|
"description": "File path to read (relative to project root or absolute)"
|
|
},
|
|
"encoding": {
|
|
"type": "string",
|
|
"description": "File encoding, default utf-8",
|
|
"default": "utf-8"
|
|
}
|
|
},
|
|
"required": ["path"]
|
|
},
|
|
category="file"
|
|
)
|
|
def file_read(arguments: dict) -> dict:
|
|
"""
|
|
Read file tool
|
|
|
|
Args:
|
|
arguments: {
|
|
"path": "file.txt",
|
|
"encoding": "utf-8"
|
|
}
|
|
|
|
Returns:
|
|
{"content": "...", "size": 100}
|
|
"""
|
|
try:
|
|
path = _resolve_path(arguments["path"])
|
|
encoding = arguments.get("encoding", "utf-8")
|
|
|
|
if not path.exists():
|
|
return {"error": f"File not found: {path}"}
|
|
|
|
if not path.is_file():
|
|
return {"error": f"Path is not a file: {path}"}
|
|
|
|
content = path.read_text(encoding=encoding)
|
|
return {
|
|
"content": content,
|
|
"size": len(content),
|
|
"path": str(path)
|
|
}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
@tool(
|
|
name="file_write",
|
|
description="Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Use when you need to create or update a file.",
|
|
parameters={
|
|
"type": "object",
|
|
"properties": {
|
|
"path": {
|
|
"type": "string",
|
|
"description": "File path to write (relative to project root or absolute)"
|
|
},
|
|
"content": {
|
|
"type": "string",
|
|
"description": "Content to write to the file"
|
|
},
|
|
"encoding": {
|
|
"type": "string",
|
|
"description": "File encoding, default utf-8",
|
|
"default": "utf-8"
|
|
},
|
|
"mode": {
|
|
"type": "string",
|
|
"description": "Write mode: 'write' (overwrite) or 'append'",
|
|
"enum": ["write", "append"],
|
|
"default": "write"
|
|
}
|
|
},
|
|
"required": ["path", "content"]
|
|
},
|
|
category="file"
|
|
)
|
|
def file_write(arguments: dict) -> dict:
|
|
"""
|
|
Write file tool
|
|
|
|
Args:
|
|
arguments: {
|
|
"path": "file.txt",
|
|
"content": "Hello World",
|
|
"encoding": "utf-8",
|
|
"mode": "write"
|
|
}
|
|
|
|
Returns:
|
|
{"success": true, "size": 11}
|
|
"""
|
|
try:
|
|
path = _resolve_path(arguments["path"])
|
|
content = arguments["content"]
|
|
encoding = arguments.get("encoding", "utf-8")
|
|
mode = arguments.get("mode", "write")
|
|
|
|
# Create parent directories if needed
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Write or append
|
|
if mode == "append":
|
|
with open(path, "a", encoding=encoding) as f:
|
|
f.write(content)
|
|
else:
|
|
path.write_text(content, encoding=encoding)
|
|
|
|
return {
|
|
"success": True,
|
|
"size": len(content),
|
|
"path": str(path),
|
|
"mode": mode
|
|
}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
@tool(
|
|
name="file_delete",
|
|
description="Delete a file. Use when you need to remove a file.",
|
|
parameters={
|
|
"type": "object",
|
|
"properties": {
|
|
"path": {
|
|
"type": "string",
|
|
"description": "File path to delete (relative to project root or absolute)"
|
|
}
|
|
},
|
|
"required": ["path"]
|
|
},
|
|
category="file"
|
|
)
|
|
def file_delete(arguments: dict) -> dict:
|
|
"""
|
|
Delete file tool
|
|
|
|
Args:
|
|
arguments: {
|
|
"path": "file.txt"
|
|
}
|
|
|
|
Returns:
|
|
{"success": true}
|
|
"""
|
|
try:
|
|
path = _resolve_path(arguments["path"])
|
|
|
|
if not path.exists():
|
|
return {"error": f"File not found: {path}"}
|
|
|
|
if not path.is_file():
|
|
return {"error": f"Path is not a file: {path}"}
|
|
|
|
path.unlink()
|
|
return {"success": True, "path": str(path)}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
@tool(
|
|
name="file_list",
|
|
description="List files and directories in a directory. Use when you need to see what files exist.",
|
|
parameters={
|
|
"type": "object",
|
|
"properties": {
|
|
"path": {
|
|
"type": "string",
|
|
"description": "Directory path to list (relative to project root or absolute)",
|
|
"default": "."
|
|
},
|
|
"pattern": {
|
|
"type": "string",
|
|
"description": "Glob pattern to filter files, e.g. '*.py'",
|
|
"default": "*"
|
|
}
|
|
},
|
|
"required": []
|
|
},
|
|
category="file"
|
|
)
|
|
def file_list(arguments: dict) -> dict:
|
|
"""
|
|
List directory contents
|
|
|
|
Args:
|
|
arguments: {
|
|
"path": ".",
|
|
"pattern": "*"
|
|
}
|
|
|
|
Returns:
|
|
{"files": [...], "directories": [...]}
|
|
"""
|
|
try:
|
|
path = _resolve_path(arguments.get("path", "."))
|
|
pattern = arguments.get("pattern", "*")
|
|
|
|
if not path.exists():
|
|
return {"error": f"Directory not found: {path}"}
|
|
|
|
if not path.is_dir():
|
|
return {"error": f"Path is not a directory: {path}"}
|
|
|
|
files = []
|
|
directories = []
|
|
|
|
for item in path.glob(pattern):
|
|
if item.is_file():
|
|
files.append({
|
|
"name": item.name,
|
|
"size": item.stat().st_size,
|
|
"path": str(item.relative_to(BASE_DIR)) if BASE_DIR else str(item)
|
|
})
|
|
elif item.is_dir():
|
|
directories.append({
|
|
"name": item.name,
|
|
"path": str(item.relative_to(BASE_DIR)) if BASE_DIR else str(item)
|
|
})
|
|
|
|
return {
|
|
"path": str(path),
|
|
"files": files,
|
|
"directories": directories,
|
|
"total_files": len(files),
|
|
"total_dirs": len(directories)
|
|
}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
@tool(
|
|
name="file_exists",
|
|
description="Check if a file or directory exists. Use when you need to verify file existence.",
|
|
parameters={
|
|
"type": "object",
|
|
"properties": {
|
|
"path": {
|
|
"type": "string",
|
|
"description": "Path to check (relative to project root or absolute)"
|
|
}
|
|
},
|
|
"required": ["path"]
|
|
},
|
|
category="file"
|
|
)
|
|
def file_exists(arguments: dict) -> dict:
|
|
"""
|
|
Check if file/directory exists
|
|
|
|
Args:
|
|
arguments: {
|
|
"path": "file.txt"
|
|
}
|
|
|
|
Returns:
|
|
{"exists": true, "type": "file"}
|
|
"""
|
|
try:
|
|
path = _resolve_path(arguments["path"])
|
|
|
|
if not path.exists():
|
|
return {"exists": False, "path": str(path)}
|
|
|
|
if path.is_file():
|
|
return {
|
|
"exists": True,
|
|
"type": "file",
|
|
"path": str(path),
|
|
"size": path.stat().st_size
|
|
}
|
|
elif path.is_dir():
|
|
return {
|
|
"exists": True,
|
|
"type": "directory",
|
|
"path": str(path)
|
|
}
|
|
else:
|
|
return {
|
|
"exists": True,
|
|
"type": "other",
|
|
"path": str(path)
|
|
}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
@tool(
|
|
name="file_mkdir",
|
|
description="Create a directory. Creates parent directories if needed. Use when you need to create a folder.",
|
|
parameters={
|
|
"type": "object",
|
|
"properties": {
|
|
"path": {
|
|
"type": "string",
|
|
"description": "Directory path to create (relative to project root or absolute)"
|
|
}
|
|
},
|
|
"required": ["path"]
|
|
},
|
|
category="file"
|
|
)
|
|
def file_mkdir(arguments: dict) -> dict:
|
|
"""
|
|
Create directory
|
|
|
|
Args:
|
|
arguments: {
|
|
"path": "new/folder"
|
|
}
|
|
|
|
Returns:
|
|
{"success": true}
|
|
"""
|
|
try:
|
|
path = _resolve_path(arguments["path"])
|
|
path.mkdir(parents=True, exist_ok=True)
|
|
return {
|
|
"success": True,
|
|
"path": str(path),
|
|
"created": not path.exists() or path.is_dir()
|
|
}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|