fix: fallback entries inherit a named provider's declared transport
A fallback_providers entry naming a `providers.<name>` block (or `custom:<name>`) without its own api_mode was re-detected from the resolved host: an Anthropic-Messages proxy on a plain host or a Responses-only relay behind a generic gateway landed on chat_completions while resolve_provider_client had already built the declared client (#33062 bottom thread, #81932 provider-level `transport:`). The hint pass now reads the named block's api_mode/transport as explicit, and entry-level `transport:` is accepted as an alias of `api_mode` with the same canonicalisation the providers block uses (`responses` -> codex_responses). Fixes #33062 Fixes #81932
This commit is contained in:
@@ -1736,9 +1736,19 @@ def _fallback_api_mode_hint(fb: dict, fb_provider: str, fb_base_url_hint: Option
|
||||
rewrites a dual-surface /anthropic base to /v1, losing the Anthropic wire signal. An explicit
|
||||
``api_mode`` always wins (even "chat_completions") and suppresses later re-detection;
|
||||
``provider: anthropic`` without a base_url still resolves to anthropic_messages."""
|
||||
explicit = str(fb.get("api_mode") or "").strip()
|
||||
from hermes_cli.runtime_provider import _get_named_custom_provider, _parse_api_mode
|
||||
# Entries accept the same ``api_mode`` / ``transport`` spellings as ``providers.<name>``.
|
||||
explicit = _parse_api_mode(fb.get("api_mode") or fb.get("transport"))
|
||||
if explicit:
|
||||
return True, explicit
|
||||
# A named ``providers.<name>`` block declares its wire once (``api_mode``/``transport``); a
|
||||
# fallback entry naming that provider inherits it instead of being re-detected from the host
|
||||
# (#33062, #81932: an Anthropic-Messages or Responses-only relay on a plain host was downgraded
|
||||
# to chat_completions while resolve_provider_client had already built the declared client).
|
||||
if fb_provider and fb_provider not in {"custom", "moa"}:
|
||||
declared = (_get_named_custom_provider(fb_provider) or {}).get("api_mode")
|
||||
if declared:
|
||||
return True, declared
|
||||
if fb_provider == "anthropic" or (fb_base_url_hint and _is_anthropic_wire_url(fb_base_url_hint)):
|
||||
return False, "anthropic_messages"
|
||||
return False, "chat_completions"
|
||||
|
||||
@@ -178,6 +178,30 @@ class TestOriginalUrlDetection:
|
||||
assert agent.api_mode == "chat_completions"
|
||||
|
||||
|
||||
class TestNamedProviderDeclaredWire:
|
||||
"""A fallback entry naming a ``providers.<name>`` block inherits the block's declared
|
||||
``api_mode``/``transport`` instead of host re-detection (#33062, #81932)."""
|
||||
|
||||
def test_named_block_anthropic_messages_inherited_on_plain_host(self):
|
||||
fbs = [{"provider": "custom:ai-proxy", "model": "claude-4.7-opus"}]
|
||||
agent = _make_agent(fallback_model=fbs)
|
||||
with patch(
|
||||
"hermes_cli.runtime_provider._get_named_custom_provider",
|
||||
return_value={"name": "ai-proxy", "base_url": "https://ai-proxy.example.com",
|
||||
"api_key": "k", "api_mode": "anthropic_messages"},
|
||||
):
|
||||
mock_rpc = _activate(agent, "https://ai-proxy.example.com", "claude-4.7-opus")
|
||||
assert agent.api_mode == "anthropic_messages"
|
||||
assert mock_rpc.call_args.kwargs["api_mode"] == "anthropic_messages"
|
||||
|
||||
def test_entry_transport_alias_is_honored(self):
|
||||
fbs = [{"provider": "custom", "model": "custom/responses", "base_url": "https://gateway.example.com/v1",
|
||||
"api_key": "k", "transport": "responses"}]
|
||||
agent = _make_agent(fallback_model=fbs)
|
||||
_activate(agent, "https://gateway.example.com/v1", "custom/responses")
|
||||
assert agent.api_mode == "codex_responses"
|
||||
|
||||
|
||||
class TestPlainFallbackUnchanged:
|
||||
def test_plain_openrouter_fallback_stays_chat_completions(self):
|
||||
fbs = [{
|
||||
|
||||
@@ -1676,9 +1676,11 @@ fallback_providers:
|
||||
- provider: anthropic
|
||||
model: claude-sonnet-4
|
||||
# base_url: http://localhost:8000/v1 # optional, for custom endpoints
|
||||
# api_mode: chat_completions # optional override
|
||||
# api_mode: chat_completions # optional override (`transport:` is an accepted alias)
|
||||
```
|
||||
|
||||
An entry that names a `providers.<name>` block (`provider: my-relay` or `provider: custom:my-relay`) inherits that block's `transport` / `api_mode` when the entry sets none, so a Responses-only or Anthropic-Messages relay keeps its declared wire on fallback. Set `api_mode` on the entry to override it.
|
||||
|
||||
The legacy single-pair `fallback_model:` dict is still accepted for back-compat:
|
||||
|
||||
```yaml
|
||||
|
||||
Reference in New Issue
Block a user