Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
be159af
Fixing error on Kimi K2.5 thinking
Neonsy Jan 30, 2026
673aed1
Document fix for Kimi K2.5 error
Neonsy Jan 30, 2026
52ad122
Attempt on improving types
Neonsy Jan 30, 2026
85cf7d7
final chore cleanup
Neonsy Jan 30, 2026
2561b3f
Merge branch 'main' into fix/kimi-k2-5_thinking-fix
Neonsy Jan 30, 2026
c6ddfba
Merge branch 'main' into fix/kimi-k2-5_thinking-fix
Neonsy Jan 30, 2026
27d9e5e
Merge branch 'main' into fix/kimi-k2-5_thinking-fix
Neonsy Jan 31, 2026
bc7f932
Merge branch 'Kilo-Org:main' into fix/kimi-k2-5_thinking-fix
Neonsy Jan 31, 2026
efe9cef
fix pnpm install:vsix for windows
Neonsy Jan 31, 2026
d901121
Merge branch 'main' into fix/kimi-k2-5_thinking-fix
Neonsy Feb 2, 2026
042d403
Fixed jetbrain check failure due to main merge
Neonsy Feb 2, 2026
baf4fc7
Merge branch 'main' into fix/kimi-k2-5_thinking-fix
Neonsy Feb 2, 2026
a5a89eb
Merge branch 'main' into fix/kimi-k2-5_thinking-fix
Neonsy Feb 3, 2026
d106d70
Merge branch 'main' into fix/kimi-k2-5_thinking-fix
Neonsy Feb 3, 2026
1d2e2dd
Merge branch 'main' into fix/kimi-k2-5_thinking-fix
Neonsy Feb 3, 2026
700651c
Added fix to make Kimi K2.5 thinking work for the other Moonshot prov…
Neonsy Feb 3, 2026
c5915c0
Fix type error
Neonsy Feb 3, 2026
6afeefe
Fix test error
Neonsy Feb 4, 2026
296084c
Merge branch 'main' into fix/kimi-k2-5_thinking-fix
Neonsy Feb 4, 2026
ff1574a
Merge branch 'main' into fix/kimi-k2-5_thinking-fix
Neonsy Feb 4, 2026
6f9496f
Removed NPX as requested
Neonsy Feb 4, 2026
0fd94d9
Fix follow up eslint error
Neonsy Feb 4, 2026
b4054e8
Merge branch 'main' into fix/kimi-k2-5_thinking-fix
Neonsy Feb 4, 2026
4bf6ca0
Merge branch 'main' into fix/kimi-k2-5_thinking-fix
Neonsy Feb 5, 2026
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
5 changes: 5 additions & 0 deletions .changeset/rare-stingrays-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---

Fixing error on Kimi K2.5 thinking
214 changes: 214 additions & 0 deletions src/api/transform/__tests__/openai-format.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ import {
} from "../openai-format"
import { normalizeMistralToolCallId } from "../mistral-format"

// Helper type for test assertions with reasoning_content
type AssistantMessageWithReasoning = {
role: string
content: string
reasoning_content?: string
tool_calls?: Array<{ id: string; function: { name: string } }>
}

