From abe5a3c93750883e0d01031304061c1579003426 Mon Sep 17 00:00:00 2001 From: 0oAstro <79555780+0oAstro@users.noreply.github.com> Date: Sun, 3 May 2026 09:55:32 +0530 Subject: [PATCH] fix(model_switch): live model discovery for custom_providers in /model picker custom_providers entries (section 4 of list_authenticated_providers) only read the static models: dict from config.yaml, ignoring the live /v1/models endpoint. This means gateways like Bifrost that expose hundreds of models only show the handful explicitly listed in config. Add live discovery via fetch_api_models() for custom_providers entries that have api_key + base_url, matching the existing behavior for user providers: entries (section 3). When the endpoint is reachable and returns models, the live list replaces the static subset. Fixes: /model picker showing only 9 models from a Bifrost gateway that actually exposes 581. --- hermes_cli/model_switch.py | 15 ++++- .../test_model_switch_custom_providers.py | 61 +++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/hermes_cli/model_switch.py b/hermes_cli/model_switch.py index 29097f5b2e..dcdd81df4a 100644 --- a/hermes_cli/model_switch.py +++ b/hermes_cli/model_switch.py @@ -1637,7 +1637,8 @@ def list_authenticated_providers( groups[group_key]["models"].append(m) _section4_emitted_slugs: set = set() - for grp in groups.values(): + for grp_key, grp in groups.items(): + api_url, api_key = grp_key slug = grp["slug"] # If the slug is already claimed by a built-in / overlay / # user-provider row (sections 1-3), skip this custom group @@ -1675,6 +1676,18 @@ def list_authenticated_providers( _grp_url_norm = _pair_key[1] if _grp_url_norm and _grp_url_norm in _builtin_endpoints: continue + # Live model discovery from custom provider endpoints (matches + # Section 3 behavior for user ``providers:`` entries). + if api_url and api_key: + try: + from hermes_cli.models import fetch_api_models + + live_models = fetch_api_models(api_key, api_url) + if live_models: + grp["models"] = live_models + grp["total_models"] = len(live_models) + except Exception: + pass results.append({ "slug": slug, "name": grp["name"], diff --git a/tests/hermes_cli/test_model_switch_custom_providers.py b/tests/hermes_cli/test_model_switch_custom_providers.py index 624cba9c99..84734e622d 100644 --- a/tests/hermes_cli/test_model_switch_custom_providers.py +++ b/tests/hermes_cli/test_model_switch_custom_providers.py @@ -506,3 +506,64 @@ def test_lmstudio_picker_skips_probe_when_not_configured(monkeypatch): ) assert "base_url" not in captured + + +def test_custom_providers_uses_live_models_for_multi_model_endpoint(monkeypatch): + """Custom providers with api_key + base_url should prefer live /models. + + Custom providers (section 4 of list_authenticated_providers) point at + gateways like Bifrost that expose hundreds of models. Reading only the + static ``models:`` dict from config.yaml leaves the /model picker with + a stale subset. Live discovery fills the picker with all available + models from the endpoint. + """ + monkeypatch.setattr("agent.models_dev.fetch_models_dev", lambda: {}) + monkeypatch.setattr("hermes_cli.providers.HERMES_OVERLAYS", {}) + + calls = [] + + def fake_fetch_api_models(api_key, base_url): + calls.append((api_key, base_url)) + return ["gateway-model-a", "gateway-model-b", "gateway-model-c"] + + monkeypatch.setattr("hermes_cli.models.fetch_api_models", fake_fetch_api_models) + + custom_providers = [ + { + "name": "my-gateway", + "api_key": "sk-gateway-key", + "base_url": "https://gateway.example.com/v1", + "model": "gateway-model-a", + "models": { + "gateway-model-a": {"context_length": 128000}, + "gateway-model-b": {"context_length": 128000}, + }, + } + ] + + providers = list_authenticated_providers( + current_provider="openrouter", + current_base_url="https://openrouter.ai/api/v1", + custom_providers=custom_providers, + max_models=50, + ) + + gateway_prov = next( + ( + p + for p in providers + if p.get("api_url") == "https://gateway.example.com/v1" + ), + None, + ) + + assert gateway_prov is not None, "Custom provider group not found in results" + assert calls == [("sk-gateway-key", "https://gateway.example.com/v1")], ( + "fetch_api_models must be called with the custom provider's credentials" + ) + assert gateway_prov["models"] == [ + "gateway-model-a", + "gateway-model-b", + "gateway-model-c", + ], "Live models must replace the static subset" + assert gateway_prov["total_models"] == 3