diff --git a/src/server/responses/core.ts b/src/server/responses/core.ts index c1ce136ca4..be85a6ed1c 100644 --- a/src/server/responses/core.ts +++ b/src/server/responses/core.ts @@ -5834,12 +5834,22 @@ async function handleResponsesInner( isPassthrough: true, stream: parsed.stream === true, }); - // The bridge wraps the RAW upstream body, so terminal repair below still owns the single - // client-facing terminal — the bridge drops the terminal of every intercepted leg. - const upstreamSseBody = webSearchBridgePlan + // Repair must observe the raw first leg before the bridge suppresses an intercepted search + // lifecycle. Otherwise a provider that leaves that complete call open never arms repair's + // grace timer, so the bridge cannot execute the search or begin its continuation. + let passthroughSseBody = terminalRepairPolicy + ? relayResponsesSseWithTerminalRepair( + upstreamResponse.body, + upstream, + terminalRepairPolicy, + translatorBudget, + options.responsesTerminalRepairScheduler, + ) + : upstreamResponse.body; + passthroughSseBody = webSearchBridgePlan ? createPassthroughWebSearchBridgeStream({ plan: webSearchBridgePlan, - firstLeg: upstreamResponse.body, + firstLeg: passthroughSseBody, requestBody: request.body, // Continuation legs replay the same built request with the executed search appended. // The first leg already passed the recovery ladder, the outbound size ceiling, and the @@ -5866,16 +5876,7 @@ async function handleResponsesInner( }, signal: upstream.signal, }) - : upstreamResponse.body; - const passthroughSseBody = terminalRepairPolicy - ? relayResponsesSseWithTerminalRepair( - upstreamSseBody, - upstream, - terminalRepairPolicy, - translatorBudget, - options.responsesTerminalRepairScheduler, - ) - : upstreamSseBody; + : passthroughSseBody; const repairConfig = route.provider.responsesItemIdRepair; // Grok Build renders deltas live but reconstructs its durable assistant // turn from the completed response snapshot. Native Responses streams diff --git a/tests/responses/passthrough-abort.test.ts b/tests/responses/passthrough-abort.test.ts index 46100c6902..41c80d79be 100644 --- a/tests/responses/passthrough-abort.test.ts +++ b/tests/responses/passthrough-abort.test.ts @@ -59,8 +59,15 @@ describe("passthrough relayWithAbort (RC2, passthrough path)", () => { ); expect(sseBranch).toContain("const terminalRepairPolicy = providerModelResponsesTerminalRepair("); - expect(sseBranch).toContain("const passthroughSseBody = terminalRepairPolicy"); + expect(sseBranch).toContain("let passthroughSseBody = terminalRepairPolicy"); expect(sseBranch).toContain(": upstreamResponse.body;"); + // Repair has to wrap the raw first leg before the bridge hides its completed web-search call; + // otherwise a terminal-less open leg cannot trigger the repair timer and continuation stalls. + const terminalRepair = sseBranch.indexOf("relayResponsesSseWithTerminalRepair("); + const webSearchBridge = sseBranch.indexOf("createPassthroughWebSearchBridgeStream({"); + expect(terminalRepair).toBeGreaterThanOrEqual(0); + expect(webSearchBridge).toBeGreaterThan(terminalRepair); + expect(sseBranch.slice(webSearchBridge)).toContain("firstLeg: passthroughSseBody,"); expect(sseBranch).toContain("passthroughSseBody.tee()"); // Rewrite traffic is derived from the finalized block chain so every // provider-specific transform participates in the platform gate.