Skip to content

Provider-executed web search results are dropped on OpenAI and Gemini (forwarded on Anthropic) #1328

Description

@AlexDemzz

Summary

A provider-executed web search reaches the consumer as a tool call on Anthropic only. On OpenAI and Gemini the adapters drop the search entirely, so an app cannot show what the model searched for, nor the pages it read.

@tanstack/ai-anthropic 0.18.5 does forward it (src/adapters/text.ts): a server tool yields TOOL_CALL_START with

metadata: {
  providerExecuted: true,
  anthropic: { serverToolType, resultBlockType, result }, // result = the raw web_search_result blocks: url, title, page_age
}

then TOOL_CALL_END with the parsed input. That is everything a UI needs. The other two adapters have the same data on the wire and never emit it.

OpenAI (@tanstack/ai-openai 0.22.5, @tanstack/openai-base 0.10.10)

OpenAIBaseResponsesTextAdapter.processStreamChunks maps function_call items only (src/adapters/responses-text.ts, the response.output_item.added / response.output_item.done branches check item.type === 'reasoning' and item.type === 'function_call'). Two event shapes fall through with no handler:

  • response.output_item.{added,done} where item.type === 'web_search_call' — the item carries action.queries and, when the request asks for include: ['web_search_call.action.sources'], action.sources: Array<{ type: 'url'; url: string }>.
  • response.output_text.annotation.added where the annotation is a url_citation — { url, title, start_index, end_index }. grep -n annotation src/adapters/responses-text.ts returns nothing.

So the search is invisible, and the citations (including the character positions of each citation in the answer) are lost.

A subclass can recover it today, but only by overriding processStreamChunks and re-declaring two module-private types (StreamedFunctionCallMetadata, LegacyReasoningDeltaEvent) by shape, since neither is exported.

Gemini (@tanstack/ai-gemini 0.29.1)

google_search is offered as a tool, but src/adapters/text.ts never reads candidates[].groundingMetadata (groundingChunks, groundingSupports, webSearchQueries) — zero occurrences in the file. Its processStreamChunks is private, so unlike OpenAI there is no subclass workaround at all.

Request

Forward provider-executed search results as tool-call metadata on OpenAI and Gemini, the way the Anthropic adapter already does. A provider-neutral key (for example metadata.sources: Array<{ url, title?, pageAge? }> alongside providerExecuted: true) would let a consumer render all three the same way; the raw provider blocks could stay beside it under the provider's own key.

On OpenAI that means emitting the web_search_call item as a TOOL_CALL_START/TOOL_CALL_END pair and joining the url_citation annotations (which carry the titles, and the positions) onto the sources by URL. Adding web_search_call.action.sources to include when a web search tool is present would make the sources available without the caller having to know about the flag.

Closest existing issue I found: #495 (user-executed OpenAI provider tools being dropped) — related in spirit, different path.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

has-prAn open PR references this issuewaiting-on: maintainerThe ball is in the maintainers’ court

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions