mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 04:42:10 +03:00
fix: hasValuableContent explicit boolean returns for SSE streaming (#676)
The hasValuableContent() function in streamHelpers.ts returned undefined instead of explicit false when checking empty delta chunks. This caused JavaScript type coercion issues where undefined !== '' evaluated to true, passing empty chunks through to clients. Fix: Replace implicit returns with explicit boolean returns using typeof checks and length comparisons for all content fields (content, reasoning_content, tool_calls, text, thinking, partial_json). Test: Added unit tests covering OpenAI, Claude, and Gemini format edge cases. Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
This commit is contained in:
@@ -39,26 +39,28 @@ export function parseSSELine(line) {
|
||||
// Check if chunk has valuable content (not empty)
|
||||
export function hasValuableContent(chunk, format) {
|
||||
// OpenAI format
|
||||
if (format === FORMATS.OPENAI && chunk.choices?.[0]?.delta) {
|
||||
if (format === FORMATS.OPENAI) {
|
||||
if (!chunk.choices?.[0]?.delta) return false;
|
||||
const delta = chunk.choices[0].delta;
|
||||
return (
|
||||
(delta.content && delta.content !== "") ||
|
||||
(delta.reasoning_content && delta.reasoning_content !== "") ||
|
||||
(delta.tool_calls && delta.tool_calls.length > 0) ||
|
||||
chunk.choices[0].finish_reason ||
|
||||
delta.role
|
||||
);
|
||||
if (typeof delta.content === "string" && delta.content.length > 0) return true;
|
||||
if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0)
|
||||
return true;
|
||||
if (Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0) return true;
|
||||
if (chunk.choices[0].finish_reason) return true;
|
||||
if (typeof delta.role === "string" && delta.role.length > 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Claude format
|
||||
if (format === FORMATS.CLAUDE) {
|
||||
const isContentBlockDelta = chunk.type === "content_block_delta";
|
||||
const hasText = chunk.delta?.text && chunk.delta.text !== "";
|
||||
const hasThinking = chunk.delta?.thinking && chunk.delta.thinking !== "";
|
||||
const hasInputJson = chunk.delta?.partial_json && chunk.delta.partial_json !== "";
|
||||
|
||||
if (isContentBlockDelta && !hasText && !hasThinking && !hasInputJson) {
|
||||
return false;
|
||||
if (isContentBlockDelta) {
|
||||
const hasText = typeof chunk.delta?.text === "string" && chunk.delta.text.length > 0;
|
||||
const hasThinking =
|
||||
typeof chunk.delta?.thinking === "string" && chunk.delta.thinking.length > 0;
|
||||
const hasInputJson =
|
||||
typeof chunk.delta?.partial_json === "string" && chunk.delta.partial_json.length > 0;
|
||||
if (!hasText && !hasThinking && !hasInputJson) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -73,7 +75,7 @@ export function hasValuableContent(chunk, format) {
|
||||
if (!parts || parts.length === 0) return false;
|
||||
// Filter out chunks where all parts have empty text
|
||||
const hasContent = parts.some(
|
||||
(p) => (p.text && p.text !== "") || p.functionCall || p.executableCode
|
||||
(p) => (typeof p.text === "string" && p.text.length > 0) || p.functionCall || p.executableCode
|
||||
);
|
||||
return hasContent;
|
||||
}
|
||||
|
||||
72
tests/unit/streamHelpers.test.mjs
Normal file
72
tests/unit/streamHelpers.test.mjs
Normal file
@@ -0,0 +1,72 @@
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { hasValuableContent } from "../../open-sse/utils/streamHelpers.ts";
|
||||
import { FORMATS } from "../../open-sse/translator/formats.ts";
|
||||
|
||||
describe("hasValuableContent", () => {
|
||||
describe("OpenAI format", () => {
|
||||
it("returns true for content with text", () => {
|
||||
const chunk = { choices: [{ delta: { content: "Hello" } }] };
|
||||
assert.strictEqual(hasValuableContent(chunk, FORMATS.OPENAI), true);
|
||||
});
|
||||
|
||||
it("returns false for empty delta", () => {
|
||||
const chunk = { choices: [{ delta: {} }] };
|
||||
assert.strictEqual(hasValuableContent(chunk, FORMATS.OPENAI), false);
|
||||
});
|
||||
|
||||
it("returns false for delta with empty string content", () => {
|
||||
const chunk = { choices: [{ delta: { content: "" } }] };
|
||||
assert.strictEqual(hasValuableContent(chunk, FORMATS.OPENAI), false);
|
||||
});
|
||||
|
||||
it("returns true for reasoning_content", () => {
|
||||
const chunk = { choices: [{ delta: { reasoning_content: "thinking" } }] };
|
||||
assert.strictEqual(hasValuableContent(chunk, FORMATS.OPENAI), true);
|
||||
});
|
||||
|
||||
it("returns true for finish_reason", () => {
|
||||
const chunk = { choices: [{ delta: {}, finish_reason: "stop" }] };
|
||||
assert.strictEqual(hasValuableContent(chunk, FORMATS.OPENAI), true);
|
||||
});
|
||||
|
||||
it("returns true for role delta", () => {
|
||||
const chunk = { choices: [{ delta: { role: "assistant" } }] };
|
||||
assert.strictEqual(hasValuableContent(chunk, FORMATS.OPENAI), true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Claude format", () => {
|
||||
it("returns true for content_block_delta with text", () => {
|
||||
const chunk = { type: "content_block_delta", delta: { text: "Hello" } };
|
||||
assert.strictEqual(hasValuableContent(chunk, FORMATS.CLAUDE), true);
|
||||
});
|
||||
|
||||
it("returns false for empty content_block_delta", () => {
|
||||
const chunk = { type: "content_block_delta", delta: {} };
|
||||
assert.strictEqual(hasValuableContent(chunk, FORMATS.CLAUDE), false);
|
||||
});
|
||||
|
||||
it("returns true for thinking blocks", () => {
|
||||
const chunk = { type: "content_block_delta", delta: { thinking: "reasoning" } };
|
||||
assert.strictEqual(hasValuableContent(chunk, FORMATS.CLAUDE), true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Gemini format", () => {
|
||||
it("returns true for content with text", () => {
|
||||
const chunk = { candidates: [{ content: { parts: [{ text: "Hello" }] } }] };
|
||||
assert.strictEqual(hasValuableContent(chunk, FORMATS.GEMINI), true);
|
||||
});
|
||||
|
||||
it("returns false for empty parts", () => {
|
||||
const chunk = { candidates: [{ content: { parts: [] } }] };
|
||||
assert.strictEqual(hasValuableContent(chunk, FORMATS.GEMINI), false);
|
||||
});
|
||||
|
||||
it("returns true for finishReason", () => {
|
||||
const chunk = { candidates: [{ finishReason: "STOP" }] };
|
||||
assert.strictEqual(hasValuableContent(chunk, FORMATS.GEMINI), true);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user