diff --git a/src/web-search/progress-stream.ts b/src/web-search/progress-stream.ts index f51a55efc6..e3889eb5bc 100644 --- a/src/web-search/progress-stream.ts +++ b/src/web-search/progress-stream.ts @@ -303,6 +303,10 @@ export async function* parseStreamWithProgress( } if (event.type === "done" || event.type === "incomplete") { heldTerminal = event; + // Response-byte inactivity ends once the adapter has produced a terminal event. + // From here the separate post-terminal drain guard owns the bounded wait for + // iterator cleanup, so leaving the inactivity timer armed creates a false timeout. + clearInactivity(); continue; } await handoff.deliver(event); diff --git a/tests/web-search/web-search-progress-stream.test.ts b/tests/web-search/web-search-progress-stream.test.ts index ddb325b2fb..d36bb42594 100644 --- a/tests/web-search/web-search-progress-stream.test.ts +++ b/tests/web-search/web-search-progress-stream.test.ts @@ -215,10 +215,13 @@ describe("web-search streamed-body progress collector", () => { let returned = false; const parser: ParseStream = async function* () { yield { type: "done", usage: { inputTokens: 1, outputTokens: 2 } }; - await sleep(20); + await sleep(30); returned = true; }; - const iterator = parseStreamWithProgress(new Response(chunkStream([])), parser, { inactivityTimeoutMs: 100 }); + const iterator = parseStreamWithProgress(new Response(chunkStream([])), parser, { + inactivityTimeoutMs: 10, + postTerminalDrainTimeoutMs: 100, + }); const next = await iterator.next(); expect(returned).toBe(true); expect(next.value).toEqual({ type: "done", usage: { inputTokens: 1, outputTokens: 2 } });