diff --git a/hermes_cli/model_switch.py b/hermes_cli/model_switch.py index 5a494dc19..3395ea70d 100644 --- a/hermes_cli/model_switch.py +++ b/hermes_cli/model_switch.py @@ -807,6 +807,10 @@ def list_authenticated_providers( # "nous" shares OpenRouter's curated list if not separately defined if "nous" not in curated: curated["nous"] = curated["openrouter"] + # Ollama Cloud uses dynamic discovery (no static curated list) + if "ollama-cloud" not in curated: + from hermes_cli.models import fetch_ollama_cloud_models + curated["ollama-cloud"] = fetch_ollama_cloud_models() # --- 1. Check Hermes-mapped providers --- for hermes_id, mdev_id in PROVIDER_TO_MODELS_DEV.items(): diff --git a/hermes_cli/models.py b/hermes_cli/models.py index 2129788be..309840aea 100644 --- a/hermes_cli/models.py +++ b/hermes_cli/models.py @@ -1286,6 +1286,10 @@ def provider_model_ids(provider: Optional[str], *, force_refresh: bool = False) live = _fetch_ai_gateway_models() if live: return live + if normalized == "ollama-cloud": + live = fetch_ollama_cloud_models(force_refresh=force_refresh) + if live: + return live if normalized == "custom": base_url = _get_custom_base_url() if base_url: diff --git a/tests/hermes_cli/test_ollama_cloud_provider.py b/tests/hermes_cli/test_ollama_cloud_provider.py index 9dad26092..f3702a417 100644 --- a/tests/hermes_cli/test_ollama_cloud_provider.py +++ b/tests/hermes_cli/test_ollama_cloud_provider.py @@ -114,6 +114,65 @@ class TestOllamaCloudModelCatalog: assert "ollama-cloud" in _PROVIDER_LABELS assert _PROVIDER_LABELS["ollama-cloud"] == "Ollama Cloud" + def test_provider_model_ids_returns_dynamic_models(self, tmp_path, monkeypatch): + """provider_model_ids('ollama-cloud') should call fetch_ollama_cloud_models().""" + from hermes_cli.models import provider_model_ids + + monkeypatch.setenv("HERMES_HOME", str(tmp_path)) + monkeypatch.setenv("OLLAMA_API_KEY", "test-key") + + mock_mdev = { + "ollama-cloud": { + "models": { + "qwen3.5:397b": {"tool_call": True}, + "glm-5": {"tool_call": True}, + } + } + } + with patch("hermes_cli.models.fetch_api_models", return_value=["qwen3.5:397b"]), \ + patch("agent.models_dev.fetch_models_dev", return_value=mock_mdev): + result = provider_model_ids("ollama-cloud", force_refresh=True) + + assert len(result) > 0 + assert "qwen3.5:397b" in result + + +# ── Model Picker (list_authenticated_providers) ── + +class TestOllamaCloudModelPicker: + def test_ollama_cloud_shows_model_count(self, tmp_path, monkeypatch): + """Ollama Cloud should show non-zero model count in provider picker.""" + from hermes_cli.model_switch import list_authenticated_providers + + monkeypatch.setenv("HERMES_HOME", str(tmp_path)) + monkeypatch.setenv("OLLAMA_API_KEY", "test-key") + + mock_mdev = { + "ollama-cloud": { + "models": { + "qwen3.5:397b": {"tool_call": True}, + "glm-5": {"tool_call": True}, + } + } + } + with patch("hermes_cli.models.fetch_api_models", return_value=["qwen3.5:397b"]), \ + patch("agent.models_dev.fetch_models_dev", return_value=mock_mdev): + providers = list_authenticated_providers(current_provider="ollama-cloud") + + ollama = next((p for p in providers if p["slug"] == "ollama-cloud"), None) + assert ollama is not None, "ollama-cloud should appear when OLLAMA_API_KEY is set" + assert ollama["total_models"] > 0, "ollama-cloud should show non-zero model count" + + def test_ollama_cloud_not_shown_without_creds(self, monkeypatch): + """Ollama Cloud should not appear without credentials.""" + from hermes_cli.model_switch import list_authenticated_providers + + monkeypatch.delenv("OLLAMA_API_KEY", raising=False) + + providers = list_authenticated_providers(current_provider="openrouter") + ollama = next((p for p in providers if p["slug"] == "ollama-cloud"), None) + assert ollama is None, "ollama-cloud should not appear without OLLAMA_API_KEY" + # ── Merged Model Discovery ──