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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 4 additions & 5 deletions plugins/multi-core/src/gateway/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
11 changes: 8 additions & 3 deletions test/unit/native-gateway.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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) => {
Expand Down