fix(providers): support anthropic proxy v1 endpoints

This commit is contained in:
helix4u 2026-06-13 16:02:23 -06:00 committed by Teknium
parent 81e42335a1
commit 85e6232a07
8 changed files with 133 additions and 14 deletions

View file

@ -113,6 +113,15 @@ class TestBuildAnthropicClient:
"anthropic-beta": "interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14"
}
def test_custom_base_url_strips_trailing_v1(self):
with patch("agent.anthropic_adapter._anthropic_sdk") as mock_sdk:
build_anthropic_client(
"sk-ant-api03-x",
base_url="https://proxy.example.com/anthropic/v1",
)
kwargs = mock_sdk.Anthropic.call_args[1]
assert kwargs["base_url"] == "https://proxy.example.com/anthropic"
def test_azure_anthropic_endpoint_keeps_context_1m_beta(self):
with patch("agent.anthropic_adapter._anthropic_sdk") as mock_sdk:
build_anthropic_client(

View file

@ -39,6 +39,8 @@ def _clean_env(monkeypatch):
("https://api.moonshot.ai/v1", False, "Moonshot legacy"),
("https://api.minimax.io/anthropic", True, "MiniMax /anthropic"),
("https://litellm.example.com/v1/anthropic", True, "/anthropic suffix"),
("https://litellm.example.com/anthropic/v1", True, "/anthropic/v1 base"),
("https://litellm.example.com/anthropic/v1/models", False, "/anthropic/v1 subpath"),
("https://api.anthropic.com", True, "native Anthropic"),
("https://api.anthropic.com/v1", True, "native Anthropic /v1"),
("https://openrouter.ai/api/v1", False, "OpenRouter"),

View file

@ -56,13 +56,16 @@ class TestAnthropicMessagesDetection:
def test_trailing_slash_tolerated(self):
assert _detect_api_mode_for_url("https://api.minimax.io/anthropic/") == "anthropic_messages"
def test_versioned_anthropic_base_url_tolerated(self):
assert _detect_api_mode_for_url("https://proxy.example.com/anthropic/v1") == "anthropic_messages"
def test_uppercase_path_tolerated(self):
assert _detect_api_mode_for_url("https://API.MINIMAX.IO/Anthropic") == "anthropic_messages"
def test_anthropic_in_middle_of_path_does_not_match(self):
def test_anthropic_endpoint_subpath_does_not_match(self):
# The helper requires ``/anthropic`` as the path SUFFIX, not anywhere.
# Protects against false positives on e.g. /anthropic/v1/models.
assert _detect_api_mode_for_url("https://api.example.com/anthropic/v1") is None
assert _detect_api_mode_for_url("https://api.example.com/anthropic/v1/models") is None
class TestDefaultCase:

View file

@ -215,6 +215,58 @@ class TestProviderModelIds:
patch("hermes_cli.models._fetch_github_models", return_value=["gpt-5.4", "claude-sonnet-4.6"]):
assert provider_model_ids("copilot-acp") == ["gpt-5.4", "claude-sonnet-4.6"]
def test_anthropic_provider_uses_configured_base_url_for_live_catalog(self):
class _Resp:
def __enter__(self):
return self
def __exit__(self, exc_type, exc, tb):
return False
def read(self):
return b'{"data": [{"id": "enterprise-claude"}]}'
with patch(
"hermes_cli.config.load_config",
return_value={
"model": {
"provider": "anthropic",
"base_url": "http://localhost:6655/anthropic/v1",
"api_key": "proxy-key",
}
},
), patch(
"hermes_cli.models.urllib.request.urlopen",
return_value=_Resp(),
) as mock_urlopen:
assert provider_model_ids("anthropic") == ["enterprise-claude"]
req = mock_urlopen.call_args[0][0]
assert req.full_url == "http://localhost:6655/anthropic/v1/models"
assert req.get_header("X-api-key") == "proxy-key"
def test_custom_provider_passes_anthropic_mode_for_versioned_proxy_catalog(self):
with patch(
"hermes_cli.config.load_config",
return_value={
"model": {
"provider": "custom",
"base_url": "http://localhost:6655/anthropic/v1",
"api_key": "proxy-key",
}
},
), patch(
"hermes_cli.models.fetch_api_models",
return_value=["enterprise-claude"],
) as mock_fetch:
assert provider_model_ids("custom") == ["enterprise-claude"]
mock_fetch.assert_called_once_with(
"proxy-key",
"http://localhost:6655/anthropic/v1",
api_mode="anthropic_messages",
)
# -- fetch_api_models --------------------------------------------------------