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:
11
open-sse/translator/helpers/chunkBuilder.js
Normal file
11
open-sse/translator/helpers/chunkBuilder.js
Normal 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 }],
|
||||
};
|
||||
}
|
||||
@@ -1,19 +1,14 @@
|
||||
import { register } from "../index.js";
|
||||
import { FORMATS } from "../formats.js";
|
||||
import { buildChunk } from "../helpers/chunkBuilder.js";
|
||||
|
||||
// Create OpenAI chunk helper
|
||||
function createChunk(state, delta, finishReason = null) {
|
||||
return {
|
||||
id: `chatcmpl-${state.messageId}`,
|
||||
object: "chat.completion.chunk",
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
model: state.model,
|
||||
choices: [{
|
||||
index: 0,
|
||||
delta,
|
||||
finish_reason: finishReason
|
||||
}]
|
||||
};
|
||||
return buildChunk(
|
||||
{ id: `chatcmpl-${state.messageId}`, created: Math.floor(Date.now() / 1000), model: state.model },
|
||||
delta,
|
||||
finishReason
|
||||
);
|
||||
}
|
||||
|
||||
// Convert Claude stream chunk to OpenAI format
|
||||
@@ -129,13 +124,7 @@ export function claudeToOpenAIResponse(chunk, state) {
|
||||
|
||||
if (chunk.delta?.stop_reason) {
|
||||
state.finishReason = convertStopReason(chunk.delta.stop_reason);
|
||||
const finalChunk = {
|
||||
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 }]
|
||||
};
|
||||
const finalChunk = createChunk(state, {}, state.finishReason);
|
||||
|
||||
if (state.usage) {
|
||||
finalChunk.usage = {
|
||||
@@ -169,18 +158,7 @@ export function claudeToOpenAIResponse(chunk, state) {
|
||||
total_tokens: (state.usage.input_tokens || 0) + (state.usage.output_tokens || 0)
|
||||
}
|
||||
} : {};
|
||||
results.push({
|
||||
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
|
||||
});
|
||||
results.push({ ...createChunk(state, {}, finishReason), ...usageObj });
|
||||
state.finishReasonSent = true;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
import { register } from "../index.js";
|
||||
import { FORMATS } from "../formats.js";
|
||||
import { buildChunk } from "../helpers/chunkBuilder.js";
|
||||
|
||||
function ensureState(state, model) {
|
||||
if (!state.responseId) {
|
||||
@@ -34,13 +35,11 @@ function ensureState(state, model) {
|
||||
}
|
||||
|
||||
function makeChunk(state, delta, finishReason = null) {
|
||||
return {
|
||||
id: state.responseId,
|
||||
object: "chat.completion.chunk",
|
||||
created: state.created,
|
||||
model: state.model,
|
||||
choices: [{ index: 0, delta, finish_reason: finishReason }],
|
||||
};
|
||||
return buildChunk(
|
||||
{ id: state.responseId, created: state.created, model: state.model },
|
||||
delta,
|
||||
finishReason
|
||||
);
|
||||
}
|
||||
|
||||
function mapFinishReason(reason) {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { register } from "../index.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
|
||||
export function geminiToOpenAIResponse(chunk, state) {
|
||||
@@ -18,17 +24,7 @@ export function geminiToOpenAIResponse(chunk, state) {
|
||||
state.messageId = response.responseId || `msg_${Date.now()}`;
|
||||
state.model = response.modelVersion || "gemini";
|
||||
state.functionIndex = 0;
|
||||
results.push({
|
||||
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
|
||||
}]
|
||||
});
|
||||
results.push(buildChunk(chunkMeta(state), { role: "assistant" }, null));
|
||||
}
|
||||
|
||||
// Process parts
|
||||
@@ -43,19 +39,11 @@ export function geminiToOpenAIResponse(chunk, state) {
|
||||
const hasFunctionCall = !!part.functionCall;
|
||||
|
||||
if (hasTextContent) {
|
||||
results.push({
|
||||
id: `chatcmpl-${state.messageId}`,
|
||||
object: "chat.completion.chunk",
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
model: state.model,
|
||||
choices: [{
|
||||
index: 0,
|
||||
delta: isThought
|
||||
? { reasoning_content: part.text }
|
||||
: { content: part.text },
|
||||
finish_reason: null
|
||||
}]
|
||||
});
|
||||
results.push(buildChunk(
|
||||
chunkMeta(state),
|
||||
isThought ? { reasoning_content: part.text } : { content: part.text },
|
||||
null
|
||||
));
|
||||
}
|
||||
|
||||
if (hasFunctionCall) {
|
||||
@@ -77,17 +65,7 @@ export function geminiToOpenAIResponse(chunk, state) {
|
||||
|
||||
state.toolCalls.set(toolCallIndex, toolCall);
|
||||
|
||||
results.push({
|
||||
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
|
||||
}]
|
||||
});
|
||||
results.push(buildChunk(chunkMeta(state), { tool_calls: [toolCall] }, null));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -97,19 +75,11 @@ export function geminiToOpenAIResponse(chunk, state) {
|
||||
// can also stream thought parts without a signature; those must not be
|
||||
// surfaced as normal assistant content in OpenAI-compatible clients.
|
||||
if (part.text !== undefined && part.text !== "") {
|
||||
results.push({
|
||||
id: `chatcmpl-${state.messageId}`,
|
||||
object: "chat.completion.chunk",
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
model: state.model,
|
||||
choices: [{
|
||||
index: 0,
|
||||
delta: isThought
|
||||
? { reasoning_content: part.text }
|
||||
: { content: part.text },
|
||||
finish_reason: null
|
||||
}]
|
||||
});
|
||||
results.push(buildChunk(
|
||||
chunkMeta(state),
|
||||
isThought ? { reasoning_content: part.text } : { content: part.text },
|
||||
null
|
||||
));
|
||||
}
|
||||
|
||||
// Function call
|
||||
@@ -132,39 +102,23 @@ export function geminiToOpenAIResponse(chunk, state) {
|
||||
|
||||
state.toolCalls.set(toolCallIndex, toolCall);
|
||||
|
||||
results.push({
|
||||
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
|
||||
}]
|
||||
});
|
||||
results.push(buildChunk(chunkMeta(state), { tool_calls: [toolCall] }, null));
|
||||
}
|
||||
|
||||
// Inline data (images)
|
||||
const inlineData = part.inlineData || part.inline_data;
|
||||
if (inlineData?.data) {
|
||||
const mimeType = inlineData.mimeType || inlineData.mime_type || "image/png";
|
||||
results.push({
|
||||
id: `chatcmpl-${state.messageId}`,
|
||||
object: "chat.completion.chunk",
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
model: state.model,
|
||||
choices: [{
|
||||
index: 0,
|
||||
delta: {
|
||||
images: [{
|
||||
type: "image_url",
|
||||
image_url: { url: `data:${mimeType};base64,${inlineData.data}` }
|
||||
}]
|
||||
},
|
||||
finish_reason: null
|
||||
}]
|
||||
});
|
||||
results.push(buildChunk(
|
||||
chunkMeta(state),
|
||||
{
|
||||
images: [{
|
||||
type: "image_url",
|
||||
image_url: { url: `data:${mimeType};base64,${inlineData.data}` }
|
||||
}]
|
||||
},
|
||||
null
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,17 +172,7 @@ export function geminiToOpenAIResponse(chunk, state) {
|
||||
finishReason = "tool_calls";
|
||||
}
|
||||
|
||||
const finalChunk = {
|
||||
id: `chatcmpl-${state.messageId}`,
|
||||
object: "chat.completion.chunk",
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
model: state.model,
|
||||
choices: [{
|
||||
index: 0,
|
||||
delta: {},
|
||||
finish_reason: finishReason
|
||||
}]
|
||||
};
|
||||
const finalChunk = buildChunk(chunkMeta(state), {}, finishReason);
|
||||
|
||||
// Include usage in final chunk for downstream translators
|
||||
if (state.usage) {
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
*/
|
||||
import { register } from "../index.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
|
||||
@@ -66,20 +72,10 @@ export function convertKiroToOpenAI(chunk, state) {
|
||||
const content = data.assistantResponseEvent?.content || data.content || "";
|
||||
if (!content) return null;
|
||||
|
||||
const openaiChunk = {
|
||||
id: state.responseId,
|
||||
object: "chat.completion.chunk",
|
||||
created: state.created,
|
||||
model: state.model || "kiro",
|
||||
choices: [{
|
||||
index: 0,
|
||||
delta: {
|
||||
...(state.chunkIndex === 0 ? { role: "assistant" } : {}),
|
||||
content: content
|
||||
},
|
||||
finish_reason: null
|
||||
}]
|
||||
};
|
||||
const openaiChunk = buildChunk(chunkMeta(state), {
|
||||
...(state.chunkIndex === 0 ? { role: "assistant" } : {}),
|
||||
content: content
|
||||
}, null);
|
||||
|
||||
state.chunkIndex++;
|
||||
return openaiChunk;
|
||||
@@ -97,20 +93,10 @@ export function convertKiroToOpenAI(chunk, state) {
|
||||
: (reasoning.text || reasoning.content || data.content || "");
|
||||
if (!content) return null;
|
||||
|
||||
const openaiChunk = {
|
||||
id: state.responseId,
|
||||
object: "chat.completion.chunk",
|
||||
created: state.created,
|
||||
model: state.model || "kiro",
|
||||
choices: [{
|
||||
index: 0,
|
||||
delta: {
|
||||
...(state.chunkIndex === 0 ? { role: "assistant" } : {}),
|
||||
reasoning_content: content
|
||||
},
|
||||
finish_reason: null
|
||||
}]
|
||||
};
|
||||
const openaiChunk = buildChunk(chunkMeta(state), {
|
||||
...(state.chunkIndex === 0 ? { role: "assistant" } : {}),
|
||||
reasoning_content: content
|
||||
}, null);
|
||||
|
||||
state.chunkIndex++;
|
||||
return openaiChunk;
|
||||
@@ -123,28 +109,18 @@ export function convertKiroToOpenAI(chunk, state) {
|
||||
const toolName = toolUse.name || "";
|
||||
const toolInput = toolUse.input || {};
|
||||
|
||||
const openaiChunk = {
|
||||
id: state.responseId,
|
||||
object: "chat.completion.chunk",
|
||||
created: state.created,
|
||||
model: state.model || "kiro",
|
||||
choices: [{
|
||||
const openaiChunk = buildChunk(chunkMeta(state), {
|
||||
...(state.chunkIndex === 0 ? { role: "assistant" } : {}),
|
||||
tool_calls: [{
|
||||
index: 0,
|
||||
delta: {
|
||||
...(state.chunkIndex === 0 ? { role: "assistant" } : {}),
|
||||
tool_calls: [{
|
||||
index: 0,
|
||||
id: toolCallId,
|
||||
type: "function",
|
||||
function: {
|
||||
name: toolName,
|
||||
arguments: JSON.stringify(toolInput)
|
||||
}
|
||||
}]
|
||||
},
|
||||
finish_reason: null
|
||||
id: toolCallId,
|
||||
type: "function",
|
||||
function: {
|
||||
name: toolName,
|
||||
arguments: JSON.stringify(toolInput)
|
||||
}
|
||||
}]
|
||||
};
|
||||
}, null);
|
||||
|
||||
state.chunkIndex++;
|
||||
return openaiChunk;
|
||||
@@ -154,17 +130,7 @@ export function convertKiroToOpenAI(chunk, state) {
|
||||
if (eventType === "messageStopEvent" || eventType === "done" || data.messageStopEvent) {
|
||||
state.finishReason = "stop"; // Mark for usage injection in stream.js
|
||||
|
||||
const openaiChunk = {
|
||||
id: state.responseId,
|
||||
object: "chat.completion.chunk",
|
||||
created: state.created,
|
||||
model: state.model || "kiro",
|
||||
choices: [{
|
||||
index: 0,
|
||||
delta: {},
|
||||
finish_reason: "stop"
|
||||
}]
|
||||
};
|
||||
const openaiChunk = buildChunk(chunkMeta(state), {}, "stop");
|
||||
|
||||
// Include usage in final chunk if available
|
||||
if (state.usage && typeof state.usage === "object") {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { register } from "../index.js";
|
||||
import { FORMATS } from "../formats.js";
|
||||
import { buildChunk } from "../helpers/chunkBuilder.js";
|
||||
|
||||
/**
|
||||
* Convert Ollama NDJSON response to OpenAI SSE format
|
||||
@@ -36,18 +37,9 @@ export function ollamaToOpenAI(chunk, state) {
|
||||
finishReason = "tool_calls";
|
||||
}
|
||||
|
||||
return {
|
||||
id: id,
|
||||
object: "chat.completion.chunk",
|
||||
created: created,
|
||||
model: model,
|
||||
choices: [{
|
||||
index: 0,
|
||||
delta: {},
|
||||
finish_reason: finishReason
|
||||
}],
|
||||
usage: usage
|
||||
};
|
||||
const doneChunk = buildChunk({ id, created, model }, {}, finishReason);
|
||||
doneChunk.usage = usage;
|
||||
return doneChunk;
|
||||
}
|
||||
|
||||
// Content chunk
|
||||
@@ -79,17 +71,7 @@ export function ollamaToOpenAI(chunk, state) {
|
||||
delta.tool_calls = convertToolCalls(toolCalls);
|
||||
}
|
||||
|
||||
return {
|
||||
id: id,
|
||||
object: "chat.completion.chunk",
|
||||
created: created,
|
||||
model: model,
|
||||
choices: [{
|
||||
index: 0,
|
||||
delta: delta,
|
||||
finish_reason: null
|
||||
}]
|
||||
};
|
||||
return buildChunk({ id, created, model }, delta, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
import { register } from "../index.js";
|
||||
import { FORMATS } from "../formats.js";
|
||||
import { buildChunk } from "../helpers/chunkBuilder.js";
|
||||
|
||||
/**
|
||||
* Translate OpenAI chunk to Responses API events
|
||||
@@ -377,17 +378,11 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
|
||||
state.finishReasonSent = true;
|
||||
state.finishReason = finishReason;
|
||||
|
||||
const finalChunk = {
|
||||
id: state.chatId || `chatcmpl-${Date.now()}`,
|
||||
object: "chat.completion.chunk",
|
||||
created: state.created || Math.floor(Date.now() / 1000),
|
||||
model: state.model || "unknown",
|
||||
choices: [{
|
||||
index: 0,
|
||||
delta: {},
|
||||
finish_reason: finishReason
|
||||
}]
|
||||
};
|
||||
const finalChunk = buildChunk(
|
||||
{ id: state.chatId || `chatcmpl-${Date.now()}`, created: state.created || Math.floor(Date.now() / 1000), model: state.model || "unknown" },
|
||||
{},
|
||||
finishReason
|
||||
);
|
||||
|
||||
if (state.usage && typeof state.usage === "object") {
|
||||
finalChunk.usage = state.usage;
|
||||
@@ -414,17 +409,10 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
|
||||
const delta = data.delta || "";
|
||||
if (!delta) return null;
|
||||
|
||||
return {
|
||||
id: state.chatId,
|
||||
object: "chat.completion.chunk",
|
||||
created: state.created,
|
||||
model: state.model || "unknown",
|
||||
choices: [{
|
||||
index: 0,
|
||||
delta: { content: delta },
|
||||
finish_reason: null
|
||||
}]
|
||||
};
|
||||
return buildChunk(
|
||||
{ id: state.chatId, created: state.created, model: state.model || "unknown" },
|
||||
{ content: delta }
|
||||
);
|
||||
}
|
||||
|
||||
// Text content done (ignore, we handle via delta)
|
||||
@@ -437,27 +425,17 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
|
||||
const item = data.item;
|
||||
state.currentToolCallId = item.call_id || `call_${Date.now()}`;
|
||||
|
||||
return {
|
||||
id: state.chatId,
|
||||
object: "chat.completion.chunk",
|
||||
created: state.created,
|
||||
model: state.model || "unknown",
|
||||
choices: [{
|
||||
index: 0,
|
||||
delta: {
|
||||
tool_calls: [{
|
||||
index: state.toolCallIndex,
|
||||
id: state.currentToolCallId,
|
||||
type: "function",
|
||||
function: {
|
||||
name: item.name || "",
|
||||
arguments: ""
|
||||
}
|
||||
}]
|
||||
},
|
||||
finish_reason: null
|
||||
}]
|
||||
};
|
||||
return buildChunk(
|
||||
{ id: state.chatId, created: state.created, model: state.model || "unknown" },
|
||||
{
|
||||
tool_calls: [{
|
||||
index: state.toolCallIndex,
|
||||
id: state.currentToolCallId,
|
||||
type: "function",
|
||||
function: { name: item.name || "", arguments: "" }
|
||||
}]
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Function call arguments delta (standard or custom_tool_call variant)
|
||||
@@ -465,22 +443,10 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
|
||||
const argsDelta = data.delta || "";
|
||||
if (!argsDelta) return null;
|
||||
|
||||
return {
|
||||
id: state.chatId,
|
||||
object: "chat.completion.chunk",
|
||||
created: state.created,
|
||||
model: state.model || "unknown",
|
||||
choices: [{
|
||||
index: 0,
|
||||
delta: {
|
||||
tool_calls: [{
|
||||
index: state.toolCallIndex,
|
||||
function: { arguments: argsDelta }
|
||||
}]
|
||||
},
|
||||
finish_reason: null
|
||||
}]
|
||||
};
|
||||
return buildChunk(
|
||||
{ id: state.chatId, created: state.created, model: state.model || "unknown" },
|
||||
{ tool_calls: [{ index: state.toolCallIndex, function: { arguments: argsDelta } }] }
|
||||
);
|
||||
}
|
||||
|
||||
// Function call done (standard or custom_tool_call variant)
|
||||
@@ -520,18 +486,12 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
|
||||
state.finishReasonSent = true;
|
||||
state.finishReason = finishReason; // Mark for usage injection in stream.js
|
||||
|
||||
const finalChunk = {
|
||||
id: state.chatId,
|
||||
object: "chat.completion.chunk",
|
||||
created: state.created,
|
||||
model: state.model || "unknown",
|
||||
choices: [{
|
||||
index: 0,
|
||||
delta: {},
|
||||
finish_reason: finishReason
|
||||
}]
|
||||
};
|
||||
|
||||
const finalChunk = buildChunk(
|
||||
{ id: state.chatId, created: state.created, model: state.model || "unknown" },
|
||||
{},
|
||||
finishReason
|
||||
);
|
||||
|
||||
// Include usage in final chunk if available
|
||||
if (state.usage && typeof state.usage === "object") {
|
||||
finalChunk.usage = state.usage;
|
||||
@@ -553,17 +513,11 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
|
||||
state.finishReasonSent = true;
|
||||
|
||||
// Surface the error as an OpenAI-compatible error chunk
|
||||
return {
|
||||
id: state.chatId || `chatcmpl-${Date.now()}`,
|
||||
object: "chat.completion.chunk",
|
||||
created: state.created || Math.floor(Date.now() / 1000),
|
||||
model: state.model || "unknown",
|
||||
choices: [{
|
||||
index: 0,
|
||||
delta: { content: `[Error] ${error.message || JSON.stringify(error)}` },
|
||||
finish_reason: "stop"
|
||||
}]
|
||||
};
|
||||
return buildChunk(
|
||||
{ id: state.chatId || `chatcmpl-${Date.now()}`, created: state.created || Math.floor(Date.now() / 1000), model: state.model || "unknown" },
|
||||
{ content: `[Error] ${error.message || JSON.stringify(error)}` },
|
||||
"stop"
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -572,13 +526,10 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
|
||||
if (eventType === "response.reasoning_summary_text.delta") {
|
||||
const delta = data.delta || "";
|
||||
if (!delta) return null;
|
||||
return {
|
||||
id: state.chatId,
|
||||
object: "chat.completion.chunk",
|
||||
created: state.created,
|
||||
model: state.model || "unknown",
|
||||
choices: [{ index: 0, delta: { reasoning_content: delta }, finish_reason: null }]
|
||||
};
|
||||
return buildChunk(
|
||||
{ id: state.chatId, created: state.created, model: state.model || "unknown" },
|
||||
{ reasoning_content: delta }
|
||||
);
|
||||
}
|
||||
|
||||
// Ignore other events
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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,
|
||||
},
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -95,3 +95,24 @@ describe("GOLDEN response stream: Ollama → OpenAI", () => {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user