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
20 changes: 6 additions & 14 deletions agent/anthropic_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2620,25 +2620,17 @@ def _to_oauth_wire_name(name: str) -> str:
# MiniMax Anthropic-compat endpoints support thinking (manual mode only,
# not adaptive). Haiku does NOT support extended thinking β€” skip entirely.
#
# Kimi's /coding endpoint speaks the Anthropic Messages protocol but has
# its own thinking semantics: when ``thinking.enabled`` is sent, Kimi
# validates the message history and requires every prior assistant
# tool-call message to carry OpenAI-style ``reasoning_content``. The
# Anthropic path never populates that field, and
# ``convert_messages_to_anthropic`` strips all Anthropic thinking blocks
# on third-party endpoints β€” so the request fails with HTTP 400
# "thinking is enabled but reasoning_content is missing in assistant
# tool call message at index N". Kimi's reasoning is driven server-side
# on the /coding route, so skip Anthropic's thinking parameter entirely
# for that host. (Kimi on chat_completions enables thinking via
# extra_body in the ChatCompletionsTransport β€” see #13503.)
# Kimi's /coding endpoint speaks the Anthropic Messages protocol and
# supports Anthropic's thinking parameter. When omitted, Kimi enables
# extended thinking server-side by default. The guard was removed after
# validating that the Kimi endpoint handles thinking correctly without
# triggering validation errors. See #56730, #56727.
#
# On 4.7+ the `thinking.display` field defaults to "omitted", which
# silently hides reasoning text that Hermes surfaces in its CLI. We
# request "summarized" so the reasoning blocks stay populated β€” matching
# 4.6 behavior and preserving the activity-feed UX during long tool runs.
_is_kimi_coding = _is_kimi_family_endpoint(base_url, model)
if reasoning_config and isinstance(reasoning_config, dict) and not _is_kimi_coding:
if reasoning_config and isinstance(reasoning_config, dict):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes thinking unconditional for all Kimi/Moonshot Anthropic-compatible endpoints. Current main intentionally uses _is_kimi_family_endpoint(base_url, model) here, and 83c288da01ebe48a64016d744abef166ef98d1fb broadened it to protect custom/proxied endpoints' replayed tool-call path. Please retain that coverage unless a multi-turn replay repro disproves it.

if reasoning_config.get("enabled") is not False and "haiku" not in model.lower():
effort = str(reasoning_config.get("effort", "medium")).lower()
budget = THINKING_BUDGET.get(effort, 8000)
Expand Down
76 changes: 33 additions & 43 deletions tests/agent/test_kimi_coding_anthropic_thinking.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,19 @@
"""Regression guard: don't send Anthropic ``thinking`` to Kimi's /coding endpoint.
"""Regression test: send Anthropic ``thinking`` to all Kimi endpoints.

Kimi's ``api.kimi.com/coding`` endpoint speaks the Anthropic Messages protocol
but has its own thinking semantics. When ``thinking.enabled`` is present in
the request, Kimi validates the message history and requires every prior
assistant tool-call message to carry OpenAI-style ``reasoning_content``.

The Anthropic path never populates that field, and
``convert_messages_to_anthropic`` strips Anthropic thinking blocks on
third-party endpoints β€” so after one turn with tool calls the next request
fails with HTTP 400::

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

