Skip to content

fix(opencode): preserve reasoning_content for Kimi K2.5/K2.6 multi-turn tool calls#28542

Closed
j0hk3r1 wants to merge 1 commit into
anomalyco:devfrom
j0hk3r1:fix-kimi-reasoning-roundtrip
Closed

fix(opencode): preserve reasoning_content for Kimi K2.5/K2.6 multi-turn tool calls#28542
j0hk3r1 wants to merge 1 commit into
anomalyco:devfrom
j0hk3r1:fix-kimi-reasoning-roundtrip

Conversation

@j0hk3r1
Copy link
Copy Markdown

@j0hk3r1 j0hk3r1 commented May 20, 2026

Summary

Kimi K2.5 and K2.6 (Moonshot AI) reject subsequent requests in multi-turn tool-calling conversations with:

thinking is enabled but reasoning_content is missing in assistant
tool call message at index [N]

The model returns reasoning_content alongside tool_calls in the first turn, but opencode strips it before building the next request's message history. Kimi requires reasoning_content to be preserved on every assistant message (even empty) when thinking mode is active — which is the default on Moonshot's API, Cloudflare Workers AI, Cerebras, and any other openai-compatible Kimi K2.5/K2.6 hosting.

This is the same class of bug already fixed for DeepSeek in transform.ts.

Symptom in the wild

Observed empirically with cloudflare/@cf/moonshotai/kimi-k2.6 via the openai-compatible SDK:

  • Sisyphus/orchestrator agent says "I should call Oracle now" as text
  • No structured tool_call is emitted in the response
  • finish_reason: stop
  • Session hangs until user manually re-prompts

Multiple matching reports:

Fix

Two changes in packages/opencode/src/provider/transform.ts:

  1. Extend the reasoning-placeholder injection guard (isKimiK2RequiringReasoning) to match Kimi K2.5/K2.6 model IDs alongside DeepSeek. Matches: kimi-k2.5, kimi-k2.6, kimi-k2p5, kimi-k2p6, kimi-k2-5, kimi-k2-6. Kimi K2-Thinking is excluded — it returns reasoning_content natively and uses a different SDK path.

  2. Add an explicit interleaved-style serialization block that places reasoning_content into providerOptions.openaiCompatible for Kimi K2.5/K2.6 on @ai-sdk/openai-compatible, even when the models.dev catalog has not yet declared interleaved: { field: "reasoning_content" } for these models. This avoids depending on a separate models.dev change to fix the bug.

Tests

Added 4 test cases following the existing DeepSeek test pattern:

  • Kimi K2.6 injects empty reasoning placeholder on assistant tool-call without reasoning
  • Kimi K2.5 preserves existing reasoning content in providerOptions
  • Kimi K2-Thinking is NOT touched (returns reasoning_content natively)
  • Kimi K2 base (non-K2.5/K2.6) is not affected

All 228 existing + new transform tests pass. All 340 tests under test/provider/ pass with no regressions.

Test plan

  • bun test packages/opencode/test/provider/transform.test.ts — 228 pass, 0 fail
  • bun test packages/opencode/test/provider/ — 340 pass, 0 fail
  • Manual: route kimi-k2.6 (via Cloudflare Workers AI or Cerebras openai-compatible endpoint) through a multi-turn tool-calling session

Fixes #10996
Related: #24722, #24190

…rn tool calls

Kimi K2.5 and K2.6 (Moonshot AI) reject subsequent requests in multi-turn
tool-calling conversations with:

    thinking is enabled but reasoning_content is missing in assistant
    tool call message at index [N]

The model returns reasoning_content alongside tool_calls in the first
turn, but opencode strips it before building the next request's message
history. Kimi requires reasoning_content to be preserved on every
assistant message (even empty) when thinking mode is active — which is
the default on Moonshot's API, Cloudflare Workers AI, Cerebras, and any
other openai-compatible Kimi K2.5/K2.6 hosting.

Same class of bug already fixed for DeepSeek in this file. This patch:

1. Extends the existing reasoning-placeholder injection guard to also
   match Kimi K2.5/K2.6 model IDs (kimi-k2.5, kimi-k2.6, kimi-k2p5,
   kimi-k2p6, kimi-k2-5, kimi-k2-6). Kimi K2-Thinking is excluded — it
   returns reasoning_content natively and uses a different SDK path.

2. Adds an explicit interleaved-style serialization block that places
   reasoning_content into providerOptions.openaiCompatible for Kimi
   K2.5/K2.6 on @ai-sdk/openai-compatible, even when the models.dev
   catalog does not yet declare interleaved={field:"reasoning_content"}
   for these models. This avoids depending on a separate models.dev
   change to fix the bug.

Adds 4 test cases covering: placeholder injection, existing reasoning
preservation, Kimi K2-Thinking exclusion, and Kimi K2 base no-op.

Fixes anomalyco#10996.
Related: anomalyco#24722, anomalyco#24190 (same bug class for DeepSeek)
@github-actions github-actions Bot added the needs:compliance This means the issue will auto-close after 2 hours. label May 20, 2026
@github-actions
Copy link
Copy Markdown
Contributor

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 2 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@github-actions
Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

Based on my search, PR #28542 (the current PR) does not have exact duplicates, but there are related PRs that address similar reasoning_content preservation issues:

Related PRs (Not Duplicates):

  1. PR fix(provider): skip empty reasoning_content to preserve KV cache hits #28352 - fix(provider): skip empty reasoning_content to preserve KV cache hits

    • Related to the same reasoning_content handling, but focused on optimization (skipping empty reasoning to preserve cache hits)
  2. PR fix(opencode): ensure DeepSeek reasoning_content round-trips for all interleaved variants #25110 - fix(opencode): ensure DeepSeek reasoning_content round-trips for all interleaved variants

    • Addresses the same class of bug but for DeepSeek models; this PR builds on that pattern
  3. PR fix(provider): preserve reasoning_content on second interleaved pass (#24146 follow-up) #24443 - fix(provider): preserve reasoning_content on second interleaved pass (#24146 follow-up)

    • Another reasoning_content preservation fix, likely part of the same refactoring effort
  4. PR fix(provider): auto-enable interleaved for reasoning models #24218 - fix(provider): auto-enable interleaved for reasoning models

    • Related to auto-enabling interleaved mode for models with reasoning
  5. PR fix(opencode): filter empty assistant content for @ai-sdk/openai-compatible #27914 - fix(opencode): filter empty assistant content for @ai-sdk/openai-compatible

    • Potentially touches similar @ai-sdk/openai-compatible logic

These are related work but not duplicate PRs. PR #28542 is specifically targeting the Kimi K2.5/K2.6 regression, following the established pattern used for DeepSeek fixes.

@j0hk3r1
Copy link
Copy Markdown
Author

j0hk3r1 commented May 20, 2026

Closing — not ready to upstream yet, will revisit after more local validation.

@j0hk3r1 j0hk3r1 closed this May 20, 2026
@j0hk3r1 j0hk3r1 deleted the fix-kimi-reasoning-roundtrip branch May 20, 2026 22:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs:compliance This means the issue will auto-close after 2 hours.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Kimi for Coding (k2p5) fails with tool calls when thinking enabled: reasoning_content is missing in assistant tool call message

1 participant