"""Filesystem side-effect validator."""
from pathlib import Path
from typing import Any
from toolscore.adapters.base import ToolCall
[docs]
class FileSystemValidator:
"""Validator for filesystem-related side effects.
Checks if files or directories exist or have expected properties.
Can also validate file content.
"""
[docs]
def __init__(
self,
base_path: str | None = None,
contains: str | list[str] | None = None,
min_size: int | None = None,
max_size: int | None = None,
) -> None:
"""Initialize filesystem validator.
Args:
base_path: Base directory for resolving relative paths.
contains: String or list of strings that file content must contain.
min_size: Minimum file size in bytes.
max_size: Maximum file size in bytes.
"""
self.base_path = Path(base_path) if base_path else Path.cwd()
self.contains = [contains] if isinstance(contains, str) else contains
self.min_size = min_size
self.max_size = max_size
[docs]
def validate(self, call: ToolCall, expected: Any) -> bool:
"""Validate filesystem side effect.
Args:
call: The tool call to validate.
expected: Expected value (filename/path that should exist).
Returns:
True if validation passes, False otherwise.
"""
# Get the file path to check
file_path = self._get_file_path(call, expected)
if not file_path:
return False
# Check if file exists
path = self.base_path / file_path if not Path(file_path).is_absolute() else Path(file_path)
if not path.exists():
return False
# If file exists but is a directory, and we're checking file properties, fail
if path.is_dir() and (self.contains or self.min_size or self.max_size):
return False
# Validate file size if constraints are set
if path.is_file():
file_size = path.stat().st_size
if self.min_size is not None and file_size < self.min_size:
return False
if self.max_size is not None and file_size > self.max_size:
return False
# Validate content if contains is set
if self.contains and path.is_file():
try:
content = path.read_text(encoding="utf-8")
for required_string in self.contains:
if required_string not in content:
return False
except (UnicodeDecodeError, OSError):
# Can't read file content
return False
return True
def _get_file_path(self, call: ToolCall, expected: Any) -> str | None:
"""Extract file path from call or expected value.
Args:
call: The tool call.
expected: Expected value.
Returns:
File path string or None.
"""
# If expected is a string, it's the path to check
if isinstance(expected, str):
return expected
# Try to find path in call arguments
if call.args:
for key in ["path", "file", "filename", "filepath", "file_path", "output"]:
if key in call.args:
return str(call.args[key])
# Check result for file path
if isinstance(call.result, str):
return call.result
if isinstance(call.result, dict) and "path" in call.result:
return str(call.result["path"])
return None