diff --git a/open-sse/translator/response/openai-to-claude.ts b/open-sse/translator/response/openai-to-claude.ts index ffe1887068..15b481da74 100644 --- a/open-sse/translator/response/openai-to-claude.ts +++ b/open-sse/translator/response/openai-to-claude.ts @@ -191,7 +191,11 @@ export function openaiToClaudeResponse(chunk, state) { } if (parts.length > 0) reasoningContent = parts.join(""); } - if (reasoningContent && !isInternalReasoningPlaceholder(reasoningContent)) { + if ( + typeof reasoningContent === "string" && + reasoningContent !== "" && + !isInternalReasoningPlaceholder(reasoningContent) + ) { stopTextBlock(state, results); if (!state.thinkingBlockStarted) { diff --git a/tests/unit/translator-openai-to-claude-empty-reasoning.test.ts b/tests/unit/translator-openai-to-claude-empty-reasoning.test.ts new file mode 100644 index 0000000000..95bc262fd4 --- /dev/null +++ b/tests/unit/translator-openai-to-claude-empty-reasoning.test.ts @@ -0,0 +1,30 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { openaiToClaudeResponse } from "../../open-sse/translator/response/openai-to-claude.ts"; + +test("openai-to-claude ignores zero-length reasoning_content empty string deltas", () => { + const state = { + messageStartSent: true, + thinkingBlockStarted: false, + thinkingBlockIndex: -1, + textBlockStarted: false, + textBlockIndex: -1, + textBlockClosed: false, + nextBlockIndex: 0, + _pendingXmlToolCalls: [], + }; + + const chunk = { + choices: [ + { + delta: { + reasoning_content: "", + }, + }, + ], + }; + + const results = openaiToClaudeResponse(chunk, state) || []; + assert.equal(results.length, 0); + assert.equal(state.thinkingBlockStarted, false); +});