From 2d9f116351ba35cb90ded82e6520d5c83402269a Mon Sep 17 00:00:00 2001 From: pierrenode <298902573+pierrenode@users.noreply.github.com> Date: Fri, 14 Aug 2026 03:20:42 +0300 Subject: [PATCH] fix(agent): anchor ZAI/Kimi base_url host matching to avoid substring false positives _to_openai_base_url() matched ZAI (open.bigmodel.cn, api.z.ai, bare "bigmodel") and Kimi (api.kimi.com) via `substring in url`, so any custom gateway whose base_url happened to contain one of those strings as a path segment (e.g. a reverse-proxy prefix like /proxy/bigmodel-fallback/) was silently misrouted to the wrong OpenAI-wire endpoint shape. This is the same false-positive class 6f33f510e8 just fixed for the MiniMax branch in the same function by switching to base_url_host_matches() (hostname-anchored). Apply the same fix to the ZAI and Kimi branches, which that commit didn't touch. Drops the bare "bigmodel" substring check since open.bigmodel.cn is the only canonical bigmodel-family host referenced anywhere else in the codebase (agent/model_metadata.py, hermes_cli/auth.py). Added regression tests mirroring the MiniMax marker-in-path tests added in the same commit. --- agent/auxiliary_client.py | 4 ++-- tests/agent/test_minimax_auxiliary_url.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index 39daa4deed..0c8253d3e5 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -1283,7 +1283,7 @@ def _to_openai_base_url(base_url: str) -> str: # ZAI uses /api/anthropic for the Coding Plan's Anthropic wire. The # matching OpenAI-wire endpoint is /api/coding/paas/v4; /api/paas/v4 # is the independently billed general API. - if "open.bigmodel.cn" in url or "bigmodel" in url or "api.z.ai" in url: + if base_url_host_matches(url, "open.bigmodel.cn") or base_url_host_matches(url, "api.z.ai"): rewritten = url[: -len("/anthropic")] + "/coding/paas/v4" logger.debug("Auxiliary client: rewrote ZAI base URL %s → %s", url, rewritten) return rewritten @@ -1297,7 +1297,7 @@ def _to_openai_base_url(base_url: str) -> str: url, ) return url - if "api.kimi.com" in url and url.endswith("/coding"): + if base_url_host_matches(url, "api.kimi.com") and url.endswith("/coding"): # Kimi Code uses /coding/v1/messages for Anthropic SDK (appends /v1/messages) # but /coding/v1/chat/completions for OpenAI SDK (appends /chat/completions) # Without /v1 here, OpenAI SDK hits /coding/chat/completions — a 404. diff --git a/tests/agent/test_minimax_auxiliary_url.py b/tests/agent/test_minimax_auxiliary_url.py index 0f07670c7c..bfc41a0002 100644 --- a/tests/agent/test_minimax_auxiliary_url.py +++ b/tests/agent/test_minimax_auxiliary_url.py @@ -51,5 +51,24 @@ class TestToOpenaiBaseUrl: == "https://open.bigmodel.cn/api/coding/paas/v4" ) + def test_bigmodel_marker_in_path_does_not_false_positive(self): + """Host-anchored matching: 'bigmodel' in the path must not trigger rewrite.""" + url = "https://gateway.example.com/proxy/bigmodel-fallback/anthropic" + assert _to_openai_base_url(url) == url + + def test_zai_marker_in_path_does_not_false_positive(self): + url = "https://gateway.example.com/api.z.ai-mirror/anthropic" + assert _to_openai_base_url(url) == url + + def test_kimi_coding_host_rewritten(self): + assert ( + _to_openai_base_url("https://api.kimi.com/coding") + == "https://api.kimi.com/coding/v1" + ) + + def test_kimi_marker_in_path_does_not_false_positive(self): + url = "https://gateway.example.com/some/api.kimi.com-proxy/coding" + assert _to_openai_base_url(url) == url + def test_none(self): assert _to_openai_base_url(None) == ""