fix(sse): stop stream readiness from treating a choices-less error frame as success (#7503) (#8504)

Co-authored-by: ikelvingo <im.kelvinwong@gmail.com>
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-25 04:57:22 -03:00
committed by GitHub
parent 6cad24eec0
commit 9ced2e99df
3 changed files with 88 additions and 1 deletions

View File

@@ -0,0 +1 @@
- fix(sse): stop stream readiness from treating a choices-less mid-stream error frame as a successful stream, so combo can fail over instead of returning zero `choices` (#7503)

View File

@@ -81,11 +81,40 @@ function getPayloadType(payload: unknown, eventType = ""): string {
return typeof type === "string" ? type : eventType;
}
// Keys that indicate a frame carries (or is starting to carry) actual model
// output — as opposed to a bare `{error:{...}}` frame with no output signal
// at all. A stream that only ever emits error-only frames (e.g. a CLI
// passthrough executor's mid-stream spawn failure, #7503) must NOT be
// classified as "ready" — treating it as ready lets the malformed frame
// reach the client as a fake 200 success and blocks combo fallback to the
// next candidate.
const CONTENT_BEARING_KEYS = [
"choices",
"candidates",
"content_block",
"delta",
"output",
"response",
"parts",
"tool_calls",
"tool_use",
"function_call",
"function_call_output",
];
function isErrorOnlyStructuredPayload(payload: Record<string, unknown>): boolean {
if (!("error" in payload)) return false;
return !CONTENT_BEARING_KEYS.some((key) => key in payload);
}
function hasNonPingStructuredPayload(payload: unknown, eventType = ""): boolean {
const type = getPayloadType(payload, eventType);
if (isPingEventType(eventType) || isPingEventType(type)) return false;
if (Array.isArray(payload)) return payload.length > 0;
if (isRecord(payload)) return Object.keys(payload).length > 0;
if (isRecord(payload)) {
if (Object.keys(payload).length === 0) return false;
return !isErrorOnlyStructuredPayload(payload);
}
return payload !== null && payload !== undefined;
}

View File

@@ -0,0 +1,57 @@
import test from "node:test";
import assert from "node:assert/strict";
const { hasStreamReadinessSignal } = await import("@omniroute/open-sse/utils/streamReadiness");
const { buildErrorBody } = await import("@omniroute/open-sse/utils/error");
const { AuggieExecutor } = await import("@omniroute/open-sse/executors/auggie");
test("hasStreamReadinessSignal wrongly reports readiness for a choices-less mid-stream error frame", () => {
const errorBody = buildErrorBody(502, "Auggie CLI not found: auggie.cmd");
assert.equal("choices" in errorBody, false, "sanity check: buildErrorBody() output really has no choices key");
const sse = `data: ${JSON.stringify(errorBody)}\n\ndata: [DONE]\n\n`;
const ready = hasStreamReadinessSignal(sse);
assert.equal(
ready,
false,
"a stream carrying only a choices-less error frame should NOT be reported as 'ready' " +
"(successful) — it gives the combo router no signal to fail over to the next candidate, " +
"and the client accumulates zero `choices` for the whole request, which is exactly what " +
"makes strict OpenAI-SDK clients (VS Code Copilot, Cline) throw 'Response contained no choices.'"
);
});
test("AuggieExecutor's real streaming error path emits an SSE body with zero populated `choices` (end-to-end)", async () => {
const executor = new AuggieExecutor();
const previousBin = process.env.AUGGIE_BIN;
process.env.AUGGIE_BIN = "/definitely/does/not/exist/auggie-xyz-7503";
try {
const { response } = await executor.execute({
model: "claude-sonnet-4.6",
body: { messages: [{ role: "user", content: "hi" }] },
stream: true,
credentials: {} as never,
signal: null,
log: { info: () => {}, debug: () => {}, warn: () => {} },
});
const text = await response.text();
const dataLines = text.split("\n").filter((l) => l.startsWith("data: "))
.map((l) => l.slice("data: ".length).trim()).filter((d) => d !== "[DONE]");
assert.ok(dataLines.length > 0, "expected at least one SSE data event");
const anyPopulatedChoices = dataLines.some((d) => {
const parsed = JSON.parse(d);
return Array.isArray(parsed.choices) && parsed.choices.length > 0 &&
(parsed.choices[0]?.delta?.content || parsed.choices[0]?.message?.content);
});
assert.equal(anyPopulatedChoices, false,
"AuggieExecutor's CLI-failure SSE stream never carries a populated `choices` entry — " +
"it is only a choices-less {error:...} frame + [DONE]");
} finally {
if (previousBin === undefined) delete process.env.AUGGIE_BIN;
else process.env.AUGGIE_BIN = previousBin;
}
});