Fixed
-
๐ Router stream-stall failover after partial response (#137) โ previously, if a model streamed some data then stalled (per-chunk timeout), the client received a truncated response with no automatic retry. Now the router:
- Emits a synthetic SSE error event in OpenAI format (
code: fcm_stream_failover) so the client knows the stream was truncated and that failover is happening. - Returns
failoverToNext: trueto the outer retry loop, which tries the next model on a fresh upstream connection. - The new model's chunks are appended to the same response object โ the client sees one continuous stream with a clear mid-stream error marker.
- On a mid-stream failover the router skips
res.writeHead()(headers were already sent by the previous model) and emits an SSE comment: fcm-router-failover-from=<oldKey>so debuggers can see which model served which segment.
Scope of the fix: only
stream_stall_timeoutandtimeouterrors trigger mid-stream failover. Generic upstream errors (malformed JSON, connection reset, destroy) still close cleanly with no failover โ their partial data is more likely to be invalid, so re-streaming from another model wouldn't help. - Emits a synthetic SSE error event in OpenAI format (
Maintenance
- ๐งช +1 regression test (
test/test.js) locking in the new failover behavior with a stalled-stream mock provider. The existing "does not retry after partial output" test still passes โ it usesres.destroy()which is not a stall, so it correctly keeps the old behaviour. - ๐งช 591/591 tests pass (
pnpm test). - ๐งน
vite buildsucceeds.
How clients see this
A streaming request that stalls now produces something like:
data: {"choices":[{"delta":{"content":"partial"}}]}
data: {"error":{"message":"Stream truncated by router due to upstream stream_stall_timeout; failing over to next model.","type":"stream_error","code":"fcm_stream_failover","reason":"stream_stall_timeout"}}
data: {"choices":[{"delta":{"content":"fresh answer from fallback"}}]}
data: [DONE]
Plus an SSE comment marker on each failover segment.