Skip to content

fix(runtime_provider): _get_named_custom_provider must honour transport field on v12+ providers dict#17860

Closed
zicochaos wants to merge 1 commit into
NousResearch:mainfrom
zicochaos:fix/v12-providers-transport-field
Closed

fix(runtime_provider): _get_named_custom_provider must honour transport field on v12+ providers dict#17860
zicochaos wants to merge 1 commit into
NousResearch:mainfrom
zicochaos:fix/v12-providers-transport-field

Conversation

@zicochaos

Copy link
Copy Markdown
Contributor

Summary

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's _KNOWN_KEYS). _get_named_custom_provider reads only the legacy api_mode: spelling, so for every migrated config the lookup returns None.

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.

Result: a config like

providers:
  my-codex-proxy:
    api: http://127.0.0.1:4000/v1
    default_model: gpt-5
    transport: codex_responses        # ← never honoured

routes requests to /v1/chat/completions instead of /v1/responses. The proxy returns 404 (if it's strict) or strips out reasoning/caching surface (if it's lenient).

End-to-end repro

from hermes_cli.runtime_provider import _get_named_custom_provider, resolve_runtime_provider
# Config has providers.my-codex-proxy.transport == 'codex_responses'

# Before the fix:
_get_named_custom_provider("my-codex-proxy")
# → {'name': '...', 'base_url': '...', 'api_key': '...', 'model': '...'}
#   ← no api_mode field!
resolve_runtime_provider(requested="my-codex-proxy")
# → {'api_mode': 'chat_completions', ...}   ← wrong

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 (line 2358); this lifts the same compat into the direct-dict reader so v12+ configs work without going through the shim.

Test plan

  • tests/hermes_cli/test_user_providers_model_switch.py::test_get_named_custom_provider_reads_transport_field — transport field is read on the match-by-key branch
  • tests/hermes_cli/test_user_providers_model_switch.py::test_get_named_custom_provider_legacy_api_mode_field_still_works — legacy api_mode: spelling still works for hand-edited configs
  • tests/hermes_cli/test_user_providers_model_switch.py::test_get_named_custom_provider_transport_resolves_via_display_name — transport is read on the match-by-display-name branch
$ pytest tests/hermes_cli/test_user_providers_model_switch.py::test_get_named_custom_provider_reads_transport_field tests/hermes_cli/test_user_providers_model_switch.py::test_get_named_custom_provider_legacy_api_mode_field_still_works tests/hermes_cli/test_user_providers_model_switch.py::test_get_named_custom_provider_transport_resolves_via_display_name -q
======================== 3 passed in 2.29s ========================

Related

Companion to PR #17844 — same v11→v12 migration boundary, different downstream loss. Once both land, hermes config migrate produces a config that's faithfully read by every code path.

…rt 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
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard area/config Config system, migrations, profiles labels Apr 30, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Merged via PR #17901 onto current main. Your fix commit was rebase-merged so your authorship is preserved in git log — thanks for the tight, well-scoped diagnosis and regression tests!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants