diff --git a/src/services/api/openai/convertMessages.ts b/src/services/api/openai/convertMessages.ts index 1dbf1791f..8a95b7a85 100644 --- a/src/services/api/openai/convertMessages.ts +++ b/src/services/api/openai/convertMessages.ts @@ -250,9 +250,13 @@ function convertInternalAssistantMessage( }, }) } else if (block.type === 'thinking' && preserveReasoning) { - // DeepSeek thinking mode: preserve reasoning_content for tool call iterations + // DeepSeek thinking mode: always preserve reasoning_content, + // including the empty-string case. DeepSeek v4 may return + // reasoning_content: "" when the model answers directly, and the + // empty value must be echoed back in the next request — otherwise + // DeepSeek returns 400 ("reasoning_content ... must be passed back"). const thinkingText = (block as Record).thinking - if (typeof thinkingText === 'string' && thinkingText) { + if (typeof thinkingText === 'string') { reasoningParts.push(thinkingText) } } diff --git a/src/services/api/openai/streamAdapter.ts b/src/services/api/openai/streamAdapter.ts index 2b248a1d6..c8c60255e 100644 --- a/src/services/api/openai/streamAdapter.ts +++ b/src/services/api/openai/streamAdapter.ts @@ -90,10 +90,13 @@ export async function* adaptOpenAIStreamToAnthropic( if (!delta) continue - // Handle reasoning_content → Anthropic thinking block - // DeepSeek and compatible providers send delta.reasoning_content + // Handle reasoning_content → Anthropic thinking block. + // Empty string is a valid signal: DeepSeek v4 thinking mode sometimes + // returns reasoning_content: "" when the model answers directly. The + // empty thinking block must round-trip back to the API in subsequent + // requests, otherwise DeepSeek rejects with 400. const reasoningContent = (delta as any).reasoning_content - if (reasoningContent != null && reasoningContent !== '') { + if (reasoningContent != null) { if (!thinkingBlockOpen) { currentContentIndex++ thinkingBlockOpen = true @@ -110,14 +113,16 @@ export async function* adaptOpenAIStreamToAnthropic( } as BetaRawMessageStreamEvent } - yield { - type: 'content_block_delta', - index: currentContentIndex, - delta: { - type: 'thinking_delta', - thinking: reasoningContent, - }, - } as BetaRawMessageStreamEvent + if (reasoningContent !== '') { + yield { + type: 'content_block_delta', + index: currentContentIndex, + delta: { + type: 'thinking_delta', + thinking: reasoningContent, + }, + } as BetaRawMessageStreamEvent + } } // Handle text content