diff --git a/hermes_cli/model_switch.py b/hermes_cli/model_switch.py index 2cdb661844..d5a118f2db 100644 --- a/hermes_cli/model_switch.py +++ b/hermes_cli/model_switch.py @@ -1346,8 +1346,23 @@ def list_authenticated_providers( if fb: models_list = list(fb) - # Try to probe /v1/models if URL is set (but don't block on it) - # For now just show what we know from config + # Prefer the endpoint's live /models list when credentials are + # available. This keeps OpenAI-compatible relays (for example CRS) + # in sync when the server catalog changes without requiring the + # user to mirror every model into config.yaml. + api_key = str(ep_cfg.get("api_key", "") or "").strip() + if not api_key: + key_env = str(ep_cfg.get("key_env", "") or "").strip() + api_key = os.environ.get(key_env, "").strip() if key_env else "" + 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: + models_list = live_models + except Exception: + pass + results.append({ "slug": ep_name, "name": display_name, diff --git a/tests/hermes_cli/test_user_providers_model_switch.py b/tests/hermes_cli/test_user_providers_model_switch.py index 00ccf701c8..b86dcdba3b 100644 --- a/tests/hermes_cli/test_user_providers_model_switch.py +++ b/tests/hermes_cli/test_user_providers_model_switch.py @@ -131,6 +131,55 @@ def test_list_authenticated_providers_enumerates_dict_format_models(monkeypatch) ] +def test_list_authenticated_providers_uses_live_models_for_user_provider(monkeypatch): + """User-defined OpenAI-compatible providers should prefer live /models. + + Regression: CRS-style providers with a stale config ``models:`` dict kept + showing only the configured subset in the /model picker, even though their + /v1/models endpoint exposed newly added models. + """ + monkeypatch.setattr("agent.models_dev.fetch_models_dev", lambda: {}) + monkeypatch.setattr("hermes_cli.providers.HERMES_OVERLAYS", {}) + monkeypatch.setenv("CRS_TEST_KEY", "sk-test") + + calls = [] + + def fake_fetch_api_models(api_key, base_url): + calls.append((api_key, base_url)) + return ["old-configured-model", "new-live-model"] + + monkeypatch.setattr("hermes_cli.models.fetch_api_models", fake_fetch_api_models) + + user_providers = { + "crs-henkee": { + "name": "CRS Henkee", + "base_url": "http://127.0.0.1:3000/api/v1", + "key_env": "CRS_TEST_KEY", + "model": "old-configured-model", + "models": { + "old-configured-model": {"context_length": 200000}, + }, + } + } + + providers = list_authenticated_providers( + current_provider="crs-henkee", + user_providers=user_providers, + custom_providers=[], + max_models=50, + ) + + user_prov = next( + (p for p in providers if p.get("is_user_defined") and p["slug"] == "crs-henkee"), + None, + ) + + assert user_prov is not None + assert calls == [("sk-test", "http://127.0.0.1:3000/api/v1")] + assert user_prov["models"] == ["old-configured-model", "new-live-model"] + assert user_prov["total_models"] == 2 + + def test_list_authenticated_providers_dict_models_without_default_model(monkeypatch): """Dict-format ``models:`` without a ``default_model`` must still expose every dict key, not collapse to an empty list."""