diff --git a/CHANGELOG.md b/CHANGELOG.md index a489109..3be492a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ See [ARCHITECTURE.md](ARCHITECTURE.md) for current direction and ## Unreleased +- **Stop cutting Claude responses at three minutes.** The gateway no longer applies + an implicit 180-second deadline to Claude passthrough, matching the OpenAI and Zen + routes. Long streamed Claude turns, including server-side advisor calls, previously + ended mid-response ("Server error mid-response"; `claude-multi -p` reported the + truncated text as success). Client disconnects and explicit gateway timeouts still + abort upstream requests. + ## 0.2.1 — 2026-09-22 - Explain missing native worker spawn acknowledgements with the unsupported diff --git a/plugins/multi-core/src/gateway/server.ts b/plugins/multi-core/src/gateway/server.ts index ac2ea5f..a202ecd 100644 --- a/plugins/multi-core/src/gateway/server.ts +++ b/plugins/multi-core/src/gateway/server.ts @@ -887,13 +887,12 @@ function providerSignal(disconnected: AbortSignal, model: string | null, timeout ) { return disconnected; } - if ( - (model?.startsWith('multi/openai/') || model?.startsWith('multi/zen/')) && - timeoutMs === undefined - ) { + // Streamed OpenAI, Zen and Claude responses (including server-side advisor calls) + // can outlive any fixed wall-clock deadline; only an explicit limit bounds them. + if (timeoutMs === undefined) { return disconnected; } - return AbortSignal.any([disconnected, AbortSignal.timeout(timeoutMs ?? 180000)]); + return AbortSignal.any([disconnected, AbortSignal.timeout(timeoutMs)]); } async function readRequest(req: http.IncomingMessage, catalog?: AgentCatalog) { diff --git a/test/unit/native-gateway.test.ts b/test/unit/native-gateway.test.ts index 211a94d..8510754 100644 --- a/test/unit/native-gateway.test.ts +++ b/test/unit/native-gateway.test.ts @@ -1246,7 +1246,7 @@ test('oversized uploads receive HTTP 413 while the client is still streaming', a assert.equal(await response.text(), 'Request too large'); }); -test('OpenAI has no implicit request deadline while explicit limits and Claude passthrough remain bounded', async (t) => { +test('OpenAI and Claude passthrough have no implicit request deadline while explicit limits bound both', async (t) => { const durations: number[] = []; t.mock.method(AbortSignal, 'timeout', (ms: number) => { durations.push(ms); @@ -1260,10 +1260,15 @@ test('OpenAI has no implicit request deadline while explicit limits and Claude p assert.equal((await ordinary(body)).status, 200); assert.deepEqual(durations, [], 'Astra must not inherit an absolute three-minute timer'); assert.equal((await ordinary({ model: 'claude-sonnet-5', messages })).status, 200); - assert.deepEqual(durations, [180000]); + assert.deepEqual( + durations, + [], + 'Claude passthrough must not cut long streams, such as advisor calls, at three minutes', + ); const bounded = await gateway(t, upstream, { timeoutMs: 25 }); assert.equal((await bounded(body)).status, 200); - assert.deepEqual(durations, [180000, 25]); + assert.equal((await bounded({ model: 'claude-sonnet-5', messages })).status, 200); + assert.deepEqual(durations, [25, 25]); }); test('an explicit OpenAI timeout aborts upstream inference', async (t) => {