diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index 33b29abcaf..a4ccd47f66 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -199,10 +199,16 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl } } - if ("reasoning_content" in delta && delta.reasoning_content) { + // Handle reasoning/thinking tokens from various providers: + // - reasoning_content: DeepSeek, OpenAI (standard) + // - reasoning: Ollama /v1/ (non-standard, used by Kimi K2.5, etc.) + const reasoningText = + ("reasoning_content" in delta && (delta as any).reasoning_content) || + ("reasoning" in delta && (delta as any).reasoning) + if (reasoningText) { yield { type: "reasoning", - text: (delta.reasoning_content as string | undefined) || "", + text: String(reasoningText), } } @@ -260,6 +266,18 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl } } + // Yield reasoning content from non-streaming responses + // Thinking models (Kimi K2.5, DeepSeek-R1) may return reasoning + // in the message alongside (or instead of) regular content + const messageAny = message as any + const nonStreamReasoning = messageAny?.reasoning_content || messageAny?.reasoning + if (nonStreamReasoning) { + yield { + type: "reasoning", + text: String(nonStreamReasoning), + } + } + yield { type: "text", text: message?.content || "", diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 3feb695e10..b24d3ffe1b 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -3401,14 +3401,19 @@ export class Task extends EventEmitter implements TaskLike { // the assistant message is already in history. Otherwise, tool_result blocks would appear // BEFORE their corresponding tool_use blocks, causing API errors. - // Check if we have any content to process (text or tool uses) + // Check if we have any content to process (text, reasoning, or tool uses) + // Thinking models (e.g., Kimi K2, DeepSeek-R1, QwQ) may produce only + // reasoning_content with no regular text content. This should not be + // treated as an empty/failed response — the model did respond, just + // entirely in reasoning tokens. const hasTextContent = assistantMessage.length > 0 + const hasReasoningContent = reasoningMessage.length > 0 const hasToolUses = this.assistantMessageContent.some( (block) => block.type === "tool_use" || block.type === "mcp_tool_use", ) - if (hasTextContent || hasToolUses) { + if (hasTextContent || hasToolUses || hasReasoningContent) { // Reset counter when we get a successful response with content this.consecutiveNoAssistantMessagesCount = 0 // Display grounding sources to the user if they exist