Skip to content

fix(web-search): preserve buffered upstream policy - #1155

Draft
myrosla wants to merge 2 commits into
lidge-jun:devfrom
myrosla:fix/1143-web-search-buffered-upstream
Draft

fix(web-search): preserve buffered upstream policy#1155
myrosla wants to merge 2 commits into
lidge-jun:devfrom
myrosla:fix/1143-web-search-buffered-upstream

Conversation

@myrosla

@myrosla myrosla commented Aug 6, 2026

Copy link
Copy Markdown

Summary

  • Preserve the routed model's effective upstream streaming setting inside the web-search loop.
  • Parse buffered upstream JSON with the adapter's parseResponse path while keeping the client response as Responses SSE.
  • Enforce the existing 32 MiB turn cap before a buffered adapter can materialize an oversized body.
  • Cover a real two-iteration loop: routed tool call, hosted sidecar search, then buffered final answer.
  • Keep architecture and localized sidecar documentation aligned.

Refs #1143

Scope note

Public dev currently has no OpenCode Go / GPT-5.6 Luna entry in modelResponsesUpstreamStreaming. Its only false entry is DeepSeek Flash on the native openai-responses passthrough path, which does not enter the web-search loop. This PR fixes and tests the loop-level transport contract, but intentionally does not add unverified Luna provider metadata or claim an end-to-end reproduction of that provider route.

Verification

  • bun test tests/web-search.test.ts tests/web-search-progress-stream.test.ts — 72 pass, 0 fail.
  • bun run typecheck — passed.
  • bun run test — passed on commit 307045c5.
  • bun run privacy:scan — passed.
  • cd docs-site && bun run build — 221 pages built.
  • git diff --check — passed.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Summary by CodeRabbit

  • Bug Fixes

    • Routed web-search requests now consistently follow each route’s streaming policy.
    • Compatibility routes use bounded non-streaming processing, while streaming routes continue to stream.
    • Oversized upstream responses are rejected safely, with cancellation to prevent excess buffering.
    • Client responses remain available in Responses SSE format, without exposing intermediate search output.
  • Documentation

    • Updated sidecar and transport guides across supported languages to describe streaming behavior, buffering, response limits, and error handling.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Routed web-search iterations now follow the effective upstream streaming policy. Streaming routes use parseStream; compatibility routes use bounded parseResponse. Raw-body limits, cancellation, internal buffering, and client-facing Responses SSE remain supported.

Changes

Routed web-search transport policy

Layer / File(s) Summary
Propagate routed streaming policy
src/server/responses/core.ts:2467, src/web-search/loop.ts:247-248, 290-299, 369, 415
The Responses bridge passes parsed.stream as upstreamStreaming. The loop uses this value when constructing and dispatching provider requests.
Select response parser and enforce limits
src/web-search/loop.ts:543-559, 574-577, src/web-search/progress-stream.ts:33-34, 147-149, 159, 233-237
Streaming routes use parseStream. Buffered routes use bounded parseResponse. Raw response chunks are counted against maxBodyBytes; excess input raises TranslatorBudgetExceededError and stops forwarding data.
Validate and document routed behavior
tests/web-search.test.ts:560-655, tests/web-search-progress-stream.test.ts:9, 125-146, structure/04_transports-and-sidecars.md:231-235, docs-site/src/content/docs/guides/sidecars.md:32-37, docs-site/src/content/docs/{ja,ko,ru,zh-cn}/guides/sidecars.md
Tests cover buffered routed requests, Responses SSE output, raw-body limits, and stream cancellation. English, Japanese, Korean, Russian, and Simplified Chinese documentation describes the route-specific parsing behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

Possibly related PRs

  • lidge-jun/opencodex#302 — Modifies the same web-search loop and progress-stream handling for streaming and terminal-event behavior.
  • lidge-jun/opencodex#1006 — Extends bounded non-streaming upstream handling in the Responses bridge and routed streaming policy.

Suggested labels: bug

