""" Tool registrations for the Explore agent. Registers our tool implementations with the PydanticAI agent. """ from pydantic_ai import Agent, RunContext from src.domains.agents.base import AgentContext from src.domains.tools.file.read import ReadFileTool from src.domains.tools.file.glob import GlobFilesTool from src.domains.tools.search.grep import GrepContentTool from src.domains.tools.shell.bash import BashReadOnlyTool def register_explore_tools(agent: Agent[AgentContext, str]) -> None: """ Register all exploration tools with the agent. Each tool is wrapped to use context from RunContext. """ @agent.tool async def read_file( ctx: RunContext[AgentContext], file_path: str, offset: int = 0, limit: int = 2000 ) -> str: """Read contents of a file with line numbers. Args: file_path: Absolute path to the file to read offset: Line number to start from (0-based, default: 0) limit: Maximum number of lines to read (default: 2000) Returns: File contents with line numbers, or error message. IMPORTANT: Always use absolute paths. Never guess file contents. """ tool = ReadFileTool(allowed_paths=ctx.deps.allowed_paths) result = await tool.execute( file_path=file_path, offset=offset, limit=limit ) return result.to_string() @agent.tool async def glob_files( ctx: RunContext[AgentContext], pattern: str, path: str | None = None, limit: int = 100 ) -> str: """Find files matching a glob pattern. Args: pattern: Glob pattern (e.g., "**/*.py", "src/**/*.ts", "*.md") path: Directory to search in (default: working directory) limit: Maximum number of files to return (default: 100) Returns: List of absolute file paths, sorted by modification time (newest first). Examples: - "**/*.py" finds all Python files - "src/**/*.ts" finds TypeScript files in src/ - "**/test_*.py" finds all test files IMPORTANT: Use this to discover files before reading them. """ tool = GlobFilesTool(allowed_paths=ctx.deps.allowed_paths) search_path = path or ctx.deps.working_dir result = await tool.execute( pattern=pattern, path=search_path, limit=limit ) return result.to_string() @agent.tool async def grep_content( ctx: RunContext[AgentContext], pattern: str, path: str | None = None, file_glob: str | None = None, context_lines: int = 0, case_sensitive: bool = True ) -> str: """Search file contents using regex pattern. Args: pattern: Regex pattern to search for (Python re syntax) path: Directory or file to search (default: working directory) file_glob: Filter files by glob (e.g., "*.py", "*.ts") context_lines: Lines of context before/after matches (default: 0) case_sensitive: Case-sensitive search (default: True) Returns: Matching lines with file paths and line numbers. Format: "filepath:line_num: content" Examples: - pattern="def.*__init__" finds init methods - pattern="class\\s+\\w+" finds class definitions - pattern="TODO|FIXME" finds todo comments IMPORTANT: Use this to search for code patterns. Escape regex special chars. """ tool = GrepContentTool(allowed_paths=ctx.deps.allowed_paths) search_path = path or ctx.deps.working_dir result = await tool.execute( pattern=pattern, path=search_path, file_glob=file_glob, context_lines=context_lines, case_sensitive=case_sensitive ) return result.to_string() @agent.tool async def bash_readonly( ctx: RunContext[AgentContext], command: str, cwd: str | None = None, timeout: int = 30 ) -> str: """Execute a read-only bash command. ALLOWED commands: - File inspection: ls, find, cat, head, tail, wc, file, stat, tree, du - Git (read-only): git status, git log, git diff, git show, git branch - Text processing: grep, awk, sed (read-only), sort, uniq - System info: pwd, whoami, hostname, which FORBIDDEN: - File modification (rm, mv, cp, mkdir, touch) - Redirects (>, >>) - Command chaining (&&, ||, ;) - Network (curl, wget) Args: command: The bash command to execute cwd: Working directory (default: agent working directory) timeout: Timeout in seconds (default: 30) Returns: Command output or error message. Examples: - "ls -la" lists files with details - "git status" shows git status - "git log --oneline -10" shows recent commits """ tool = BashReadOnlyTool(allowed_paths=ctx.deps.allowed_paths) working_dir = cwd or ctx.deps.working_dir result = await tool.execute( command=command, cwd=working_dir, timeout=min(timeout, ctx.deps.timeout_seconds) ) return result.to_string()