refactor(open-sse): extract chunkBuilder, dedup chat.completion.chunk (B1)

Add helpers/chunkBuilder.js; apply to claude/gemini/kiro/ollama/commandcode/
openai-responses response translators. Caller supplies id/created/model so each
keeps exact id-generation + usage semantics. Extend golden response stream to
openai-responses (codex). No behavior change; gate: no regression.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
decolua 2026-06-13 17:05:42 +07:00
parent 0e34358e74
commit 17202f7111
10 changed files with 270 additions and 295 deletions

View file

@ -0,0 +1,11 @@
// Build OpenAI chat.completion.chunk. Caller supplies id/created/model so each
// translator keeps its exact id-generation + created semantics (no Date.now here).
export function buildChunk({ id, created, model }, delta, finishReason = null) {
return {
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta, finish_reason: finishReason }],
};
}

View file

@ -1,19 +1,14 @@
import { register } from "../index.js"; import { register } from "../index.js";
import { FORMATS } from "../formats.js"; import { FORMATS } from "../formats.js";
import { buildChunk } from "../helpers/chunkBuilder.js";
// Create OpenAI chunk helper // Create OpenAI chunk helper
function createChunk(state, delta, finishReason = null) { function createChunk(state, delta, finishReason = null) {
return { return buildChunk(
id: `chatcmpl-${state.messageId}`, { id: `chatcmpl-${state.messageId}`, created: Math.floor(Date.now() / 1000), model: state.model },
object: "chat.completion.chunk", delta,
created: Math.floor(Date.now() / 1000), finishReason
model: state.model, );
choices: [{
index: 0,
delta,
finish_reason: finishReason
}]
};
} }
// Convert Claude stream chunk to OpenAI format // Convert Claude stream chunk to OpenAI format
@ -129,13 +124,7 @@ export function claudeToOpenAIResponse(chunk, state) {
if (chunk.delta?.stop_reason) { if (chunk.delta?.stop_reason) {
state.finishReason = convertStopReason(chunk.delta.stop_reason); state.finishReason = convertStopReason(chunk.delta.stop_reason);
const finalChunk = { const finalChunk = createChunk(state, {}, state.finishReason);
id: `chatcmpl-${state.messageId}`,
object: "chat.completion.chunk",
created: Math.floor(Date.now() / 1000),
model: state.model,
choices: [{ index: 0, delta: {}, finish_reason: state.finishReason }]
};
if (state.usage) { if (state.usage) {
finalChunk.usage = { finalChunk.usage = {
@ -169,18 +158,7 @@ export function claudeToOpenAIResponse(chunk, state) {
total_tokens: (state.usage.input_tokens || 0) + (state.usage.output_tokens || 0) total_tokens: (state.usage.input_tokens || 0) + (state.usage.output_tokens || 0)
} }
} : {}; } : {};
results.push({ results.push({ ...createChunk(state, {}, finishReason), ...usageObj });
id: `chatcmpl-${state.messageId}`,
object: "chat.completion.chunk",
created: Math.floor(Date.now() / 1000),
model: state.model,
choices: [{
index: 0,
delta: {},
finish_reason: finishReason
}],
...usageObj
});
state.finishReasonSent = true; state.finishReasonSent = true;
} }
break; break;

View file

@ -17,6 +17,7 @@
*/ */
import { register } from "../index.js"; import { register } from "../index.js";
import { FORMATS } from "../formats.js"; import { FORMATS } from "../formats.js";
import { buildChunk } from "../helpers/chunkBuilder.js";
function ensureState(state, model) { function ensureState(state, model) {
if (!state.responseId) { if (!state.responseId) {
@ -34,13 +35,11 @@ function ensureState(state, model) {
} }
function makeChunk(state, delta, finishReason = null) { function makeChunk(state, delta, finishReason = null) {
return { return buildChunk(
id: state.responseId, { id: state.responseId, created: state.created, model: state.model },
object: "chat.completion.chunk", delta,
created: state.created, finishReason
model: state.model, );
choices: [{ index: 0, delta, finish_reason: finishReason }],
};
} }
function mapFinishReason(reason) { function mapFinishReason(reason) {

View file

@ -1,5 +1,11 @@
import { register } from "../index.js"; import { register } from "../index.js";
import { FORMATS } from "../formats.js"; import { FORMATS } from "../formats.js";
import { buildChunk } from "../helpers/chunkBuilder.js";
// Build chunk meta for current gemini state
function chunkMeta(state) {
return { id: `chatcmpl-${state.messageId}`, created: Math.floor(Date.now() / 1000), model: state.model };
}
// Convert Gemini response chunk to OpenAI format // Convert Gemini response chunk to OpenAI format
export function geminiToOpenAIResponse(chunk, state) { export function geminiToOpenAIResponse(chunk, state) {
@ -18,17 +24,7 @@ export function geminiToOpenAIResponse(chunk, state) {
state.messageId = response.responseId || `msg_${Date.now()}`; state.messageId = response.responseId || `msg_${Date.now()}`;
state.model = response.modelVersion || "gemini"; state.model = response.modelVersion || "gemini";
state.functionIndex = 0; state.functionIndex = 0;
results.push({ results.push(buildChunk(chunkMeta(state), { role: "assistant" }, null));
id: `chatcmpl-${state.messageId}`,
object: "chat.completion.chunk",
created: Math.floor(Date.now() / 1000),
model: state.model,
choices: [{
index: 0,
delta: { role: "assistant" },
finish_reason: null
}]
});
} }
// Process parts // Process parts
@ -43,19 +39,11 @@ export function geminiToOpenAIResponse(chunk, state) {
const hasFunctionCall = !!part.functionCall; const hasFunctionCall = !!part.functionCall;
if (hasTextContent) { if (hasTextContent) {
results.push({ results.push(buildChunk(
id: `chatcmpl-${state.messageId}`, chunkMeta(state),
object: "chat.completion.chunk", isThought ? { reasoning_content: part.text } : { content: part.text },
created: Math.floor(Date.now() / 1000), null
model: state.model, ));
choices: [{
index: 0,
delta: isThought
? { reasoning_content: part.text }
: { content: part.text },
finish_reason: null
}]
});
} }
if (hasFunctionCall) { if (hasFunctionCall) {
@ -77,17 +65,7 @@ export function geminiToOpenAIResponse(chunk, state) {
state.toolCalls.set(toolCallIndex, toolCall); state.toolCalls.set(toolCallIndex, toolCall);
results.push({ results.push(buildChunk(chunkMeta(state), { tool_calls: [toolCall] }, null));
id: `chatcmpl-${state.messageId}`,
object: "chat.completion.chunk",
created: Math.floor(Date.now() / 1000),
model: state.model,
choices: [{
index: 0,
delta: { tool_calls: [toolCall] },
finish_reason: null
}]
});
} }
continue; continue;
} }
@ -97,19 +75,11 @@ export function geminiToOpenAIResponse(chunk, state) {
// can also stream thought parts without a signature; those must not be // can also stream thought parts without a signature; those must not be
// surfaced as normal assistant content in OpenAI-compatible clients. // surfaced as normal assistant content in OpenAI-compatible clients.
if (part.text !== undefined && part.text !== "") { if (part.text !== undefined && part.text !== "") {
results.push({ results.push(buildChunk(
id: `chatcmpl-${state.messageId}`, chunkMeta(state),
object: "chat.completion.chunk", isThought ? { reasoning_content: part.text } : { content: part.text },
created: Math.floor(Date.now() / 1000), null
model: state.model, ));
choices: [{
index: 0,
delta: isThought
? { reasoning_content: part.text }
: { content: part.text },
finish_reason: null
}]
});
} }
// Function call // Function call
@ -132,39 +102,23 @@ export function geminiToOpenAIResponse(chunk, state) {
state.toolCalls.set(toolCallIndex, toolCall); state.toolCalls.set(toolCallIndex, toolCall);
results.push({ results.push(buildChunk(chunkMeta(state), { tool_calls: [toolCall] }, null));
id: `chatcmpl-${state.messageId}`,
object: "chat.completion.chunk",
created: Math.floor(Date.now() / 1000),
model: state.model,
choices: [{
index: 0,
delta: { tool_calls: [toolCall] },
finish_reason: null
}]
});
} }
// Inline data (images) // Inline data (images)
const inlineData = part.inlineData || part.inline_data; const inlineData = part.inlineData || part.inline_data;
if (inlineData?.data) { if (inlineData?.data) {
const mimeType = inlineData.mimeType || inlineData.mime_type || "image/png"; const mimeType = inlineData.mimeType || inlineData.mime_type || "image/png";
results.push({ results.push(buildChunk(
id: `chatcmpl-${state.messageId}`, chunkMeta(state),
object: "chat.completion.chunk", {
created: Math.floor(Date.now() / 1000), images: [{
model: state.model, type: "image_url",
choices: [{ image_url: { url: `data:${mimeType};base64,${inlineData.data}` }
index: 0, }]
delta: { },
images: [{ null
type: "image_url", ));
image_url: { url: `data:${mimeType};base64,${inlineData.data}` }
}]
},
finish_reason: null
}]
});
} }
} }
} }
@ -218,17 +172,7 @@ export function geminiToOpenAIResponse(chunk, state) {
finishReason = "tool_calls"; finishReason = "tool_calls";
} }
const finalChunk = { const finalChunk = buildChunk(chunkMeta(state), {}, finishReason);
id: `chatcmpl-${state.messageId}`,
object: "chat.completion.chunk",
created: Math.floor(Date.now() / 1000),
model: state.model,
choices: [{
index: 0,
delta: {},
finish_reason: finishReason
}]
};
// Include usage in final chunk for downstream translators // Include usage in final chunk for downstream translators
if (state.usage) { if (state.usage) {

View file

@ -4,6 +4,12 @@
*/ */
import { register } from "../index.js"; import { register } from "../index.js";
import { FORMATS } from "../formats.js"; import { FORMATS } from "../formats.js";
import { buildChunk } from "../helpers/chunkBuilder.js";
// Build chunk meta for current kiro state
function chunkMeta(state) {
return { id: state.responseId, created: state.created, model: state.model || "kiro" };
}
/** /**
* Parse Kiro SSE event and convert to OpenAI format * Parse Kiro SSE event and convert to OpenAI format
@ -66,20 +72,10 @@ export function convertKiroToOpenAI(chunk, state) {
const content = data.assistantResponseEvent?.content || data.content || ""; const content = data.assistantResponseEvent?.content || data.content || "";
if (!content) return null; if (!content) return null;
const openaiChunk = { const openaiChunk = buildChunk(chunkMeta(state), {
id: state.responseId, ...(state.chunkIndex === 0 ? { role: "assistant" } : {}),
object: "chat.completion.chunk", content: content
created: state.created, }, null);
model: state.model || "kiro",
choices: [{
index: 0,
delta: {
...(state.chunkIndex === 0 ? { role: "assistant" } : {}),
content: content
},
finish_reason: null
}]
};
state.chunkIndex++; state.chunkIndex++;
return openaiChunk; return openaiChunk;
@ -97,20 +93,10 @@ export function convertKiroToOpenAI(chunk, state) {
: (reasoning.text || reasoning.content || data.content || ""); : (reasoning.text || reasoning.content || data.content || "");
if (!content) return null; if (!content) return null;
const openaiChunk = { const openaiChunk = buildChunk(chunkMeta(state), {
id: state.responseId, ...(state.chunkIndex === 0 ? { role: "assistant" } : {}),
object: "chat.completion.chunk", reasoning_content: content
created: state.created, }, null);
model: state.model || "kiro",
choices: [{
index: 0,
delta: {
...(state.chunkIndex === 0 ? { role: "assistant" } : {}),
reasoning_content: content
},
finish_reason: null
}]
};
state.chunkIndex++; state.chunkIndex++;
return openaiChunk; return openaiChunk;
@ -123,28 +109,18 @@ export function convertKiroToOpenAI(chunk, state) {
const toolName = toolUse.name || ""; const toolName = toolUse.name || "";
const toolInput = toolUse.input || {}; const toolInput = toolUse.input || {};
const openaiChunk = { const openaiChunk = buildChunk(chunkMeta(state), {
id: state.responseId, ...(state.chunkIndex === 0 ? { role: "assistant" } : {}),
object: "chat.completion.chunk", tool_calls: [{
created: state.created,
model: state.model || "kiro",
choices: [{
index: 0, index: 0,
delta: { id: toolCallId,
...(state.chunkIndex === 0 ? { role: "assistant" } : {}), type: "function",
tool_calls: [{ function: {
index: 0, name: toolName,
id: toolCallId, arguments: JSON.stringify(toolInput)
type: "function", }
function: {
name: toolName,
arguments: JSON.stringify(toolInput)
}
}]
},
finish_reason: null
}] }]
}; }, null);
state.chunkIndex++; state.chunkIndex++;
return openaiChunk; return openaiChunk;
@ -154,17 +130,7 @@ export function convertKiroToOpenAI(chunk, state) {
if (eventType === "messageStopEvent" || eventType === "done" || data.messageStopEvent) { if (eventType === "messageStopEvent" || eventType === "done" || data.messageStopEvent) {
state.finishReason = "stop"; // Mark for usage injection in stream.js state.finishReason = "stop"; // Mark for usage injection in stream.js
const openaiChunk = { const openaiChunk = buildChunk(chunkMeta(state), {}, "stop");
id: state.responseId,
object: "chat.completion.chunk",
created: state.created,
model: state.model || "kiro",
choices: [{
index: 0,
delta: {},
finish_reason: "stop"
}]
};
// Include usage in final chunk if available // Include usage in final chunk if available
if (state.usage && typeof state.usage === "object") { if (state.usage && typeof state.usage === "object") {

View file

@ -1,5 +1,6 @@
import { register } from "../index.js"; import { register } from "../index.js";
import { FORMATS } from "../formats.js"; import { FORMATS } from "../formats.js";
import { buildChunk } from "../helpers/chunkBuilder.js";
/** /**
* Convert Ollama NDJSON response to OpenAI SSE format * Convert Ollama NDJSON response to OpenAI SSE format
@ -36,18 +37,9 @@ export function ollamaToOpenAI(chunk, state) {
finishReason = "tool_calls"; finishReason = "tool_calls";
} }
return { const doneChunk = buildChunk({ id, created, model }, {}, finishReason);
id: id, doneChunk.usage = usage;
object: "chat.completion.chunk", return doneChunk;
created: created,
model: model,
choices: [{
index: 0,
delta: {},
finish_reason: finishReason
}],
usage: usage
};
} }
// Content chunk // Content chunk
@ -79,17 +71,7 @@ export function ollamaToOpenAI(chunk, state) {
delta.tool_calls = convertToolCalls(toolCalls); delta.tool_calls = convertToolCalls(toolCalls);
} }
return { return buildChunk({ id, created, model }, delta, null);
id: id,
object: "chat.completion.chunk",
created: created,
model: model,
choices: [{
index: 0,
delta: delta,
finish_reason: null
}]
};
} }
/** /**

View file

@ -4,6 +4,7 @@
*/ */
import { register } from "../index.js"; import { register } from "../index.js";
import { FORMATS } from "../formats.js"; import { FORMATS } from "../formats.js";
import { buildChunk } from "../helpers/chunkBuilder.js";
/** /**
* Translate OpenAI chunk to Responses API events * Translate OpenAI chunk to Responses API events
@ -377,17 +378,11 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
state.finishReasonSent = true; state.finishReasonSent = true;
state.finishReason = finishReason; state.finishReason = finishReason;
const finalChunk = { const finalChunk = buildChunk(
id: state.chatId || `chatcmpl-${Date.now()}`, { id: state.chatId || `chatcmpl-${Date.now()}`, created: state.created || Math.floor(Date.now() / 1000), model: state.model || "unknown" },
object: "chat.completion.chunk", {},
created: state.created || Math.floor(Date.now() / 1000), finishReason
model: state.model || "unknown", );
choices: [{
index: 0,
delta: {},
finish_reason: finishReason
}]
};
if (state.usage && typeof state.usage === "object") { if (state.usage && typeof state.usage === "object") {
finalChunk.usage = state.usage; finalChunk.usage = state.usage;
@ -414,17 +409,10 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
const delta = data.delta || ""; const delta = data.delta || "";
if (!delta) return null; if (!delta) return null;
return { return buildChunk(
id: state.chatId, { id: state.chatId, created: state.created, model: state.model || "unknown" },
object: "chat.completion.chunk", { content: delta }
created: state.created, );
model: state.model || "unknown",
choices: [{
index: 0,
delta: { content: delta },
finish_reason: null
}]
};
} }
// Text content done (ignore, we handle via delta) // Text content done (ignore, we handle via delta)
@ -437,27 +425,17 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
const item = data.item; const item = data.item;
state.currentToolCallId = item.call_id || `call_${Date.now()}`; state.currentToolCallId = item.call_id || `call_${Date.now()}`;
return { return buildChunk(
id: state.chatId, { id: state.chatId, created: state.created, model: state.model || "unknown" },
object: "chat.completion.chunk", {
created: state.created, tool_calls: [{
model: state.model || "unknown", index: state.toolCallIndex,
choices: [{ id: state.currentToolCallId,
index: 0, type: "function",
delta: { function: { name: item.name || "", arguments: "" }
tool_calls: [{ }]
index: state.toolCallIndex, }
id: state.currentToolCallId, );
type: "function",
function: {
name: item.name || "",
arguments: ""
}
}]
},
finish_reason: null
}]
};
} }
// Function call arguments delta (standard or custom_tool_call variant) // Function call arguments delta (standard or custom_tool_call variant)
@ -465,22 +443,10 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
const argsDelta = data.delta || ""; const argsDelta = data.delta || "";
if (!argsDelta) return null; if (!argsDelta) return null;
return { return buildChunk(
id: state.chatId, { id: state.chatId, created: state.created, model: state.model || "unknown" },
object: "chat.completion.chunk", { tool_calls: [{ index: state.toolCallIndex, function: { arguments: argsDelta } }] }
created: state.created, );
model: state.model || "unknown",
choices: [{
index: 0,
delta: {
tool_calls: [{
index: state.toolCallIndex,
function: { arguments: argsDelta }
}]
},
finish_reason: null
}]
};
} }
// Function call done (standard or custom_tool_call variant) // Function call done (standard or custom_tool_call variant)
@ -520,18 +486,12 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
state.finishReasonSent = true; state.finishReasonSent = true;
state.finishReason = finishReason; // Mark for usage injection in stream.js state.finishReason = finishReason; // Mark for usage injection in stream.js
const finalChunk = { const finalChunk = buildChunk(
id: state.chatId, { id: state.chatId, created: state.created, model: state.model || "unknown" },
object: "chat.completion.chunk", {},
created: state.created, finishReason
model: state.model || "unknown", );
choices: [{
index: 0,
delta: {},
finish_reason: finishReason
}]
};
// Include usage in final chunk if available // Include usage in final chunk if available
if (state.usage && typeof state.usage === "object") { if (state.usage && typeof state.usage === "object") {
finalChunk.usage = state.usage; finalChunk.usage = state.usage;
@ -553,17 +513,11 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
state.finishReasonSent = true; state.finishReasonSent = true;
// Surface the error as an OpenAI-compatible error chunk // Surface the error as an OpenAI-compatible error chunk
return { return buildChunk(
id: state.chatId || `chatcmpl-${Date.now()}`, { id: state.chatId || `chatcmpl-${Date.now()}`, created: state.created || Math.floor(Date.now() / 1000), model: state.model || "unknown" },
object: "chat.completion.chunk", { content: `[Error] ${error.message || JSON.stringify(error)}` },
created: state.created || Math.floor(Date.now() / 1000), "stop"
model: state.model || "unknown", );
choices: [{
index: 0,
delta: { content: `[Error] ${error.message || JSON.stringify(error)}` },
finish_reason: "stop"
}]
};
} }
return null; return null;
} }
@ -572,13 +526,10 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
if (eventType === "response.reasoning_summary_text.delta") { if (eventType === "response.reasoning_summary_text.delta") {
const delta = data.delta || ""; const delta = data.delta || "";
if (!delta) return null; if (!delta) return null;
return { return buildChunk(
id: state.chatId, { id: state.chatId, created: state.created, model: state.model || "unknown" },
object: "chat.completion.chunk", { reasoning_content: delta }
created: state.created, );
model: state.model || "unknown",
choices: [{ index: 0, delta: { reasoning_content: delta }, finish_reason: null }]
};
} }
// Ignore other events // Ignore other events

File diff suppressed because one or more lines are too long

View file

@ -445,3 +445,126 @@ exports[`GOLDEN response stream: Ollama → OpenAI > content + thinking + tool_c
}, },
] ]
`; `;
exports[`GOLDEN response stream: OpenAI-Responses (codex) → OpenAI > error event → error chunk (fallback id/created) 1`] = `
[
{
"choices": [
{
"delta": {
"content": "[Error] model_not_found",
},
"finish_reason": "stop",
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "unknown",
"object": "chat.completion.chunk",
},
]
`;
exports[`GOLDEN response stream: OpenAI-Responses (codex) → OpenAI > text + reasoning + tool_call + completed usage 1`] = `
[
{
"choices": [
{
"delta": {
"content": "Hello",
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "unknown",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"reasoning_content": "thinking",
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "unknown",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"tool_calls": [
{
"function": {
"arguments": "",
"name": "get_weather",
},
"id": "call_1",
"index": 0,
"type": "function",
},
],
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "unknown",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {
"tool_calls": [
{
"function": {
"arguments": "{"city":"NYC"}",
},
"index": 0,
},
],
},
"finish_reason": null,
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "unknown",
"object": "chat.completion.chunk",
},
{
"choices": [
{
"delta": {},
"finish_reason": "tool_calls",
"index": 0,
},
],
"created": 0,
"id": "chatcmpl-<TS>",
"model": "unknown",
"object": "chat.completion.chunk",
"usage": {
"completion_tokens": 5,
"prompt_tokens": 10,
"prompt_tokens_details": {
"cached_tokens": 3,
},
"total_tokens": 15,
},
},
]
`;

View file

@ -95,3 +95,24 @@ describe("GOLDEN response stream: Ollama → OpenAI", () => {
expect(runStream(FORMATS.OLLAMA, FORMATS.OPENAI, events)).toMatchSnapshot(); expect(runStream(FORMATS.OLLAMA, FORMATS.OPENAI, events)).toMatchSnapshot();
}); });
}); });
describe("GOLDEN response stream: OpenAI-Responses (codex) → OpenAI", () => {
it("text + reasoning + tool_call + completed usage", () => {
const events = [
{ type: "response.output_text.delta", delta: "Hello" },
{ type: "response.reasoning_summary_text.delta", delta: "thinking" },
{ type: "response.output_item.added", item: { type: "function_call", call_id: "call_1", name: "get_weather" } },
{ type: "response.function_call_arguments.delta", delta: '{"city":"NYC"}' },
{ type: "response.output_item.done", item: { type: "function_call" } },
{ type: "response.completed", response: { usage: { input_tokens: 10, output_tokens: 5, input_tokens_details: { cached_tokens: 3 } } } },
];
expect(runStream(FORMATS.OPENAI_RESPONSES, FORMATS.OPENAI, events)).toMatchSnapshot();
});
it("error event → error chunk (fallback id/created)", () => {
const events = [
{ type: "error", error: { message: "model_not_found" } },
];
expect(runStream(FORMATS.OPENAI_RESPONSES, FORMATS.OPENAI, events)).toMatchSnapshot();
});
});