Suggested reviewers: lidge-jun, mushikingh, wibias

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: preserving the buffered upstream streaming policy in the web-search flow.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@myrosla
myrosla marked this pull request as ready for review August 6, 2026 21:40

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs-site/src/content/docs/guides/sidecars.md`:
- Around line 32-37: Correct the routed-loop buffering description so it says
events are buffered only as needed to decide whether to search or finalize,
while explicitly phased commentary may be forwarded live. Apply this behavior
correction in docs-site/src/content/docs/guides/sidecars.md lines 32-37 and the
corresponding translated sections in
docs-site/src/content/docs/ja/guides/sidecars.md lines 33-37,
docs-site/src/content/docs/ko/guides/sidecars.md lines 33-37,
docs-site/src/content/docs/ru/guides/sidecars.md lines 37-43, and
docs-site/src/content/docs/zh-cn/guides/sidecars.md lines 30-34, preserving
equivalent meaning in each locale.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6b900e54-0714-4ae8-aca2-d56983dc63ca

📥 Commits

Reviewing files that changed from the base of the PR and between b39eecf and 307045c.

📒 Files selected for processing (11)
  • docs-site/src/content/docs/guides/sidecars.md
  • docs-site/src/content/docs/ja/guides/sidecars.md
  • docs-site/src/content/docs/ko/guides/sidecars.md
  • docs-site/src/content/docs/ru/guides/sidecars.md
  • docs-site/src/content/docs/zh-cn/guides/sidecars.md
  • src/server/responses/core.ts
  • src/web-search/loop.ts
  • src/web-search/progress-stream.ts
  • structure/04_transports-and-sidecars.md
  • tests/web-search-progress-stream.test.ts
  • tests/web-search.test.ts

Comment on lines +32 to +37
Every routed-model iteration follows the route's effective upstream streaming policy. Streaming
routes use the streaming parser; compatibility routes use the bounded non-streaming parser while
the client-facing response stays Responses SSE. opencodex fully buffers semantic events internally
before deciding whether to search or return the final answer. Only the first iteration's final
headers/status and 429 key rotations are acquired eagerly. Thus synthetic search calls and
preliminary output are never exposed as client-visible model output.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the absolute buffering claim.

structure/04_transports-and-sidecars.md states that explicitly phased commentary text can forward live. The sidecar pages state that all semantic events are buffered and that preliminary output is never exposed. This contradicts the documented routed-loop behavior.

  • docs-site/src/content/docs/guides/sidecars.md#L32-L37: State that the loop buffers events needed to decide whether to search or finalize. State that explicitly phased commentary can forward live.
  • docs-site/src/content/docs/ja/guides/sidecars.md#L33-L37: Apply the same behavior correction in Japanese.
  • docs-site/src/content/docs/ko/guides/sidecars.md#L33-L37: Apply the same behavior correction in Korean.
  • docs-site/src/content/docs/ru/guides/sidecars.md#L37-L43: Apply the same behavior correction in Russian.
  • docs-site/src/content/docs/zh-cn/guides/sidecars.md#L30-L34: Apply the same behavior correction in Simplified Chinese.

As per path instructions, user-facing docs must stay in sync with actual CLI/API behavior and translated locale pages must not contradict the English source.

📍 Affects 5 files
  • docs-site/src/content/docs/guides/sidecars.md#L32-L37 (this comment)
  • docs-site/src/content/docs/ja/guides/sidecars.md#L33-L37
  • docs-site/src/content/docs/ko/guides/sidecars.md#L33-L37
  • docs-site/src/content/docs/ru/guides/sidecars.md#L37-L43
  • docs-site/src/content/docs/zh-cn/guides/sidecars.md#L30-L34
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs-site/src/content/docs/guides/sidecars.md` around lines 32 - 37, Correct
the routed-loop buffering description so it says events are buffered only as
needed to decide whether to search or finalize, while explicitly phased
commentary may be forwarded live. Apply this behavior correction in
docs-site/src/content/docs/guides/sidecars.md lines 32-37 and the corresponding
translated sections in docs-site/src/content/docs/ja/guides/sidecars.md lines
33-37, docs-site/src/content/docs/ko/guides/sidecars.md lines 33-37,
docs-site/src/content/docs/ru/guides/sidecars.md lines 37-43, and
docs-site/src/content/docs/zh-cn/guides/sidecars.md lines 30-34, preserving
equivalent meaning in each locale.

