Source code for folderbot.tools.search_files

"""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))