mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
* fix(combo): failover when upstream SSE is truncated mid-lifecycle User log 1784230812441-bf3789: a combo target returned an SSE stream that carried bytes but never sent a recognised terminator (`data: [DONE]`, `message_stop`, `message_delta` with `stop_reason`, or a `finish_reason`) and never produced a single parseable SSE frame. The streaming quality validator's generic done-branch gate only checked `!sawAnyBytes`, so any byte at all — even unparseable garbage — passed the stream through. The combo did not fail over to the next target and the downstream SSE client hung waiting for events that never arrived. Rebuilt against the current release/v3.8.49 tip instead of the original branch diff: the original diff predates and deletes two fixes already merged to release — issue #7285 (`OpenAiLifecycleFlags` / `applyOpenAiLifecycleEvent`, the OpenAI-shape "truncated without finish_reason" failover branch) and issue #1382 (`SseLifecycleFlags .hasRealContent`, the Claude real-content vs. empty-content_block nuance). Both are preserved untouched here. Two new flags are tracked in parallel to that existing machinery instead of replacing it: * sawStructuredSSE — any parseable `event:` or `data:` frame was seen, even one carrying no recognised content (ping/metadata) — keeps the #3399/#3685 pass-through contract for those streams. * sawTerminator — a recognised terminator arrived: `data: [DONE]`, an OpenAI `finish_reason` (mirrors `openAi.hasTerminalMarker`), a Claude `message_stop`/`message_delta` with `stop_reason` (mirrors `sse.hasLifecycleEnd`), or a terminal `usage`-only chunk (new). The generic done-branch gate now requires neither flag to be true before marking the stream invalid, replacing the old `!sawAnyBytes` check (now dead and removed). The #7285 and #1382 branches are untouched. Tests added in tests/unit/validate-response-quality.test.ts (adapted from the original branch, same scenarios): 1. incomplete lifecycle (the bug) -> invalid 2. `[DONE]` only -> valid (regression guard for #3685) 3. `event: ping` only -> valid (regression guard for #3399) 4. OpenAI `finish_reason`-only chunk (no `[DONE]`) -> valid, isolates the new finish_reason check Full touched-area regression set verified green (51/51): the new tests plus combo-streaming-openai-no-finish-reason-7285, streaming-empty- content-block-1382, combo-quality-validator-reasoning, masked-200- exhaustion-fallback-6427, combo-streaming-empty-content-failover, combo-empty-content-failover-5085, combo-response-validation-failover, and combo-response-validation. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(combo): extract consumeSseLine + isTerminalUsageOnlyChunk helpers (complexity gate on parseAccumulatedSse) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(combo): move parseJsonRecord to module scope (finish complexity-gate compensation) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>