fix(translator): stop emitting empty <think> markers into OpenAI content

This commit is contained in:
MrBeanDev
2026-09-26 11:31:54 +07:00
parent 3e4323e2fd
commit 5d2cfbf3c5
5 changed files with 185 additions and 38 deletions

View File

@@ -333,6 +333,9 @@ export function createResponsesApiTransformStream(logger = null) {
// Regular text content
if (content) {
// The answer starts, so thinking is over. Upstreams that send reasoning via
// reasoning_content never emit "</think>", so close it here rather than at finish.
closeReasoning(controller);
if (!state.msgItemAdded[idx]) {
state.msgItemAdded[idx] = true;
const msgId = `msg_${state.responseId}_${idx}`;
@@ -372,6 +375,7 @@ export function createResponsesApiTransformStream(logger = null) {
// Handle tool_calls
if (delta.tool_calls) {
closeReasoning(controller);
closeMessage(controller, idx);
for (const tc of delta.tool_calls) {

View File

@@ -59,10 +59,6 @@ export function claudeToOpenAIResponse(chunk, state) {
}
if (block?.type === CLAUDE_BLOCK.TEXT) {
state.textBlockStarted = true;
} else if (block?.type === CLAUDE_BLOCK.THINKING) {
state.inThinkingBlock = true;
state.currentBlockIndex = chunk.index;
results.push(createChunk(state, { content: "<think>" }));
} else if (block?.type === CLAUDE_BLOCK.TOOL_USE) {
const toolCallIndex = state.toolCallIndex++;
// Restore original tool name from mapping (Claude OAuth)
@@ -89,6 +85,8 @@ export function claudeToOpenAIResponse(chunk, state) {
if (delta?.type === "text_delta" && delta.text) {
results.push(createChunk(state, { content: delta.text }));
} else if (delta?.type === "thinking_delta" && delta.thinking) {
// Thinking travels only in reasoning_content. No "<think>" markers in
// content: OpenAI-format clients render them as literal text.
results.push(createChunk(state, reasoningDelta(delta.thinking)));
} else if (delta?.type === "input_json_delta" && delta.partial_json) {
const toolCall = state.toolCalls.get(chunk.index);
@@ -112,10 +110,6 @@ export function claudeToOpenAIResponse(chunk, state) {
state.serverToolBlockIndex = -1;
break;
}
if (state.inThinkingBlock && chunk.index === state.currentBlockIndex) {
results.push(createChunk(state, { content: "</think>" }));
state.inThinkingBlock = false;
}
state.textBlockStarted = false;
state.thinkingBlockStarted = false;
break;

View File

@@ -129,12 +129,16 @@ export function openaiToOpenAIResponsesResponse(chunk, state) {
}
if (content) {
// The answer starts, so thinking is over. Upstreams that send reasoning via
// reasoning_content never emit "</think>", so close it here rather than at finish.
closeReasoning(state, emit);
emitTextContent(state, emit, idx, content);
}
}
// Handle tool_calls (empty array is truthy; require a real call)
if (delta.tool_calls && delta.tool_calls.length) {
closeReasoning(state, emit);
closeMessage(state, emit, idx);
for (const tc of delta.tool_calls) {
emitToolCall(state, emit, tc);