Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/api/providers/moonshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class MoonshotHandler extends OpenAiHandler {
}
}

// Override to always include max_tokens for Moonshot (not max_completion_tokens)
// Override to add Kimi-specific thinking parameter format
protected override addMaxTokensIfNeeded(
requestOptions:
| OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming
Expand All @@ -47,5 +47,13 @@ export class MoonshotHandler extends OpenAiHandler {
): void {
// Moonshot uses max_tokens instead of max_completion_tokens
requestOptions.max_tokens = this.options.modelMaxTokens || modelInfo.maxTokens

// For Kimi models with reasoning budget, use { type: "enabled" } instead of { max_tokens: ... }
const { info: model } = this.getModel()
if (this.options.enableReasoningEffort && (model as any).supportsReasoningBudget) {
// Remove the OpenAI-style reasoning parameter and use Kimi's thinking parameter
delete (requestOptions as any).reasoning
;(requestOptions as any).thinking = { type: "enabled" }
}
}
}
14 changes: 13 additions & 1 deletion src/api/providers/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl

let lastUsage
const activeToolCallIds = new Set<string>()
// Track reasoning content for Kimi/DeepSeek to associate with tool calls
let pendingReasoningContent: string | undefined

for await (const chunk of stream) {
const delta = chunk.choices?.[0]?.delta ?? {}
Expand All @@ -212,14 +214,16 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
? delta.reasoning
: undefined
if (reasoningText) {
pendingReasoningContent = reasoningText
yield {
type: "reasoning",
text: reasoningText,
}
}
// kilocode_change end

yield* this.processToolCalls(delta, finishReason, activeToolCallIds)
yield* this.processToolCalls(delta, finishReason, activeToolCallIds, pendingReasoningContent)
pendingReasoningContent = undefined

if (chunk.usage) {
lastUsage = chunk.usage
Expand Down Expand Up @@ -269,6 +273,12 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
text: message.reasoning,
}
}
if ("reasoning_content" in message && typeof message.reasoning_content === "string") {
yield {
type: "reasoning",
text: message.reasoning_content,
}
}
if (message.content) {
yield {
type: "text",
Expand Down Expand Up @@ -497,11 +507,13 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
* @param delta - The delta object from the stream chunk
* @param finishReason - The finish_reason from the stream chunk
* @param activeToolCallIds - Set to track active tool call IDs (mutated in place)
* @param reasoningContent - Optional reasoning content to include with tool calls (for Kimi/DeepSeek)
*/
private *processToolCalls(
delta: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta | undefined,
finishReason: string | null | undefined,
activeToolCallIds: Set<string>,
reasoningContent?: string,
): Generator<
| { type: "tool_call_partial"; index: number; id?: string; name?: string; arguments?: string }
| { type: "tool_call_end"; id: string }
Expand Down
9 changes: 9 additions & 0 deletions src/api/transform/openai-format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,10 @@ export function convertToOpenAiMessages(
if (mapped) {
;(baseMessage as any).reasoning_details = mapped
}
// Preserve reasoning_content for Kimi/DeepSeek interleaved thinking
if (messageWithDetails.reasoning_content) {
;(baseMessage as any).reasoning_content = messageWithDetails.reasoning_content
}
}

openAiMessages.push(baseMessage)
Expand Down Expand Up @@ -502,6 +506,11 @@ export function convertToOpenAiMessages(
baseMessage.reasoning_details = mapped
}

// Preserve reasoning_content for Kimi/DeepSeek interleaved thinking
if (messageWithDetails.reasoning_content) {
;(baseMessage as any).reasoning_content = messageWithDetails.reasoning_content
}

// Add tool_calls after reasoning_details
// Cannot be an empty array. API expects an array with minimum length 1, and will respond with an error if it's empty
if (tool_calls.length > 0) {
Expand Down
58 changes: 46 additions & 12 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,17 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
} else if (!messageWithTs.content) {
messageWithTs.content = [reasoningBlock]
}

// For Kimi (Moonshot), add reasoning_content as top-level property when there are tool_use blocks
// This is required because Kimi's API expects: "reasoning_content" + "tool_calls" in the same message
if (
this.apiConfiguration.apiProvider === "moonshot" &&
reasoning &&
Array.isArray(messageWithTs.content) &&
messageWithTs.content.some((block: any) => block.type === "tool_use")
) {
messageWithTs.reasoning_content = reasoning
}
} else if (reasoningData?.encrypted_content) {
// OpenAI Native encrypted reasoning
const reasoningBlock = {
Expand Down Expand Up @@ -4847,11 +4858,19 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
}

// Create message with reasoning_details property
cleanConversationHistory.push({
const msgWithReasoningContent: Anthropic.Messages.MessageParam & {
reasoning_content?: string
reasoning_details?: any
} = {
role: "assistant",
content: assistantContent,
reasoning_details: msgWithDetails.reasoning_details,
} as any)
}
// Preserve reasoning_content for Kimi (Moonshot)
if (msg.reasoning_content) {
msgWithReasoningContent.reasoning_content = msg.reasoning_content
}
cleanConversationHistory.push(msgWithReasoningContent)

continue
}
Expand Down Expand Up @@ -4884,10 +4903,15 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
assistantContent = rest
}

