git:20261011.d5e0a0a to git:20261108.6c94ecc
3 added, 2 removed. Audit A to A.
# Tavily tools
The client helper selects credentials by tool name: search defaults to
- `web_search`, fetch passes `web_fetch`, and an omitted key uses the SDK's
- `TAVILY_API_KEY` fallback. Keep credential regressions in `backend/tests/test_tavily_tools.py`
+ `web_search`, fetch passes `web_fetch`, and an omitted key falls back to
+ `TAVILY_API_KEY`. Both go through `provider_credentials.resolve_api_key`; if neither
+ resolves, the helper passes `""` so the SDK never re-reads the raw variable. Keep credential regressions in `backend/tests/test_tavily_tools.py`
on the real helper and SDK constructor, mocking only search/extract calls.
Each tool owns its per-call async client and awaits `close()` in `finally`,
including request failures and cancellation. Lifecycle regressions exercise
the real SDK connection pool without network requests.
Search forwards `include_domains` and `exclude_domains` only when present in
the `web_search` tool config's `model_extra`, alongside `max_results` and optional
`time_range`. Preserve explicit empty lists (no restriction of that kind) and
omit absent keys. These are deployment-only search-source settings; keep the
model-visible signature limited to `query` and `time_range`. Offline regressions
in `backend/tests/test_tavily_tools.py` pin SDK arguments and the tool schema.
For non-empty `include_domains`, explicitly send `include_domains_mode="filter"`
through the SDK's keyword arguments; inclusion must restrict sources rather than
boost them. Omit the mode when the include list is absent or empty.
Validate the HTTP-success payload before normalizing it. A top-level that is not an
object, a `results`/`failed_results` container that is not a list, or a non-empty
list whose entries hold no objects is a format error, not a legitimate empty result:
search returns the structured `{"error": ..., "query": ...}` response and fetch
returns the `Error: ...` string. Non-object entries are skipped while valid objects
are preserved in order, and missing or null search leaf fields (`title`, `url`,
`content`) normalize to empty text. A missing, null, or empty `results` keeps
search's `[]` and fetch's "Error: No results found"; a failed extraction whose object has no `error`
message reports "Error: Extraction failed". `web_fetch` coerces non-string
`raw_content` to text before truncating to 4096 characters. Log the malformed
container's name and type, or that no usable result objects remain, before
returning — never the query, URL, credentials, or payload values.
`backend/tests/test_tavily_response_shapes.py` drives the real tools and the real
AsyncTavilyClient through an offline HTTPX MockTransport; non-object top-level
payloads stub the client because the SDK's public wrappers reject that shape before
the tools see it. Cover null search fields individually and together, and mixed
fetch lists so the first retained object supplies the result or extraction error.