NousResearch/hermes-agent
Custom provider config corrections across CLI and runtime
Cluster 14065 · hermes cli. Data as of
Nothing open on GitHub. Fix merged in #17901 on 30 Apr 2026. Check it covers your case.
The short answer
0open issues
0open PRs
3items grouped
- Known?
- Yes. 3 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #17901. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
unresolved. That is separate from the fix event. - Still open
- Nothing open on GitHub.
These items address bugs in how custom provider configurations are preserved and applied—covering migration from v11 to v12, CLI argument handling, API key dropping, and transport field support.
Open issues0
Open PRs0
Fixes on record
-
#17901 fix(runtime_provider): honour transport field on v12+ providers dict
files:
hermes_cli/runtime_provider.py,scripts/release.py,tests/hermes_cli/test_user_providers_model_switch.pyShow the diff
-
hermes_cli/runtime_provider.py@@ -391,7 +391,14 @@ def _get_named_custom_provider(requested_provider: str) -> Optional[Dict[str, An "api_key": [redacted:secret-assignment] "model": entry.get("default_model", ""), } - api_mode = _parse_api_mode(entry.get("api_mode")) + # The v11→v12 migration writes the API mode under the new + # ``transport`` field, but hand-edited configs may still + # use the legacy ``api_mode`` spelling. Accept both — + # the runtime normaliser ``_normalize_custom_provider_entry`` + # already does, so without this lift every migrated config + # silently downgrades codex_responses / anthropic_messages + # providers to chat_completions in the resolved runtime. + api_mode = _parse_api_mode(entry.get("api_mode") or entry.get("transport")) if api_mode: result["api_mode"] = api_mode return result @@ -409,7 +416,7 @@ def _get_named_custom_provider(requested_provider: str) -> Optional[Dict[str, An "api_key": [redacted:secret-assignment] "model": entry.get("default_model", ""), } - api_mode = _parse_api_mode(entry.get("api_mode")) + api_mode = _parse_api_mode(entry.get("api_mode") or entry.get("transport")) if api_mode: result["api_mode"] = api_mode return result -
scripts/release.py@@ -117,6 +117,7 @@ "[redacted:email]": "Abnertheforeman", "[redacted:email]": "amanning3390", "[redacted:email]": "tochukwuada", + "[redacted:email]": "zicochaos", "[redacted:email]": "hharry11", "[redacted:email]": "kshitijk4poor", "[email protected]": "Tosko4", -
tests/hermes_cli/test_user_providers_model_switch.py@@ -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": "[redacted:secret-assignment]", + "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": "[redacted:secret-assignment]", + "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": "[redacted:secret-assignment]", + "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"
-
1 PR closed without merging
From the close comments: superseded by another PR 1
-
#14960 fix(cli): preserve api_mode/key_env/model for providers-dict named resolution · superseded by another PR →
#17901
Closed issues · 1
- Closed#14065Named custom providers under providers: drop inline api_key during runtime resolution
How this cluster was grouped
-
Link evidence (
structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference. -
Pairwise link density
0.67: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Subsystem
- hermes cli
- Merged PRs
- #17901
- Closed issues
- 1
- Attempts
- 1 PR closed without merging
- Fix link checked
- 30 Apr 2026. This confirmation is old and may be out of date (a revert or reopen would not have invalidated it here); re-verify.