From 422ee022bb04395de36903165364e54551da4eee Mon Sep 17 00:00:00 2001 From: zhangyiming Date: Wed, 9 Sep 2026 14:20:29 +0800 Subject: [PATCH] fix(core): reset attempt timeout on received chunks (#87) --- CHANGELOG.md | 2 ++ src/core.ts | 8 ++++++++ tests/test-retry.ts | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 879148f..395b563 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Treat `timeoutMs` as an idle gap timeout between received chunks rather than a total wall-clock limit on the entire stream, so long reasoning streams that actively yield tokens are not aborted after 300 seconds ([#87](https://github.com/patlux/pi-commandcode-provider/issues/87)). + ## 0.6.4 - 2026-09-03 - Refresh the generated Command Code capability catalog from `command-code@1.40.1` to `command-code@1.44.0`, adding current image-input, reasoning, effort, and output-limit metadata for newly published models. diff --git a/src/core.ts b/src/core.ts index 0d16e70..cc6fe04 100644 --- a/src/core.ts +++ b/src/core.ts @@ -708,6 +708,7 @@ export function createStreamCommandCode(deps: CoreDependencies) { if (controller.signal.aborted) throw abortError("Aborted") const { done, value } = await raceAbort(reader.read(), attemptController.signal) if (done) { + clearAttemptTimeout() if (buffer.trim()) handleEvent(parseStreamEventLine(buffer)) if (!finished) { throw new Error( @@ -716,6 +717,13 @@ export function createStreamCommandCode(deps: CoreDependencies) { } break } + if (timeoutMs !== undefined) { + clearAttemptTimeout() + attemptTimeoutId = setTimeout(() => { + attemptTimedOut = true + attemptController.abort() + }, timeoutMs) + } if (controller.signal.aborted) throw abortError("Aborted") buffer += decoder.decode(value, { stream: true }) diff --git a/tests/test-retry.ts b/tests/test-retry.ts index e0366de..ff7e6ae 100644 --- a/tests/test-retry.ts +++ b/tests/test-retry.ts @@ -322,6 +322,40 @@ describe("streamCommandCode — timeout", () => { if (error?.type !== "error") throw new Error("expected error") assert.match(error.error.errorMessage ?? "", /timed out after 50ms/) }) + + // https://github.com/patlux/pi-commandcode-provider/issues/87 + it("does not abort actively streaming responses that exceed timeoutMs overall", async () => { + server.mockResponse({ + type: "success", + events: [ + JSON.stringify({ type: "text-delta", text: "chunk 1 " }), + JSON.stringify({ type: "text-delta", text: "chunk 2 " }), + JSON.stringify({ type: "text-delta", text: "chunk 3 " }), + JSON.stringify({ type: "finish", finishReason: "stop" }), + ], + delays: [0, 30, 30, 30], + }) + const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() }) + + const events = await collectEvents( + streamCommandCode(makeModel(), makeContext(), { + apiKey: TEST_API_KEY, + timeoutMs: 50, + }), + 5_000, + ) + + assert.equal(server.requestCount(), 1) + assert.deepEqual(eventTypes(events), [ + "start", + "text_start", + "text_delta", + "text_delta", + "text_delta", + "text_end", + "done", + ]) + }) }) describe("streamCommandCode — abort cancels retry loop", () => {