cleanConversationHistory.push({
role: "assistant",
content: assistantContent,
} satisfies Anthropic.Messages.MessageParam)
const msgWithEncryptedReasoning: Anthropic.Messages.MessageParam & { reasoning_content?: string } =
{
role: "assistant",
content: assistantContent,
}
if (msg.reasoning_content) {
msgWithEncryptedReasoning.reasoning_content = msg.reasoning_content
}
cleanConversationHistory.push(msgWithEncryptedReasoning)

continue
} else if (hasPlainTextReasoning) {
Expand All @@ -4911,21 +4935,31 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
}
}

cleanConversationHistory.push({
role: "assistant",
content: assistantContent,
} satisfies Anthropic.Messages.MessageParam)
const msgWithPlainTextReasoning: Anthropic.Messages.MessageParam & { reasoning_content?: string } =
{
role: "assistant",
content: assistantContent,
}
if (msg.reasoning_content) {
msgWithPlainTextReasoning.reasoning_content = msg.reasoning_content
}
cleanConversationHistory.push(msgWithPlainTextReasoning)

continue
}
}

// Default path for regular messages (no embedded reasoning)
if (msg.role) {
cleanConversationHistory.push({
const baseMessage: Anthropic.Messages.MessageParam & { reasoning_content?: string } = {
role: msg.role,
content: msg.content as Anthropic.Messages.ContentBlockParam[] | string,
})
}
// Preserve reasoning_content for Kimi (Moonshot) when present
if (msg.reasoning_content) {
baseMessage.reasoning_content = msg.reasoning_content
}
cleanConversationHistory.push(baseMessage)
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/integrations/editor/DiffViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import stripBom from "strip-bom"
import { XMLBuilder } from "fast-xml-parser"
import delay from "delay"

import { type ClineSayTool, DEFAULT_WRITE_DELAY_MS, isNativeProtocol } from "@roo-code/types"

import { createDirectoriesForFile } from "../../utils/fs"
import { arePathsEqual, getReadablePath } from "../../utils/path"
import { formatResponse } from "../../core/prompts/responses"
import { diagnosticsToProblemsString, getNewDiagnostics } from "../diagnostics"
import { ClineSayTool } from "../../shared/ExtensionMessage"
import { Task } from "../../core/task/Task"
import { DEFAULT_WRITE_DELAY_MS, isNativeProtocol } from "@roo-code/types"
import { resolveToolProtocol } from "../../utils/resolveToolProtocol"

import { DecorationController } from "./DecorationController"
Expand Down