diff --git a/agent/agent_init.py b/agent/agent_init.py index e20755c50919..906a85c2e697 100644 --- a/agent/agent_init.py +++ b/agent/agent_init.py @@ -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.") diff --git a/agent/chat_completion_helpers.py b/agent/chat_completion_helpers.py index 602b923a894a..af7094a6f332 100644 --- a/agent/chat_completion_helpers.py +++ b/agent/chat_completion_helpers.py @@ -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 diff --git a/tests/run_agent/test_run_agent.py b/tests/run_agent/test_run_agent.py index 3d0dcedddd05..5a38e478b9eb 100644 --- a/tests/run_agent/test_run_agent.py +++ b/tests/run_agent/test_run_agent.py @@ -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", + 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 ( @@ -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"}