Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Reset the generate transport's idle timeout on every received chunk, allowing active reasoning streams to exceed the timeout overall while still aborting stalled streams (#87).

- Batch consecutive tool-result images after all tool results on the generate transport, preventing interleaved user messages from breaking multi-tool turns with "Tool result is missing".

- Add display pricing for DeepSeek V4.1 Flash, Qwen 3.8 Max 0902, Gemini 3.8 Flash, Muse Spark 1.3 variants, LongCat 2.0 free, and Ling 3.0 Flash Sante free. Verify against the September 15 pricing page and live 69-model catalog; correct DeepSeek V4 Flash and Vision Exp off-peak prices to $0.15/$0.60 with $0.003 cache reads per million tokens.
Expand Down
8 changes: 8 additions & 0 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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 })
Expand Down
34 changes: 34 additions & 0 deletions tests/test-retry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, 200, 200, 200],
})
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })

const events = await collectEvents(
streamCommandCode(makeModel(), makeContext(), {
apiKey: TEST_API_KEY,
timeoutMs: 500,
}),
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", () => {
Expand Down
Loading