mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-02 02:01:47 +00:00
fix(runtime_provider): _get_named_custom_provider must honour transport field on v12+ providers dict
The v11→v12 migrate_config step writes the API mode for every entry
under the new transport: field (per the v12+ schema in
_normalize_custom_provider_entry). _get_named_custom_provider
read the legacy api_mode: spelling only, so for every migrated
config the lookup returned None for the api mode.
Downstream, _resolve_named_custom_runtime then falls back through
custom_provider.get("api_mode") or _detect_api_mode_for_url(base_url)
or "chat_completions". For loopback URLs (proxies, local servers)
or unknown hostnames, the URL detector returns None and the resolver
silently downgrades the configured codex_responses /
anthropic_messages transport to chat_completions. Requests
get sent to /v1/chat/completions instead of /v1/responses or
/v1/messages and the provider 404s — or worse, returns a usable
chat_completions response while skipping the model's reasoning /
caching surface.
Fix: read both field names — entry.get("api_mode") or
entry.get("transport") — at the two match-by-key + match-by-name
branches in _get_named_custom_provider. The runtime normaliser
_normalize_custom_provider_entry already accepts both spellings;
this lifts the same compat into the direct-dict reader so v12+
configs work without going through the shim.
Adds three regression tests under
tests/hermes_cli/test_user_providers_model_switch.py:
- transport field is read on the match-by-key branch
- legacy api_mode spelling still works for hand-edited configs
- transport is read on the match-by-display-name branch
This commit is contained in:
parent
f54935738c
commit
362996e269
2 changed files with 98 additions and 3 deletions
|
|
@ -748,6 +748,94 @@ def test_switch_model_resolves_user_provider_credentials(monkeypatch, tmp_path):
|
|||
is_global=False,
|
||||
user_providers=config["providers"],
|
||||
)
|
||||
|
||||
|
||||
assert result.success is True
|
||||
assert result.error_message == ""
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Regression: providers: dict ``transport`` field must be honored
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def test_get_named_custom_provider_reads_transport_field(monkeypatch):
|
||||
"""v12+ ``providers:`` dict stores api mode under ``transport:`` (not the
|
||||
legacy ``api_mode:``). ``_get_named_custom_provider`` must accept both
|
||||
field names.
|
||||
|
||||
Bug: this function read only ``entry.get("api_mode")`` for v12+ entries.
|
||||
After ``migrate_config()`` writes ``transport`` on every entry, the
|
||||
lookup returns None and ``_resolve_named_custom_runtime`` falls back
|
||||
through ``_detect_api_mode_for_url(base_url) or "chat_completions"``
|
||||
— silently downgrading every codex_responses / anthropic_messages
|
||||
provider to chat_completions.
|
||||
"""
|
||||
config = {
|
||||
"_config_version": 12,
|
||||
"providers": {
|
||||
"my-codex-provider": {
|
||||
"name": "my-codex-provider",
|
||||
"api": "http://127.0.0.1:4000/v1",
|
||||
"api_key": "test-key",
|
||||
"default_model": "gpt-5",
|
||||
"transport": "codex_responses",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
monkeypatch.setattr(rp, "load_config", lambda: config)
|
||||
|
||||
result = rp._get_named_custom_provider("my-codex-provider")
|
||||
assert result is not None
|
||||
assert result["api_mode"] == "codex_responses"
|
||||
assert result["base_url"] == "http://127.0.0.1:4000/v1"
|
||||
assert result["model"] == "gpt-5"
|
||||
|
||||
|
||||
def test_get_named_custom_provider_legacy_api_mode_field_still_works(monkeypatch):
|
||||
"""Hand-edited configs that used ``api_mode:`` (legacy spelling) inside
|
||||
the v12+ providers: dict shape must keep working — the migration writer
|
||||
produces ``transport:`` but human-edited configs may carry the older
|
||||
spelling forward."""
|
||||
config = {
|
||||
"_config_version": 12,
|
||||
"providers": {
|
||||
"anthropic-proxy": {
|
||||
"name": "anthropic-proxy",
|
||||
"api": "http://127.0.0.1:8082",
|
||||
"api_key": "test-key",
|
||||
"default_model": "claude-opus-4-7",
|
||||
"api_mode": "anthropic_messages", # legacy spelling
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
monkeypatch.setattr(rp, "load_config", lambda: config)
|
||||
|
||||
result = rp._get_named_custom_provider("anthropic-proxy")
|
||||
assert result is not None
|
||||
assert result["api_mode"] == "anthropic_messages"
|
||||
|
||||
|
||||
def test_get_named_custom_provider_transport_resolves_via_display_name(monkeypatch):
|
||||
"""When the requested name matches the entry's ``name:`` field rather
|
||||
than its dict key, the same transport-vs-api_mode logic must apply
|
||||
(second branch in ``_get_named_custom_provider``)."""
|
||||
config = {
|
||||
"_config_version": 12,
|
||||
"providers": {
|
||||
"slug-different-from-name": {
|
||||
"name": "Codex Provider", # display name
|
||||
"api": "http://127.0.0.1:4000/v1",
|
||||
"api_key": "test-key",
|
||||
"default_model": "gpt-5",
|
||||
"transport": "codex_responses",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
monkeypatch.setattr(rp, "load_config", lambda: config)
|
||||
|
||||
result = rp._get_named_custom_provider("Codex Provider")
|
||||
assert result is not None
|
||||
assert result["api_mode"] == "codex_responses"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue