Repository navigation
fix(responses): handle replayed hosted web-search history (#7250, #7269) - #7353
Conversation
…declared (#7250) Native Sol answers a replayed web_search_call without a matching web_search declaration with a generic server_error. Project unreferenced cells into the existing bounded reference notes whenever the final body declares no hosted search, on every Responses destination; declared requests keep structural history. Also translates the comments carried over from #7268 into English.
Codex replays a hosted web_search_call cell without its result text, and the parser dropped it for routed providers, so a model whose turn ended on a client tool call forgot it had searched and repeated the search and the tool. Rebuild the cell as a web_search call/result pair when search is declared, and let the sidecar loops record the result under the emitted cell id so the next turn restores it, scoped to caller and conversation.
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
✅ Deterministic PR hygiene checks passed. |
…p them without a sidecar Review round 1: the parse-time pair could call an undeclared web_search tool when no sidecar plan resolves, split a parallel client batch, and collide with client call ids. Rewrite cells into function_call/output items before the item loop, defer the output past the following call batch, allocate collision-free call ids mapped back to cell ids, and remove the pairs when the search plan is undefined.
Review round 2: a deferred search output crossed into the following assistant turn when consecutive client batches followed the cell, and a restored failed search lost its error flag. Flush the deferred output when a call follows the batch outputs, and set the flag independently of the restored text.
…ory-and-remove-free
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1f0370d05d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| export function rewriteReplayedWebSearchCells(input: readonly unknown[]): RewrittenWebSearchInput | undefined { | ||
| if (!input.some(item => isObj(item) && item.type === "web_search_call")) return undefined; | ||
| // A synthetic call id must not collide with any client call id, nor with another cell's. | ||
| const taken = new Set(input.flatMap(item => isObj(item) && typeof item.call_id === "string" ? [item.call_id] : [])); |
There was a problem hiding this comment.
Reserve fallback IDs used by local-shell calls
When a replayed cell's id matches a local_shell_call.id or tool_search_call.id on an item without call_id, this set misses the collision even though parseRequest treats those IDs as call.call_id ?? call.id. The rewrite then emits two tool calls with the same effective ID, so the subsequent output can attach to the synthetic web_search call instead of the original tool and corrupt the routed history. Reserve the parser's effective fallback IDs for these call types as well.
Useful? React with 👍 / 👎.
Summary
This fixes two related ways that replayed hosted web-search history goes wrong. It absorbs and supersedes community PR #7268.
Native Sol 500 on search history without a search tool (#7250). If an earlier turn produced a
web_search_callitem (for example on Gemini) and the conversation switches togpt-6.1-solwith a request that declares noweb_searchtool, the ChatGPT backend answers with a generic SSEserver_error. Codex retries that request until it gives up. Before this change, the passthrough adapter forwarded those cells unchanged. Now the final Responses serializer converts every unreferencedweb_search_callinto the existing bounded, untrusted assistant reference note whenever the final outbound body declares no hosted search tool. That coverstools, Liteadditional_tools, and the case where the destination's unsupported hosted tools were stripped. This applies on every Responses destination and on both HTTP and WS, because WS sends the same body. Requests that do declare search keep structural history. A function tool namedweb_searchdoes not count as a declaration. No search tool is added, no search is rerun, and no result is invented. Cells referenced by a same-inputitem_referencestay structural. The exact synthetic request from the issue is now a regression test on the canonical Codex endpoint, the public API, and a third-party gateway, with a declared-tool control for each.#7268 by @EricFeng9 is cherry-picked with authorship kept. It removes the
_compactionRequestexclusion so the existingprojectResponsesLiteHostedSearchHistoryopt-in also covers native v2compaction_triggerrequests. Because native v2 compaction carries an empty tool list, the new missing-declaration rule now projects that history even without the opt-in. The opt-in still decides what happens when search is declared. A follow-up commit translates that PR's source and test comments into English.Search results never reach routed models such as
anthropic/claude-opus-5-5(#7269). The sidecar search loop gives the model its results inside the turn. When the model then calls a client tool (shell), the turn ends. Codex replays only the hosted cell, which carries the query and no result text, andsrc/responses/parser.tsdropped that cell for routed providers. On the next request the model had no record that it had searched, so it searched again and reran the tool. That matches the report's 18 searches and 18 identical shell commands, and the resumed turn saying "I never called the search tool". The fix has two parts:web_searchfunction_callthe routed model made, plus itsfunction_call_output. The output is deferred past the following run of call/output items, so a cell inside a parallel client batch never splits it, and reasoning attaches the same way as for any other call. Synthetic call IDs avoid every clientcall_idand each other, and they map back to the cell ID. The fallback output names the queries, states that the result text is not available, and lists up to 20 untrusted titles and URLs. Without a declaration the cell stays out of routed history, as before.executeResponsesSidecarsandexecuteResponsesRunTurnremove the pairs again, because the synthetic tool would be undeclared on the wire.loop.ts,run-turn-loop.ts) now name the cell they emit. Both bridges honour a well-formedws_itemIdonweb_search_call_begin/_end. The loops record the result text insrc/web-search/sidecar-search-replay.ts, which reuses the bounded, in-memory, never-logged store from [Provider compatibility] Bridged hosted web_search result is not replayed to the destination on the next turn #4587. Before the first iteration of the next turn, the recorded result is swapped into the rebuilt pair. The memo is keyed by caller principal and conversation only, because the proxy ran the search with its own backend, so the result survives a model switch within one conversation. It fails closed when either key is missing.Closes #7250
Closes #7269
Supersedes #7268
Co-authored-by: Junming Feng 12311031@mail.sustech.edu.cn
Decisions (HOTL)
structure/decisions/ADR-7250-undeclared-hosted-search-history.md. This narrows the earlier ADR-6930 position that an absent declaration should not trigger projection: here the trigger is a request-shape fact, and Lite intent is not inferred.Verification
bun run typecheck: passed.bun test tests/adapters/openai/openai-native-hosted-search-history.test.ts tests/responses/responses-native-compact-search-history.test.ts tests/responses/responses-compaction-override.test.ts tests/config/config-hosted-search-projection.test.ts: 152 pass, 0 fail.bun test tests/web-search/web-search-sidecar-replay.test.ts(new): 12 pass. It covers result recovery through a realrunWithWebSearchtwo-turn flow, isolation by conversation and caller, keyless fallback, the fallback text, failed cells, no declaration, parallel-batch placement, reasoning grouping, collision-free IDs, and removal without a plan. One test runshandleResponsesagainst an Anthropic provider with the sidecar off and asserts that noweb_searchtool_usereaches the wire; it fails when the removal is disabled.bun test tests/responses/openai-responses-passthrough.test.ts tests/web-search/web-search-bridge-replay.test.ts tests/server/server-key-failover-e2e.test.ts: 260 pass. The fixtures that replay a cell and expect structural output now declareweb_search, as Codex does on every turn. The passthrough test file stays at its line cap.bun teston every file referencingweb_search_call, plustests/web-search tests/responses tests/bridge: the only failures were compaction and retention groups that pass when each file runs alone (reasoning-retention-v2,responses-compaction-override,reasoning-retention-routing,anthropic-model-weekly-admission). They come from running whole directories in one process.bun run test:changed: 43,615 pass, 6 fail. All six are client-home and link-runtime ownership tests (tests/cli/sibling-home-client-sync.test.ts,tests/clients/client-link-runtime.test.ts) that pass when run alone (40/40); they are timing flakes on a loaded machine.bun run structure:checkandbun run privacy:scan: passed.bun run testwas not run locally because several agent worktrees share this machine; CI covers the full suite. No live upstream requests were made.Checklist