Skip to content
Open
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
8 changes: 5 additions & 3 deletions agent/agent_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,10 +307,12 @@ def init_agent(
elif agent.provider == "anthropic" or (provider_name is None and agent._base_url_hostname == "api.anthropic.com"):
agent.api_mode = "anthropic_messages"
agent.provider = "anthropic"
elif agent._base_url_lower.rstrip("/").endswith("/anthropic"):
elif agent._base_url_lower.rstrip("/").endswith("/anthropic") or (
agent._base_url_hostname == "api.kimi.com" and "/coding" in agent._base_url_lower
):
# Third-party Anthropic-compatible endpoints (e.g. MiniMax, DashScope)
# use a URL convention ending in /anthropic. Auto-detect these so the
# Anthropic Messages API adapter is used instead of chat completions.
# use a URL convention ending in /anthropic. Kimi Coding also speaks
# Anthropic Messages on /coding without using the /anthropic suffix.
agent.api_mode = "anthropic_messages"
elif agent.provider == "bedrock" or (
agent._base_url_hostname.startswith("bedrock-runtime.")
Expand Down
4 changes: 3 additions & 1 deletion agent/chat_completion_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,9 @@ def try_activate_fallback(agent, reason: "FailoverReason | None" = None) -> bool
_fb_is_azure = agent._is_azure_openai_url(fb_base_url)
if fb_provider == "openai-codex":
fb_api_mode = "codex_responses"
elif fb_provider == "anthropic" or fb_base_url.rstrip("/").lower().endswith("/anthropic"):
elif fb_provider == "anthropic" or fb_base_url.rstrip("/").lower().endswith("/anthropic") or (
base_url_hostname(fb_base_url) == "api.kimi.com" and "/coding" in fb_base_url.lower()
):
fb_api_mode = "anthropic_messages"
elif _fb_is_azure:
# Azure OpenAI serves gpt-5.x on /chat/completions — does NOT
Expand Down
48 changes: 48 additions & 0 deletions tests/run_agent/test_run_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,24 @@ def test_anthropic_base_url_accepted(self):
assert agent.api_mode == "anthropic_messages"
mock_anthropic.Anthropic.assert_called_once()

def test_kimi_coding_base_url_uses_anthropic_messages_mode(self):
"""Kimi Coding endpoints speak the Anthropic Messages wire format."""
with (
patch("run_agent.get_tool_definitions", return_value=[]),
patch("run_agent.check_toolset_requirements", return_value={}),
patch("agent.anthropic_adapter._anthropic_sdk") as mock_anthropic,
):
agent = AIAgent(
api_key="sk-kimi-test",
base_url="https://api.kimi.com/coding/v1",

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 regression encodes /coding/v1 as Anthropic, but the follow-up comment says /coding/v1 must remain chat_completions and only bare /coding should change. Please split the endpoint cases after the transport contract is confirmed.

provider="kimi-coding",
quiet_mode=True,
skip_context_files=True,
skip_memory=True,
)
assert agent.api_mode == "anthropic_messages"
mock_anthropic.Anthropic.assert_called_once()

def test_prompt_caching_claude_openrouter(self):
"""Claude model via OpenRouter should enable prompt caching."""
with (
Expand Down Expand Up @@ -4501,6 +4519,36 @@ def test_fallback_to_anthropic_enables_prompt_caching(self, agent):

assert agent._use_prompt_caching is True

def test_fallback_to_kimi_coding_sets_anthropic_messages_mode(self, agent):
agent._fallback_activated = False
agent._fallback_model = {
"provider": "kimi-coding",
"model": "kimi-k2.6",
"base_url": "https://api.kimi.com/coding/v1",
}
agent._fallback_chain = [agent._fallback_model]
agent._fallback_index = 0

mock_client = MagicMock()
mock_client.base_url = "https://api.kimi.com/coding"
mock_client.api_key = "sk-kimi-test"

with (
patch("agent.auxiliary_client.resolve_provider_client", return_value=(mock_client, None)),
patch("agent.anthropic_adapter.build_anthropic_client") as mock_build,
patch("agent.anthropic_adapter.resolve_anthropic_token", return_value=None),
):
mock_build.return_value = MagicMock()
result = agent._try_activate_fallback()

assert result is True
assert agent.api_mode == "anthropic_messages"
assert agent.provider == "kimi-coding"
assert agent.model == "kimi-k2.6"
assert agent._anthropic_client is not None
assert agent.client is None
assert agent._client_kwargs == {}

def test_fallback_to_openrouter_uses_openai_client(self, agent):
agent._fallback_activated = False
agent._fallback_model = {"provider": "openrouter", "model": "anthropic/claude-sonnet-4"}
Expand Down