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.
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-anthropic0.18.5 does forward it (src/adapters/text.ts): a server tool yieldsTOOL_CALL_STARTwiththen
TOOL_CALL_ENDwith 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-openai0.22.5,@tanstack/openai-base0.10.10)OpenAIBaseResponsesTextAdapter.processStreamChunksmapsfunction_callitems only (src/adapters/responses-text.ts, theresponse.output_item.added/response.output_item.donebranches checkitem.type === 'reasoning'anditem.type === 'function_call'). Two event shapes fall through with no handler:response.output_item.{added,done}whereitem.type === 'web_search_call'— the item carriesaction.queriesand, when the request asks forinclude: ['web_search_call.action.sources'],action.sources: Array<{ type: 'url'; url: string }>.response.output_text.annotation.addedwhere the annotation is aurl_citation—{ url, title, start_index, end_index }.grep -n annotation src/adapters/responses-text.tsreturns 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
processStreamChunksand re-declaring two module-private types (StreamedFunctionCallMetadata,LegacyReasoningDeltaEvent) by shape, since neither is exported.Gemini (
@tanstack/ai-gemini0.29.1)google_searchis offered as a tool, butsrc/adapters/text.tsnever readscandidates[].groundingMetadata(groundingChunks,groundingSupports,webSearchQueries) — zero occurrences in the file. ItsprocessStreamChunksisprivate, 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? }>alongsideproviderExecuted: 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_callitem as aTOOL_CALL_START/TOOL_CALL_ENDpair and joining theurl_citationannotations (which carry the titles, and the positions) onto the sources by URL. Addingweb_search_call.action.sourcestoincludewhen 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.