Files
webber/webber-api/src/domains/tools
jpmschweitzerandClaude 3e495daa73 fix(webber-api): clear mypy, and the dead code it was covering for
55 errors to zero. Nearly all of them traced back to two causes rather than 55.

THE DECORATOR. @logged wraps ~24 functions across this package and was declared
`def decorator(func: Callable):` with no ParamSpec and no return annotation, so
it erased the signature of everything it touched. ToolResult.execute() is
annotated `-> ToolResult`; through the decorator it came back Any, and mypy
reported 33 no-any-return errors spread across the tools and agents. Each looked
like a local annotation slip. All of them were one decorator. Typed with
ParamSpec/TypeVar; the async branch casts at the await rather than loosening R,
because loosening R would put the Any straight back into every caller.

THE MISSING TYPE PARAMETER. BaseAgent was not generic, so _create_agent returned
a bare Agent — Agent[Any, Any] — and pydantic_ai then typed every run() result
as Any. BaseAgent is now Generic[CtxT] bound to AgentContext, _agent is declared
on the base instead of reached through hasattr, and the three tool-registration
functions take their agent's real context type. tools_streaming.py already did
this; the other three had not been updated.

Eight `execute` overrides carry a targeted ignore rather than a package-wide
disable_error_code. Every tool narrows the base's **kwargs to its own named
parameters, which is a real LSP violation — but nothing anywhere is typed as
BaseTool, and every call site constructs the concrete tool. The abstract method
earns its place by making a tool without execute impossible to instantiate. The
reasoning lives in BaseTool.execute's docstring; the per-site suppressions mean
an override that IS unsound still gets caught.

BaseAgent.run_stream widened to AsyncIterator[str | StreamEvent], which is what
callers already receive: task streams structured events, explore and plan stream
strings, and the router branches on isinstance with a comment calling the string
path legacy. The annotation now says what the code does.

AND THE PART THAT MATTERS MORE THAN THE TYPES.

Chasing the last error found that the Ollama sanitiser has been broken. It
fetched the parent's chat getter with `AsyncOpenAI.chat.fget`, and openai made
`chat` a functools.cached_property, whose getter is `.func`. Touching `.chat`
raised AttributeError — meaning the content: null workaround that CLAUDE.md
documents as live would have failed on the first completion any agent attempted.
Confirmed in the running container (openai 2.46.0) as well as locally (2.15.0).

Two things hid it. The line carried a bare `# type: ignore`, which suppressed
precisely the complaint that would have caught it. And /agents/run and
/agents/stream have served zero requests in 30 days, so nothing exercised the
path. A mitigation can rot completely while every check stays green, if no check
actually runs it.

The lookup now reads whichever getter the descriptor exposes and raises a
legible TypeError if openai adopts a third shape. tests/test_ollama_provider.py
walks the chain an agent request walks, short of the network call —
mutation-checked: all four fail against the old lookup.

215 passed, 23 skipped, plus the four new. mypy clean over 90 files.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-11 16:27:51 +02:00
..

Tools Domain

This domain contains tool implementations for agent use.

Tool Categories

File Tools (file/)

Tools for reading, writing, and finding files.

Tool Purpose Key Parameters
Read Read file contents file_path, offset, limit
Write Create/overwrite files file_path, content
Edit Exact string replacement file_path, old_string, new_string, replace_all
Glob Find files by pattern pattern, path

Read Tool:

  • Returns line-numbered content (cat -n format)
  • Supports offset/limit for large files
  • Can read images, PDFs, Jupyter notebooks
  • Default: 2000 lines, 2000 chars per line

Edit Tool:

  • Performs exact string replacements
  • Fails if old_string is not unique (use replace_all or provide more context)
  • Preserves indentation from Read output

Glob Tool:

  • Supports patterns like **/*.py, src/**/*.ts
  • Returns files sorted by modification time
  • Use for finding files by name patterns

Shell Tools (shell/)

Tools for executing system commands.

Tool Purpose Key Parameters
Bash Execute shell commands command, timeout, description

Bash Tool:

  • Persistent shell session
  • 2-minute default timeout (max 10 minutes)
  • Supports background execution (run_in_background)
  • Quote paths with spaces: cd "/path with spaces"

Git Operations:

  • Never commit to main/master directly
  • Use conventional commits format
  • Never use -i flag (interactive)
  • Never skip hooks unless explicitly requested
  • Never force push to main/master

Security:

  • Sandboxed execution when SANDBOX_ENABLED=true
  • Validates against ALLOWED_PATHS
  • Timeout enforcement

Tools for searching content and the web.

Tool Purpose Key Parameters
Grep Search file contents pattern, path, glob, output_mode
WebSearch Search the web query, allowed_domains, blocked_domains
WebFetch Fetch and analyze URLs url, prompt

Grep Tool:

  • Built on ripgrep (NOT grep/rg bash commands)
  • Supports regex patterns
  • Output modes: files_with_matches (default), content, count
  • Context lines: -A, -B, -C

WebSearch Tool:

  • Returns search results with URLs
  • Always include sources in responses
  • Domain filtering supported

WebFetch Tool:

  • Fetches URL, converts HTML to markdown
  • Processes content with AI for extraction
  • 15-minute cache for repeated URLs
  • Handles redirects (returns redirect URL)

Structure

tools/
├── router.py            # Tool routes (list, execute)
├── controller.py        # Tool orchestration
├── schemas.py           # Tool request/response models
│
├── file/                # File operation tools
│   ├── __init__.py
│   ├── read.py          # Read file contents
│   ├── write.py         # Write file contents
│   ├── edit.py          # Edit file contents
│   ├── glob.py          # Find files by pattern
│   └── example-prompt.md
│
├── shell/               # Shell execution tools
│   ├── __init__.py
│   ├── bash.py          # Execute bash commands
│   └── example-prompt.md
│
└── search/              # Search tools
    ├── __init__.py
    ├── grep.py          # Search file contents
    ├── web.py           # Web search and fetch
    └── example-prompt.md

Tool Pattern

Tools are registered with PydanticAI agents via the @agent.tool decorator. Each tool should:

  1. Have clear input/output types
  2. Include a docstring (used by LLM)
  3. Handle errors gracefully
  4. Respect sandbox settings
from src.shared.config import get_settings
from src.shared.logging import logged

settings = get_settings()

@logged()
async def read_file(file_path: str, limit: int = 2000) -> str:
    """
    Read contents of a file.

    Args:
        file_path: Absolute path to the file
        limit: Maximum lines to read

    Returns:
        File contents as string with line numbers
    """
    # Check path is allowed
    if settings.sandbox_enabled:
        _validate_path(file_path, settings.allowed_paths)

    # Read and return with line numbers
    pass

Adding a New Tool

  1. Create a new file in appropriate category (file/, shell/, search/)
  2. Implement the tool function with proper types and docstring
  3. Add @logged() decorator for timing
  4. Handle sandbox restrictions
  5. Register with agents that need it
  6. Add tests

Reference Prompts

Each tool category directory contains an example-prompt.md file with reference prompts from the claude-code-system-prompts repository. These document the expected behavior and usage patterns for each tool.