import { describe, test } from "node:test"; import assert from "node:assert/strict"; import { synthesizeOpenAiSseFromJson } from "../../open-sse/utils/jsonToSse.ts"; function parseDataChunks(sse: string) { return sse .split(/\n\n/) .map((b) => b.trim()) .filter((b) => b.startsWith("data:")) .map((b) => b.slice(5).trim()); } describe("synthesizeOpenAiSseFromJson (#3089)", () => { test("converts a reasoning chat-completion JSON to SSE preserving content + reasoning_content", () => { const body = JSON.stringify({ id: "mock-1", object: "chat.completion", created: 123, model: "mock-reasoner", choices: [ { index: 0, message: { role: "assistant", reasoning_content: "thinking...", content: "HI there", }, finish_reason: "stop", }, ], usage: { prompt_tokens: 7, completion_tokens: 3, total_tokens: 10 }, }); const sse = synthesizeOpenAiSseFromJson(body); const chunks = parseDataChunks(sse); assert.ok(sse.startsWith("data: "), "must be SSE"); assert.equal(chunks[chunks.length - 1], "[DONE]", "must terminate with [DONE]"); const events = chunks.filter((c) => c !== "[DONE]").map((c) => JSON.parse(c)); const deltas = events.map((e) => e.choices[0].delta); assert.equal(events[0].object, "chat.completion.chunk"); assert.equal(events[0].model, "mock-reasoner"); assert.equal(deltas[0].role, "assistant", "first delta announces the role"); // #3089 follow-up: reasoning_content and content are emitted as SEPARATE // deltas, each EXACTLY ONCE (no duplication), with reasoning before content. const reasoningDeltas = deltas.filter((d) => d.reasoning_content !== undefined); const contentDeltas = deltas.filter((d) => d.content !== undefined); assert.equal(reasoningDeltas.length, 1, "reasoning_content must appear exactly once"); assert.equal(contentDeltas.length, 1, "content must appear exactly once"); assert.equal(reasoningDeltas[0].reasoning_content, "thinking..."); assert.equal(contentDeltas[0].content, "HI there"); const reasoningIdx = deltas.findIndex((d) => d.reasoning_content !== undefined); const contentIdx = deltas.findIndex((d) => d.content !== undefined); assert.ok(reasoningIdx < contentIdx, "reasoning_content delta precedes content delta"); const finishChunk = events[events.length - 1]; assert.equal(finishChunk.choices[0].finish_reason, "stop"); assert.deepEqual(finishChunk.usage, { prompt_tokens: 7, completion_tokens: 3, total_tokens: 10, }); }); test("content-only completion converts without a reasoning_content delta", () => { const sse = synthesizeOpenAiSseFromJson( JSON.stringify({ choices: [{ message: { role: "assistant", content: "ok" } }] }) ); const deltas = parseDataChunks(sse) .filter((c) => c !== "[DONE]") .map((c) => JSON.parse(c).choices[0].delta); assert.equal(deltas.filter((d) => d.content === "ok").length, 1); assert.equal( deltas.some((d) => d.reasoning_content !== undefined), false ); }); test("preserves client-readable reasoning alias", () => { const sse = synthesizeOpenAiSseFromJson( JSON.stringify({ choices: [ { message: { role: "assistant", reasoning: "client-readable thinking", content: "final text", }, }, ], }) ); const deltas = parseDataChunks(sse) .filter((c) => c !== "[DONE]") .map((c) => JSON.parse(c).choices[0].delta); assert.equal( deltas.find((d) => d.reasoning !== undefined)?.reasoning, "client-readable thinking" ); assert.equal( deltas.some((d) => d.reasoning_content !== undefined), false ); }); test("mirrors unsupported reasoning aliases to reasoning_content", () => { const sse = synthesizeOpenAiSseFromJson( JSON.stringify({ choices: [ { message: { role: "assistant", reasoning_text: "alias thinking", content: "final text", }, }, ], }) ); const deltas = parseDataChunks(sse) .filter((c) => c !== "[DONE]") .map((c) => JSON.parse(c).choices[0].delta); assert.equal( deltas.find((d) => d.reasoning_content !== undefined)?.reasoning_content, "alias thinking" ); }); test("forwards tool_calls in the delta", () => { const sse = synthesizeOpenAiSseFromJson( JSON.stringify({ choices: [ { message: { role: "assistant", tool_calls: [ { id: "t1", type: "function", function: { name: "f", arguments: "{}" } }, ], }, finish_reason: "tool_calls", }, ], }) ); const deltas = parseDataChunks(sse) .filter((c) => c !== "[DONE]") .map((c) => JSON.parse(c).choices[0].delta); const toolDelta = deltas.find((d) => Array.isArray(d.tool_calls)); assert.ok(toolDelta, "a delta must carry tool_calls"); assert.equal(toolDelta.tool_calls[0].id, "t1"); }); test("normalizes prohibited content finish reasons in synthesized SSE", () => { const sse = synthesizeOpenAiSseFromJson( JSON.stringify({ choices: [ { message: { role: "assistant", content: "partial text" }, finish_reason: "prohibited_content", }, ], }) ); const events = parseDataChunks(sse) .filter((c) => c !== "[DONE]") .map((c) => JSON.parse(c)); const finishChunk = events.at(-1); assert.equal(finishChunk.choices[0].finish_reason, "content_filter"); assert.equal( events.some((event) => event.choices[0].delta.content === "partial text"), true ); }); test("returns empty string for non-completion JSON / invalid JSON", () => { assert.equal(synthesizeOpenAiSseFromJson('{"error":{"message":"x"}}'), ""); assert.equal(synthesizeOpenAiSseFromJson("{not json"), ""); assert.equal(synthesizeOpenAiSseFromJson('{"choices":[]}'), ""); assert.equal(synthesizeOpenAiSseFromJson("[]"), ""); }); });