Keep OpenAI tool search server-side alongside deferred capabilities and unify the `tool_availability_delta` profile field

Closed
DO
3mo ago
+11050-3079

Stacked on #6793 — retarget to main when that merges; only the last four commits are new.

When a run mixes searchable deferred tools with lazy-capability tools on an OpenAI Responses model with native tool search (gpt-5.4/5.5/5.6 + first-party provider), we currently force the search client-side (execution: 'client' dispatching into our local search_tools), because the capability-gated tools sit in tools with the same defer_loading flag the search corpus uses and server-side search would surface them before their capability loads.

That forcing is only necessary on providers whose reveal channel references pre-advertised tools. On first-party OpenAI Responses, a reveal is an additional_tools input item carrying the full definition — no pre-advertisement needed — so a capability-gated tool can stay off the wire entirely until its capability loads, and server-side search over the searchable corpus is safe: it cannot reveal what it never saw.

Live-verified (2026-08-03, gpt-5.6 + gpt-5.4-mini, with controls): server-executed tool_search coexists with an additional_tools item declaring a never-advertised tool (the model calls it; without the item it can't); server-side search still works over the corpus; and the item is cache-safe (warmed 2430-token prefix, 2427 read from cache after appending the item; mutating tools[] as control reads 0).

Changes

  • Unify the two delta profile flags (both introduced on #6793, unreleased, so no shim): anthropic_supports_tool_availability_delta and openai_responses_supports_tool_availability_delta become one generic field, tool_availability_delta: Literal['by_reference', 'with_definitions'] | None. 'by_reference' = Anthropic tool_addition (reveal references a pre-advertised deferred entry); 'with_definitions' = OpenAI Responses additional_tools (reveal carries the definitions; Kimi K3 will be a second 'with_definitions' renderer, #6977); None = project via prepare_messages. This is the axis the resolution logic needs, and it answers the flag-collapse question raised in #6977. Naming is open to bikeshedding.
  • Keep capability-gated tools off the wire when reveals carry definitions: Rule 3 in Model._resolve_native_tool_swap now treats a capability-gated deferred tool on a 'with_definitions' model like a tool the request can't hide in place — omitted while hidden, demoted to a plain visible definition once revealed, at which point the adapter carries it in the append-only additional_tools item and tools stays byte-identical.
  • Scope the client-side forcing to 'by_reference' models: _resolve_tool_search_native_for_capability_gated_tools no longer downgrades ToolSearchTool(strategy=None) to 'custom' when there is nothing on the wire to leak. Anthropic and third-party Responses (no delta channel) keep the existing forcing.

Mixed-corpus wire on gpt-5.6 before and after a capability load, asserted byte-identically: tool_search (server-executed), load_capability, searchable tools with defer_loading: true — the capability tool never appears in tools, and after the load an additional_tools item carries its full definition. A live two-interaction cassette puts the path under the autouse cache-prefix checker, which passes without exemption.

Follow-up: #7087 extends the same idea to Anthropic via lazy advertisement (deferred entries are outside Anthropic's cache key), which would drop the forcing there too.

Checklist

  • Any AI generated code has been reviewed line-by-line by the human PR author, who stands by it.
  • No breaking changes in accordance with the version policy.
  • PR title is fit for the release changelog.