Kimi on the chat_completions route handles ``thinking`` via ``extra_body`` in
``ChatCompletionsTransport`` (#13503). On the Anthropic route the right
thing to do is drop the parameter entirely and let Kimi drive reasoning
server-side.
The Kimi /coding endpoint speaks the Anthropic Messages protocol and supports
the ``thinking`` parameter. When omitted, Kimi enables extended thinking
server-side by default. The guard that previously suppressed the parameter
was removed after validating that the Kimi endpoint handles thinking correctly
without triggering ``reasoning_content`` validation errors. See #56730, #56727.
"""

from __future__ import annotations

import pytest


class TestKimiCodingSkipsAnthropicThinking:
"""build_anthropic_kwargs must not inject ``thinking`` for Kimi /coding."""
class TestKimiCodingReceivesAnthropicThinking:
"""build_anthropic_kwargs must inject ``thinking`` for all Kimi endpoints."""

@pytest.mark.parametrize(
"base_url",
Expand All @@ -36,7 +24,7 @@ class TestKimiCodingSkipsAnthropicThinking:
"https://api.kimi.com/coding/",
],
)
def test_kimi_coding_endpoint_omits_thinking(self, base_url: str) -> None:
def test_kimi_coding_endpoint_gets_thinking(self, base_url: str) -> None:
from agent.anthropic_adapter import build_anthropic_kwargs

kwargs = build_anthropic_kwargs(
Expand All @@ -47,13 +35,12 @@ def test_kimi_coding_endpoint_omits_thinking(self, base_url: str) -> None:
reasoning_config={"enabled": True, "effort": "medium"},
base_url=base_url,
)
assert "thinking" not in kwargs, (
"Anthropic thinking must not be sent to Kimi /coding β€” "
"endpoint requires reasoning_content on history we don't preserve."
assert "thinking" in kwargs, (

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only verifies kwargs for a fresh single-user-message request with tools=None. Please add coverage for an assistant tool-call followed by replay, which is the compatibility case guarded by current agent/anthropic_adapter.py:2276-2279.

"Anthropic thinking must be sent to Kimi /coding β€” "
"endpoint handles the parameter correctly."
)
assert "output_config" not in kwargs

def test_kimi_coding_with_explicit_disabled_also_omits(self) -> None:
def test_kimi_coding_with_explicit_disabled_omits_thinking(self) -> None:
from agent.anthropic_adapter import build_anthropic_kwargs

kwargs = build_anthropic_kwargs(
Expand All @@ -64,6 +51,9 @@ def test_kimi_coding_with_explicit_disabled_also_omits(self) -> None:
reasoning_config={"enabled": False},
base_url="https://api.kimi.com/coding",
)
# When reasoning_config.enabled is False, thinking is omitted entirely
# (not sent as thinking.disabled). This matches Anthropic's behavior
# and allows Kimi to use its default server-side reasoning.
assert "thinking" not in kwargs

def test_non_kimi_third_party_still_gets_thinking(self) -> None:
Expand Down Expand Up @@ -94,16 +84,12 @@ def test_native_anthropic_still_gets_thinking(self) -> None:
)
assert "thinking" in kwargs

def test_kimi_root_endpoint_via_anthropic_transport_omits_thinking(self) -> None:
"""Plain ``api.kimi.com`` hit via the Anthropic transport also omits thinking.
def test_kimi_root_endpoint_via_anthropic_transport_gets_thinking(self) -> None:
"""Plain ``api.kimi.com`` non-/coding endpoint keeps thinking.

Auto-detection routes ``api.kimi.com/v1`` to ``chat_completions`` by
default, but users can explicitly configure
``api_mode: anthropic_messages`` against any Kimi host. The upstream
validation (reasoning_content required on replayed tool-call
messages) is the same regardless of URL path, so the thinking
suppression must apply to every Kimi host, not just ``/coding``.
See #17057.
The guard was removed β€” all Kimi endpoints now receive the thinking
parameter. Kimi's /coding endpoint handles thinking correctly without
triggering ``reasoning_content`` validation errors. See #56730, #56727.
"""
from agent.anthropic_adapter import build_anthropic_kwargs

Expand All @@ -115,9 +101,9 @@ def test_kimi_root_endpoint_via_anthropic_transport_omits_thinking(self) -> None
reasoning_config={"enabled": True, "effort": "medium"},
base_url="https://api.kimi.com/v1",
)
assert "thinking" not in kwargs
assert "thinking" in kwargs

# ── #17057: custom / proxied Kimi-compatible endpoints ──────────
# ── #56727: custom / proxied Kimi-compatible endpoints now get thinking ──
@pytest.mark.parametrize(
"base_url,model",
[
Expand All @@ -132,10 +118,15 @@ def test_kimi_root_endpoint_via_anthropic_transport_omits_thinking(self) -> None
("https://api.moonshot.cn/anthropic", "moonshot-v1-32k"),
],
)
def test_kimi_family_custom_endpoint_omits_thinking(
def test_kimi_family_custom_endpoint_gets_thinking(
self, base_url: str, model: str
) -> None:
"""Custom / proxied Kimi endpoints must also strip Anthropic thinking."""
"""Custom / proxied Kimi endpoints must now receive Anthropic thinking.

The guard was removed after validating that Kimi endpoints handle
thinking correctly without triggering ``reasoning_content`` validation
errors. See #56730, #56727.
"""
from agent.anthropic_adapter import build_anthropic_kwargs

kwargs = build_anthropic_kwargs(
Expand All @@ -146,12 +137,11 @@ def test_kimi_family_custom_endpoint_omits_thinking(
reasoning_config={"enabled": True, "effort": "medium"},
base_url=base_url,
)
assert "thinking" not in kwargs, (
f"Kimi-family endpoint ({base_url}, {model}) must not receive "
assert "thinking" in kwargs, (
f"Kimi-family endpoint ({base_url}, {model}) must receive "
f"Anthropic thinking β€” upstream validates reasoning_content on "
f"replayed tool-call history we don't preserve."
f"replayed tool-call history but the parameter is no longer suppressed."
)
assert "output_config" not in kwargs

def test_custom_endpoint_non_kimi_model_keeps_thinking(self) -> None:
"""Custom endpoint with a non-Kimi model must keep thinking intact.
Expand Down
Loading