class FileSearchToolsMixin:
"""
Mixin providing shared file search and read operations.
Register with `self.register_file_search_tools()`.
Tools provided:
- search_file: Multi-phase intelligent file search
- search_directory: Directory search by name
- read_file: Type-aware file reading with analysis
- search_file_content: Grep-like content search
- write_file: Generic file writer
- edit_file: Partial string replacement in an existing file
- browse_directory: List directory contents with metadata
- get_file_info: Inspect a single file's metadata
- analyze_data_file: Summarise CSV / JSON / XLSX / log files
- list_recent_files: Surface recently-modified files by location
"""
@tool
def search_file(
file_pattern: str,
directory: str = None,
deep_search: bool = False,
file_types: str = None
) -> Dict[str, Any]:
"""
Search for files with two-phase strategy.
Quick search (default): workspace roots + common locations
Scoped search (directory=...): that directory only
Deep search (deep_search=True): All drives (Windows) or root (Unix)
Always do quick search first. Only use deep_search=True after quick search
found nothing and user confirms they want a deeper search. A named
directory is never widened, deep_search or not.
Args:
file_pattern: Pattern to search (e.g., 'oil', '*.pdf')
directory: Restrict the search to this folder. Pass it whenever the
user names one. A relative path resolves against the deepest
workspace root containing it.
deep_search: Search all drives thoroughly (default: False)
file_types: Filter by extensions (e.g., 'pdf,docx')
Returns:
{
"status": "success",
"files": List[str], # Paths (max 10)
"file_list": List[{ # Formatted for display
"number": int,
"name": str,
"path": str,
"directory": str
}],
"count": int,
"search_context": "common_locations",
"display_message": str,
"deep_search_available": bool, # True when quick search found nothing
"user_instruction": str # If multiple found
}
"""
pass
@tool
def search_directory(
directory_name: str,
search_root: str = None,
max_depth: int = 4
) -> Dict[str, Any]:
"""
Search for directories by name.
Args:
directory_name: Directory name pattern
search_root: Root to start from (default: home)
max_depth: Max recursion depth (default: 4)
Returns:
{
"status": "success",
"directories": List[str], # Max 10
"count": int,
"message": str
}
"""
pass
@tool
def read_file(file_path: str) -> Dict[str, Any]:
"""
Read file with intelligent type-based analysis.
File Type Support:
- Python (.py): AST validation + symbol extraction (functions/classes)
- Markdown (.md): Headers + code blocks + links extraction
- Word (.docx), PDF, Excel (.xlsx): extracted text in budget-sized
parts ("offset", "total_chars", "next_offset"; None at the end).
Same contract as FileIOToolsMixin.read_file. Other binary document
formats (.doc, .pptx, .xls, OpenDocument) return an error pointing
at index_document.
- Binary: Detection with size reporting
- Text: Raw content with line count. A file with a UTF-16, UTF-32,
or UTF-8 byte-order mark is decoded in that encoding and reports
it as "encoding".
A successful read, a paged one included, is what lets write_file and
edit_file change the file afterwards.
Args:
file_path: Path to file
Returns:
{
"status": "success",
"file_path": str,
"file_type": "python" | "markdown" | "binary" | str,
"content": str,
"line_count": int,
"size_bytes": int,
# Python-specific
"is_valid": bool,
"errors": List[str],
"symbols": List[{
"name": str,
"type": "function" | "class",
"line": int
}],
# Markdown-specific
"headers": List[str],
"code_blocks": List[{
"language": str,
"code": str
}],
"links": List[{
"text": str,
"url": str
}],
# Binary-specific
"is_binary": bool
}
"""
pass
@tool
def search_file_content(
pattern: str,
directory: str = ".",
file_pattern: str = None,
case_sensitive: bool = False
) -> Dict[str, Any]:
"""
Grep-like file content search.
Args:
pattern: Text pattern to find
directory: Where to search (default: current)
file_pattern: File glob filter (e.g., '*.py')
case_sensitive: Case sensitivity (default: False)
Returns:
{
"status": "success",
"pattern": str,
"matches": List[{
"file": str,
"line": int,
"content": str # Max 200 chars; a longer line is cut to a
# window around the match, "..." marking each cut
}],
"total_matches": int, # Max 100
"files_searched": int,
"message": str
}
"""
pass
@tool
def write_file(
file_path: str,
content: str,
create_dirs: bool = True
) -> Dict[str, Any]:
"""
Write content to file.
Creating a file needs no read. Overwriting an existing one is refused
unless this agent has read it with read_file, and it hasn't changed on
disk since — the same rule as edit_file.
Args:
file_path: Target path
content: File content
create_dirs: Create parent dirs (default: True)
Returns:
{
"status": "success",
"file_path": str,
"bytes_written": int,
"line_count": int
}
"""
pass
@tool
def edit_file(
file_path: str,
old_content: str,
new_content: str,
) -> Dict[str, Any]:
"""
Edit a file by replacing old content with new content.
Partial string replacement rather than a full overwrite — like Claude
Code's Edit tool. Runs the same security guardrails as write_file
(PathValidator allowlist, blocked-dir and sensitive-file checks, backup
creation, audit logging). The target file must already exist.
The agent must have read the file with read_file first (a paged read
counts), and the file must not have changed on disk since — a
different mtime or size. Otherwise the edit is refused before anything
is written, and before any confirmation prompt; read the file again
to proceed. A file this agent created or already edited needs no read.
old_content must match exactly one location. Matching several is an
error, not a first-match replacement: picking one would edit a region
the caller never named while reporting success. Matching nothing is an
error too, and carries the file's current content so the retry needs
no separate read. The semantics live in `gaia.agents.tools.file_edit`,
shared by every edit tool.
Args:
file_path: File to edit
old_content: Exact text to find and replace; must be unique in the
file — include surrounding lines until it is
new_content: Replacement text
Returns:
{
"status": "success",
"file_path": str,
"old_size": int, # chars before edit
"new_size": int, # chars after edit
"diff": str, # unified diff
"backup_path": str # only when a backup was created; under
# ~/.gaia/cache/backups/, never beside the file
# (owner-only; newest five per file kept)
}
On a match rejection:
{
"status": "error",
"error": str, # names the cause and the fix
"match_count": int, # 0 = not found, >1 = ambiguous
"match_lines": [int], # ambiguous only
"current_content": str, # excerpt around the target region
"current_content_start_line": int,
"current_content_end_line": int,
"current_content_total_lines": int
}
On a read-first refusal:
{
"status": "error",
"error": str, # names the file and says to read it
"error_type": "not_read" | "changed_since_read",
"file_path": str,
"executed": False,
"operation": "edit_file"
}
"""
pass
@tool
def browse_directory(
directory_path: str = None,
show_hidden: bool = False,
sort_by: str = "name",
) -> Dict[str, Any]:
"""
List a directory's contents with file metadata.
Args:
directory_path: Directory to list (default: current working dir)
show_hidden: Include dotfiles
sort_by: "name", "size", or "mtime"
Returns:
{"status": "success",
"directory": str,
"entries": List[{"name": str, "is_dir": bool,
"size_bytes": int, "mtime": float}]}
"""
pass
@tool
def get_file_info(file_path: str) -> Dict[str, Any]:
"""
Return metadata for a single file (size, mtime, type, permissions).
"""
pass
@tool
def analyze_data_file(file_path: str) -> Dict[str, Any]:
"""
Summarise a data file (CSV, JSON, XLSX, log, …) with row/column counts,
column names, sample rows, and inferred dtypes. Used when the agent
needs a quick overview of a dataset before reading it in full.
"""
pass
@tool
def list_recent_files(
location: str = "all",
file_types: str = None,
max_results: int = 20,
days: int = 30,
) -> Dict[str, Any]:
"""
Return recently-modified files in Documents, Downloads, Desktop, and
OneDrive. location is documents, downloads, desktop, or all.
max_results (1-200) bounds files and display_message. The redundant all_files field is removed.
total_found counts all matches; truncated reports omitted matches.
Narrow location, file_types, or days to retrieve other matches.
"""
pass