diff --git a/open-sse/utils/streamReadiness.ts b/open-sse/utils/streamReadiness.ts index 3b73024921..b61d5a6f3b 100644 --- a/open-sse/utils/streamReadiness.ts +++ b/open-sse/utils/streamReadiness.ts @@ -28,6 +28,11 @@ function hasUsefulValue(value: unknown): boolean { "delta", "reasoning_content", "reasoning", + // Mistral/Magistral thinking arrays and StepFun/OpenRouter reasoning_details are + // valid model output — without these a reasoning-only stream was misclassified as + // "no useful content" and turned into a spurious 502 (#2520). + "thinking", + "reasoning_details", "partial_json", "arguments", "name", diff --git a/tests/unit/stream-readiness.test.ts b/tests/unit/stream-readiness.test.ts index f322bd1516..bcd36ab671 100644 --- a/tests/unit/stream-readiness.test.ts +++ b/tests/unit/stream-readiness.test.ts @@ -446,3 +446,21 @@ test("ensureStreamReadiness returns 502 when stream ends without useful content" assert.equal(result.ok, false); assert.equal(result.response.status, 502); }); + +// Regression for #2520: a reasoning-only stream (Mistral `thinking` array / StepFun +// `reasoning_details`) is real output and must NOT be classified as "no useful content" +// (which produced a spurious 502). +test("hasUsefulStreamContent detects thinking[] and reasoning_details (#2520)", () => { + assert.equal( + hasUsefulStreamContent( + `data: ${JSON.stringify({ choices: [{ delta: { content: [{ type: "thinking", thinking: [{ text: "reasoning..." }] }] }, index: 0 }] })}\n\n` + ), + true + ); + assert.equal( + hasUsefulStreamContent( + `data: ${JSON.stringify({ choices: [{ delta: { reasoning_details: [{ type: "reasoning.text", text: "deliberating" }] }, index: 0 }] })}\n\n` + ), + true + ); +});