Skip to content

feat: tag web search sources and keep OpenAI search actions - #435

Open
mafredri wants to merge 4 commits into
charmbracelet:mainfrom
coder:mafredri/upstream-web-search-sources
Open

mafredri wants to merge 4 commits into
charmbracelet:mainfrom
coder:mafredri/upstream-web-search-sources

Conversation

@mafredri

Copy link
Copy Markdown

A source part is a citation for OpenAI (url_citation) and a found page for Anthropic (web_search_tool_result), with nothing to tell the two apart. OpenAI Responses also never requests the pages a search found, and drops the search queries, the item status, and the URL and pattern of open_page and find_in_page.

Change

  • SourceContent.ToolCallID (StreamPart.SourceToolCallID when streaming) is set on sources a provider-executed tool call returned. Citations leave it empty.
  • Anthropic sets it on search results.
  • OpenAI Responses:
    • adds the web_search_call.action.sources include when a web search tool is set;
    • emits a search's found pages as sources when its item finishes;
    • sets the tool call input from the action, for example {"type":"search","queries":[…]};
    • keeps Status, Queries, URL and Pattern in WebSearchCallMetadata.

OpenAI consumers now receive found pages as sources, as Anthropic consumers already do.

Found pages come from the streamed output_item.done, not response.completed: in live gpt-4.1-mini and gpt-5-mini streams, the final summary also listed the pages the answer cited among each search's sources. Generate only has that final output, so there it can report cited pages as found pages.

Replay is unchanged.

Tests

Unit tests cover each action shape in Generate and Stream, cited pages staying untagged, a failed search, include deduplication, Anthropic tagging, and the SourceContent JSON round trip. The TestOpenAIWebSearch cassettes are re-recorded because the request now carries the include.

🤖 This PR was created with the help of Coder Agents.

A source part meant a citation for some providers and a page a provider-
executed search found for others, with nothing to tell them apart.
SourceContent.ToolCallID (StreamPart.SourceToolCallID when streaming)
names the tool call that returned the source and stays empty for
citations. The agent copies it from stream parts, and the custom JSON
marshalers keep it so a found page does not read back as a citation.
Sources from a web_search_tool_result are pages the search found, not
citations, so they carry the search's tool_use_id in Generate and
Stream.
The Responses API only returns the pages a web search found when the
request includes web_search_call.action.sources, so requests with a web
search tool now add it once. When a web_search_call item closes, its
found pages are emitted as sources tagged with the item ID, before the
tool result. The tool call input is built from the action: the queries
of a search (or the deprecated query), the URL of open_page, or the URL
and pattern of find_in_page. WebSearchCallMetadata keeps the item status
and those action fields.

Streaming reads found pages from the item, not response.completed,
because the final summary also lists pages the answer cited. Generate
only has the final output, so cited pages can appear as found pages
there.

The TestOpenAIWebSearch cassettes were re-recorded because the request
body now carries the include.
Keep each explanation in one place and match the surrounding comment
style. Restore upstream comments that still hold.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant