"""Search files tool."""
import re
from pydantic import BaseModel, Field
from ..bot import BotContext
from .base import ToolResult
from .registry import folder_bot, get_services
[docs]
class SearchFilesRequest(BaseModel, frozen=True):
"""Request for searching files."""
query: str = Field(description="Text to search for (case-insensitive)")
max_results: int = Field(
default=10, description="Maximum number of matching files to return."
)
[docs]
@folder_bot.tool(
name="search_files",
request_type=SearchFilesRequest,
response_type=ToolResult,
)
async def search_files(
request: SearchFilesRequest, _context: BotContext | None = None
) -> ToolResult:
"""Search for text content across all files in the folder.
Returns matching file paths and relevant excerpts.
Useful for finding files containing specific keywords.
"""
services = get_services(_context)
if services is None:
return ToolResult(content="Services not available", is_error=True)
pattern = re.compile(re.escape(request.query), re.IGNORECASE)
results: list[str] = []
for file_path in services.root.rglob("*"):
if not file_path.is_file():
continue
rel_path = str(file_path.relative_to(services.root))
if not services.is_file_allowed(rel_path):
continue
try:
content = file_path.read_text(encoding="utf-8", errors="replace")
matches = list(pattern.finditer(content))
if matches:
match = matches[0]
start = max(0, match.start() - 50)
end = min(len(content), match.end() + 50)
excerpt = content[start:end].replace("\n", " ")
results.append(f"{rel_path}:\n ...{excerpt}...")
if len(results) >= request.max_results:
break
except Exception:
continue
if not results:
return ToolResult(content=f"No files contain '{request.query}'")
return ToolResult(content="\n\n".join(results))