Source: Path instructions

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 307045c555

ℹ️ 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".

Comment thread src/web-search/loop.ts
throw new LoopError(502, `Provider adapter ${prepared.responseAdapter.name} does not support buffered responses`);
}
parse = async function* (response, budget) {
for (const event of await parseResponse(response, budget)) yield event;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve tool calls when parsing buffered Responses turns

When upstreamStreaming is false for an openai-responses route—the adapter family that core.ts can force into buffered mode—this invokes openai-responses.parseResponse, but that parser is compaction-specific: it extracts only summary text and returns upstream compaction returned no summary text for a function-call-only response. As a result, a buffered model request for the synthetic web_search tool never reaches scanEventsForWebSearch, so the hosted sidecar is not dispatched. The added integration test uses openai-chat, masking this incompatibility; use a buffered Responses parser that emits function/tool-call events before routing its output into the loop.

AGENTS.md reference: src/AGENTS.md:L19-L19

Useful? React with 👍 / 👎.

Comment thread src/web-search/loop.ts
throw new LoopError(502, `Provider adapter ${prepared.responseAdapter.name} does not support buffered responses`);
}
parse = async function* (response, budget) {
for (const event of await parseResponse(response, budget)) yield event;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Release retained events after intercepted buffered iterations

When a buffered adapter such as openai-chat, Anthropic, or Google returns a web_search call, its parseResponse charges the resulting batch through retainTranslatedEventBatch. The loop then discards that iteration's original events after copying the query and thinking, while only events eventually consumed by bridgeToResponsesSSE have their leases released. Consequently every intercepted buffered iteration remains charged for the rest of the turn, and a later response can spuriously hit translation_buffer_limit when their combined size exceeds 32 MiB (or a smaller configured test budget), even though the earlier batch is no longer retained. Release each discarded source event after the loop has copied the data it needs.

Useful? React with 👍 / 👎.

@Wibias
Wibias marked this pull request as draft August 7, 2026 07:19
@lidge-jun

Copy link
Copy Markdown
Owner

Correcting an assessment I made and got wrong.

I had this queued to close as unreachable: no production registry entry opts into modelResponsesUpstreamStreaming, and the DeepSeek force was retired in 0b8e608c0. That part is accurate but it is not the whole change.

The hook that matters is upstreamStreaming: parsed.stream — the caller decides, not the registry. stream is optional in the public Responses schema (src/responses/schema.ts:133-144), and planWebSearch() imposes no streaming precondition (src/web-search/index.ts:148-150). So a routed /v1/responses request using web_search with stream omitted or false lands exactly on the buffered branch this PR preserves. Your own test invokes it deliberately.

Withdrawing the unreachable rationale. The path is reachable and worth keeping.

It is not merge-ready as it stands, though. The buffered openai-responses path calls a compaction-only parser that errors on a tool-call-only response (src/adapters/openai-responses.ts:1271-1293), which matches the unresolved automated review feedback. Could you address that case and the retained-event accounting, or split them out? Happy to take it once that is handled.

@lidge-jun

Copy link
Copy Markdown
Owner

Correcting my own correction — one detail above is wrong.

I wrote that a request with stream omitted reaches the buffered branch. It does not. Your runWithWebSearch does const upstreamStreaming = deps.upstreamStreaming ?? true, so an absent stream resolves to streaming. Only an explicit stream: false reaches the buffered path.

The substance of the withdrawal stands: the branch is reachable from a public request, so closing this as an unreachable path would have been wrong. It is narrower than I stated — explicit stream: false with web_search, not merely an omitted field.

Sorry for the noise. The merge-readiness point is unchanged: the buffered openai-responses path still needs the tool-call-only response case handled.

iF2007 pushed a commit to iF2007/opencodex that referenced this pull request Aug 8, 2026
Records a campaign that gave a terminal disposition to every open bug
issue and bug PR at the 2026-08-08 cutoff, and what it got wrong along
the way.

The unit carries the inventory and disposition matrix with file:line
evidence per verdict, the rebase-and-co-author republish protocol, and
per-work-phase implementation docs. Execution records cover the CI
approval unblock, the first CI results, and the merges and closes.

What the audits overturned is the more useful half. Two issues were
queued for closure as resolved and were not - lidge-jun#1176 carried a maintainer
comment from the same morning asking for a v2.11.0 retest, and lidge-jun#1024
rested on an upstream attribution the plan itself proposed to test and
had not tested. lidge-jun#1155 was queued as an unreachable path and is reachable.
lidge-jun#1263 was diagnosed as having no defect and has a real TOCTOU race, shown
by contrast experiment. lidge-jun#1119 was described as fully absorbed and was
not, so its coverage was recreated on dev before it was closed.

Two execution faults are recorded as faults: lidge-jun#1202 was merged without
checking its exact-head CI had concluded success, violating a reading
rule written earlier in this same unit; and a public comment to a
contributor made a false claim about stream defaulting, corrected on the
PR. A transient six-failure test run is recorded as unexplained rather
than dismissed as flaky, since its log was overwritten before the names
could be preserved.

Nothing in the build, typecheck, or test path reads from devlog/.
privacy:scan passes; repo-hygiene 11 pass / 0 fail.
@lidge-jun

Copy link
Copy Markdown
Owner

Status update, since this one has been sitting without any signal at all.

No CI has ever run on this PR. Not failed, not cancelled — zero workflow runs against 307045c55, and zero for the branch in this repository's Actions history. The only status on the head commit is CodeRabbit. I swept every open PR today and approved the pending workflow runs on all of them; this is the one where there was nothing to approve.

The likely cause is that this comes from a fork (myrosla/opencodex) and the first-time-contributor approval gate never produced a queued run I can release from the API side. I could not find one to approve, so I am not able to unblock it from here the way I did for the others.

Two things that would move it:

  1. Pushing any commit to the branch usually re-triggers the workflows and creates a run that can be approved — even an empty commit or a rebase onto current dev would do it. The branch is 429 commits behind, so a rebase is worth doing regardless.
  2. If a run does appear and sits at action_required, tell me and I will approve it against the exact head SHA.

Separately, my two comments above stand as the substantive review: the first retracted a wrong close-as-unreachable assessment, and the second corrected an error in that retraction about which branch a stream-omitted request reaches. The patch itself is not blocked on my analysis — it is blocked on never having been tested by CI.

No action needed from you if you would rather leave it; say so and I will close it as stale with the record intact. But I would rather see it rebased and actually run.

lidge-jun added a commit that referenced this pull request Aug 8, 2026
028 WP6 — the two bug issues that had no fix PR. #1297 shipped; #1296
diagnosed and deliberately left open.

The record worth keeping is the pattern review kept catching: four rounds,
and each time I had read a name or a comment as evidence of behaviour. I
used promptCacheKey as a conversation identity because the name sounded
right, when it is unvalidated client input the repo already warns against.
I then found vertexReplaySessionId, whose comment claims a stable thread key
while the code reads promptCacheKey, and mistook a second instance of the
reported bug for the fixed version. I confirmed #1296 by reading three lines
that pass err.message into a 401 without checking whether anything can reach
them carrying an ACL error — nothing can. And I called codex-thread:
prefixing a namespace that cannot collide, then reproduced the collision
myself.

029 WP17 — the goalplan read complete while nine pre-cutoff PRs had never
had CI executed. Approved 37 pending runs SHA-matched and drove all nine to
a recorded outcome: eight green, and #1155 documented as having no run
object to approve.

Two corrections folded there too. An approval starts CI and is not a
disposition, so the completion gate became an outcome per item rather than
an unblock per item. And my claim that a cutoff campaign cannot close
against its starting snapshot contradicts 000_plan.md, which freezes the
inventory at the cutoff — complete means every item open then has a final
disposition, not that the repository has no open bug work now.
@Wibias Wibias added the stale No activity on a needs-info issue; will close soon unless updated label Aug 8, 2026
@github-actions github-actions Bot added the bug Something isn't working label Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working stale No activity on a needs-info issue; will close soon unless updated

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants