fix: handle thinking model reasoning-only responses as valid content

Thinking models (Kimi K2.5, DeepSeek-R1, QwQ) may produce only
reasoning_content with no regular text content or tool calls. Previously
this was treated as an empty/failed response, triggering the "language
model did not provide any assistant messages" error and unnecessary
retries.

Changes:
- Task.ts: Include reasoningMessage in the content check so
  reasoning-only responses are recognized as valid model output
  instead of triggering the empty response error path
- openai.ts: Handle both "reasoning_content" (standard) and
  "reasoning" (Ollama /v1/) field names in streaming responses
- openai.ts: Yield reasoning content from non-streaming responses

Fixes #10603
Related: #9959, #10064, #9551

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
neorrk 2026-02-16 09:12:02 +01:00
parent 6ef149d567
commit 61778d2ad9
2 changed files with 27 additions and 4 deletions

View file

@ -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 || "",

View file

@ -3401,14 +3401,19 @@ export class Task extends EventEmitter<TaskEvents> 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