fix: correct finish_reason for tool calls in OpenAI Responses translator
Apply fix from PR #354 by @tannk4w to properly signal tool_calls finish_reason when model emits tool calls, allowing OpenAI-compatible clients to continue with tool result processing instead of stopping prematurely. Refactored finish_reason logic into computeFinishReason() helper to eliminate duplication and improve maintainability across flush and completion paths. Co-authored-by: tannk4w <tannk@tmi-soft.vn> Thanks to @tannk4w, @trungtq2799, @quanhavn, and @East-rayyy for the thorough review and improvement suggestions on the original PR. Made-with: Cursor
This commit is contained in:
@@ -355,6 +355,14 @@ function flushEvents(state) {
|
|||||||
return events;
|
return events;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// currentToolCallId is intentionally sticky for the current turn so flush/completion
|
||||||
|
// can still finalize as tool_calls even if the tool call was emitted before stream end.
|
||||||
|
function computeFinishReason(state) {
|
||||||
|
return state.toolCallIndex > 0 || state.currentToolCallId
|
||||||
|
? "tool_calls"
|
||||||
|
: "stop";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translate OpenAI Responses API chunk to OpenAI Chat Completions format
|
* Translate OpenAI Responses API chunk to OpenAI Chat Completions format
|
||||||
* This is for when Codex returns data and we need to send it to an OpenAI-compatible client
|
* This is for when Codex returns data and we need to send it to an OpenAI-compatible client
|
||||||
@@ -362,22 +370,30 @@ function flushEvents(state) {
|
|||||||
export function openaiResponsesToOpenAIResponse(chunk, state) {
|
export function openaiResponsesToOpenAIResponse(chunk, state) {
|
||||||
if (!chunk) {
|
if (!chunk) {
|
||||||
// Flush: send final chunk with finish_reason
|
// Flush: send final chunk with finish_reason
|
||||||
if (!state.finishReasonSent && state.started) {
|
if (state.finishReasonSent || !state.started) return null;
|
||||||
state.finishReasonSent = true;
|
|
||||||
const hasToolCalls = state.toolCallIndex > 0;
|
const finishReason = computeFinishReason(state);
|
||||||
return {
|
|
||||||
id: state.chatId || `chatcmpl-${Date.now()}`,
|
state.finishReasonSent = true;
|
||||||
object: "chat.completion.chunk",
|
state.finishReason = finishReason;
|
||||||
created: state.created || Math.floor(Date.now() / 1000),
|
|
||||||
model: state.model || "unknown",
|
const finalChunk = {
|
||||||
choices: [{
|
id: state.chatId || `chatcmpl-${Date.now()}`,
|
||||||
index: 0,
|
object: "chat.completion.chunk",
|
||||||
delta: {},
|
created: state.created || Math.floor(Date.now() / 1000),
|
||||||
finish_reason: hasToolCalls ? "tool_calls" : "stop"
|
model: state.model || "unknown",
|
||||||
}]
|
choices: [{
|
||||||
};
|
index: 0,
|
||||||
|
delta: {},
|
||||||
|
finish_reason: finishReason
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (state.usage && typeof state.usage === "object") {
|
||||||
|
finalChunk.usage = state.usage;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
return finalChunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle different event types from Responses API
|
// Handle different event types from Responses API
|
||||||
@@ -505,10 +521,10 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!state.finishReasonSent) {
|
if (!state.finishReasonSent) {
|
||||||
|
const finishReason = computeFinishReason(state);
|
||||||
|
|
||||||
state.finishReasonSent = true;
|
state.finishReasonSent = true;
|
||||||
const hasToolCalls = state.toolCallIndex > 0;
|
state.finishReason = finishReason; // Mark for usage injection in stream.js
|
||||||
const resolvedFinishReason = hasToolCalls ? "tool_calls" : "stop";
|
|
||||||
state.finishReason = resolvedFinishReason; // Mark for usage injection in stream.js
|
|
||||||
|
|
||||||
const finalChunk = {
|
const finalChunk = {
|
||||||
id: state.chatId,
|
id: state.chatId,
|
||||||
@@ -518,7 +534,7 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
|
|||||||
choices: [{
|
choices: [{
|
||||||
index: 0,
|
index: 0,
|
||||||
delta: {},
|
delta: {},
|
||||||
finish_reason: resolvedFinishReason
|
finish_reason: finishReason
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -571,4 +587,3 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
|
|||||||
// Register both directions
|
// Register both directions
|
||||||
register(FORMATS.OPENAI, FORMATS.OPENAI_RESPONSES, null, openaiToOpenAIResponsesResponse);
|
register(FORMATS.OPENAI, FORMATS.OPENAI_RESPONSES, null, openaiToOpenAIResponsesResponse);
|
||||||
register(FORMATS.OPENAI_RESPONSES, FORMATS.OPENAI, null, openaiResponsesToOpenAIResponse);
|
register(FORMATS.OPENAI_RESPONSES, FORMATS.OPENAI, null, openaiResponsesToOpenAIResponse);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user