describe("convertToOpenAiMessages", () => {
it("should convert simple text messages", () => {
const anthropicMessages: Anthropic.Messages.MessageParam[] = [
Expand Down Expand Up @@ -967,6 +975,212 @@ describe("convertToOpenAiMessages", () => {
expect(assistantMessage.reasoning_details[2].data).toBe("encrypted_data")
})
})

describe("reasoning_content extraction for Kimi K2.5 / DeepSeek", () => {
it("should extract reasoning blocks and add as reasoning_content for Kimi K2.5 compatibility", () => {
// This test simulates the stored format after receiving from Moonshot API
// The reasoning is stored as a content block with type: "reasoning"
const anthropicMessages = [
{
role: "assistant" as const,
content: [
{ type: "reasoning", text: "Let me analyze this step by step..." } as const,
{ type: "text", text: "I'll help you with that." },
{ type: "tool_use", id: "tool_123", name: "read_file", input: { path: "test.ts" } },
],
},
] as Anthropic.Messages.MessageParam[]

const openAiMessages = convertToOpenAiMessages(anthropicMessages)
expect(openAiMessages).toHaveLength(1)

const assistantMessage = openAiMessages[0] as AssistantMessageWithReasoning
expect(assistantMessage.role).toBe("assistant")
// reasoning_content should be extracted from the reasoning block
expect(assistantMessage.reasoning_content).toBe("Let me analyze this step by step...")
// Content should contain only text blocks (not reasoning)
expect(assistantMessage.content).toBe("I'll help you with that.")
// Tool calls should be preserved
expect(assistantMessage.tool_calls).toBeDefined()
expect(assistantMessage.tool_calls).toHaveLength(1)
expect(assistantMessage.tool_calls?.[0]?.id).toBe("tool_123")
expect(assistantMessage.tool_calls?.[0]?.function.name).toBe("read_file")
})

it("should extract thinking blocks and add as reasoning_content", () => {
// Some providers use type: "thinking" instead of "reasoning"
const anthropicMessages = [
{
role: "assistant" as const,
content: [
{ type: "thinking", thinking: "This is my thinking process..." } as const,
{ type: "text", text: "Here's the answer." },
],
},
] as Anthropic.Messages.MessageParam[]

const openAiMessages = convertToOpenAiMessages(anthropicMessages)
const assistantMessage = openAiMessages[0] as AssistantMessageWithReasoning

// reasoning_content should be extracted from the thinking block
expect(assistantMessage.reasoning_content).toBe("This is my thinking process...")
expect(assistantMessage.content).toBe("Here's the answer.")
})

it("should concatenate multiple reasoning blocks", () => {
// Multiple reasoning blocks should be concatenated with newlines
const anthropicMessages = [
{
role: "assistant" as const,
content: [
{ type: "reasoning", text: "First thought..." } as const,
{ type: "reasoning", text: "Second thought..." } as const,
{ type: "text", text: "Final answer." },
],
},
] as Anthropic.Messages.MessageParam[]

const openAiMessages = convertToOpenAiMessages(anthropicMessages)
const assistantMessage = openAiMessages[0] as AssistantMessageWithReasoning

// reasoning_content should concatenate all reasoning blocks
expect(assistantMessage.reasoning_content).toBe("First thought...\nSecond thought...")
expect(assistantMessage.content).toBe("Final answer.")
})

it("should handle assistant message with only reasoning (no tool_calls)", () => {
const anthropicMessages = [
{
role: "assistant" as const,
content: [
{ type: "reasoning", text: "Just reasoning, no tools." } as const,
{ type: "text", text: "Response." },
],
},
] as Anthropic.Messages.MessageParam[]

const openAiMessages = convertToOpenAiMessages(anthropicMessages)
const assistantMessage = openAiMessages[0] as AssistantMessageWithReasoning

expect(assistantMessage.reasoning_content).toBe("Just reasoning, no tools.")
expect(assistantMessage.content).toBe("Response.")
// Should not have tool_calls
expect(assistantMessage.tool_calls).toBeUndefined()
})

it("should handle assistant message with no reasoning", () => {
const anthropicMessages: Anthropic.Messages.MessageParam[] = [
{
role: "assistant",
content: [
{ type: "text", text: "Simple response." },
{ type: "tool_use", id: "tool_456", name: "write_file", input: { path: "test.ts" } },
],
},
]

const openAiMessages = convertToOpenAiMessages(anthropicMessages)
const assistantMessage = openAiMessages[0] as AssistantMessageWithReasoning

// Should not have reasoning_content when no reasoning blocks exist
expect(assistantMessage.reasoning_content).toBeUndefined()
expect(assistantMessage.content).toBe("Simple response.")
expect(assistantMessage.tool_calls).toHaveLength(1)
})

it("should preserve reasoning_content when already present at message level (string content)", () => {
// This handles cases where reasoning_content is already at message level
// (e.g., when messages are converted and then re-converted)
const anthropicMessages = [
{
role: "assistant" as const,
content: "Here's the answer.",
reasoning_content: "This is the reasoning.",
},
] as unknown as Anthropic.Messages.MessageParam[]

const openAiMessages = convertToOpenAiMessages(anthropicMessages)
const assistantMessage = openAiMessages[0] as AssistantMessageWithReasoning

// reasoning_content should be preserved at message level
expect(assistantMessage.reasoning_content).toBe("This is the reasoning.")
expect(assistantMessage.content).toBe("Here's the answer.")
})

it("should preserve reasoning field at message level as reasoning_content", () => {
// Some providers use "reasoning" instead of "reasoning_content"
const anthropicMessages = [
{
role: "assistant" as const,
content: "Response text.",
reasoning: "This is the reasoning.",
},
] as unknown as Anthropic.Messages.MessageParam[]

const openAiMessages = convertToOpenAiMessages(anthropicMessages)
const assistantMessage = openAiMessages[0] as AssistantMessageWithReasoning

// reasoning should be converted to reasoning_content
expect(assistantMessage.reasoning_content).toBe("This is the reasoning.")
expect(assistantMessage.content).toBe("Response text.")
})

it("should handle both reasoning blocks and reasoning_details", () => {
// Some providers may have both reasoning blocks in content AND reasoning_details
// This test ensures both are preserved correctly
const anthropicMessages = [
{
role: "assistant" as const,
content: [
{ type: "reasoning", text: "Reasoning from content block..." } as const,
{ type: "text", text: "Response." },
{ type: "tool_use", id: "tool_789", name: "execute_command", input: {} },
],
reasoning_details: [
{
type: "reasoning.summary",
summary: "Summary from reasoning_details.",
format: "xai-responses-v1",
index: 0,
},
],
},
] as unknown as Anthropic.Messages.MessageParam[]

const openAiMessages = convertToOpenAiMessages(anthropicMessages)
const assistantMessage = openAiMessages[0] as AssistantMessageWithReasoning & { reasoning_details?: ReasoningDetail[] }

// reasoning_content should be extracted from content blocks
expect(assistantMessage.reasoning_content).toBe("Reasoning from content block...")
// reasoning_details should also be preserved
expect(assistantMessage.reasoning_details).toHaveLength(1)
expect(assistantMessage.reasoning_details?.[0]?.summary).toBe("Summary from reasoning_details.")
// Tool calls should be preserved
expect(assistantMessage.tool_calls).toHaveLength(1)
expect(assistantMessage.tool_calls?.[0]?.id).toBe("tool_789")
})

it("should filter out empty reasoning text", () => {
// Empty reasoning blocks should be filtered out
const anthropicMessages = [
{
role: "assistant" as const,
content: [
{ type: "reasoning", text: "" } as const,
{ type: "reasoning", text: "Valid reasoning." } as const,
{ type: "text", text: "Response." },
],
},
] as Anthropic.Messages.MessageParam[]

const openAiMessages = convertToOpenAiMessages(anthropicMessages)
const assistantMessage = openAiMessages[0] as AssistantMessageWithReasoning

// Only non-empty reasoning should be included
expect(assistantMessage.reasoning_content).toBe("Valid reasoning.")
expect(assistantMessage.content).toBe("Response.")
})
})
})

describe("consolidateReasoningDetails", () => {
Expand Down
Loading
Loading