diff --git a/hermes_cli/model_switch.py b/hermes_cli/model_switch.py index fa83973622a8..4205bdd302e2 100644 --- a/hermes_cli/model_switch.py +++ b/hermes_cli/model_switch.py @@ -2024,6 +2024,16 @@ def list_authenticated_providers( has_creds = False if overlay.auth_type == "aws_sdk": has_creds = _has_aws_sdk_creds_for_listing(hermes_slug) + elif overlay.auth_type == "vertex": + # Vertex authenticates via OAuth2 (service-account JSON / ADC), + # not an API key — mirror the aws_sdk gate above, otherwise the + # provider is silently hidden from the /model picker even when + # fully configured. + try: + from agent.vertex_adapter import has_vertex_credentials + has_creds = has_vertex_credentials() + except Exception as exc: + logger.debug("Vertex credential check failed: %s", exc) elif overlay.extra_env_vars: has_creds = any(os.environ.get(ev) for ev in overlay.extra_env_vars) # Also check api_key_env_vars from PROVIDER_REGISTRY for api_key auth_type diff --git a/hermes_cli/models.py b/hermes_cli/models.py index 2eb1b14cc2af..bea34c44486b 100644 --- a/hermes_cli/models.py +++ b/hermes_cli/models.py @@ -560,6 +560,18 @@ _PROVIDER_MODELS: dict[str, list[str]] = { # Azure Foundry: user-provided endpoint and model. # Empty list because models depend on the endpoint configuration. "azure-foundry": [], + # Google Vertex AI — static curated list. Vertex's OpenAI-compatible + # endpoint has no /models listing route, so without this entry the + # /model picker only ever shows the currently-configured model. + # Model IDs use the "google/" publisher prefix Vertex's openapi + # endpoint expects (see hermes_cli/model_setup_flows.py). + "vertex": [ + "google/gemini-3.1-pro-preview", + "google/gemini-3-pro-preview", + "google/gemini-3.5-flash", + "google/gemini-3-flash-preview", + "google/gemini-3.1-flash-lite-preview", + ], "novita": [ "moonshotai/kimi-k2.5", "minimax/minimax-m2.7", diff --git a/tests/hermes_cli/test_vertex_model_picker.py b/tests/hermes_cli/test_vertex_model_picker.py new file mode 100644 index 000000000000..ccf17e6b4ed9 --- /dev/null +++ b/tests/hermes_cli/test_vertex_model_picker.py @@ -0,0 +1,49 @@ +"""Vertex visibility in the /model picker (follow-up to PR #56688). + +Community verification of the vertex-registration fix found two remaining +gaps that kept the provider invisible/unusable in the /model menu: + +1. ``list_authenticated_providers`` had no credential gate for the + ``vertex`` auth_type (only ``aws_sdk`` was special-cased), so the + provider was silently hidden even when fully configured. +2. Vertex's OpenAI-compatible endpoint has no ``/models`` listing route, + so without a curated ``_PROVIDER_MODELS["vertex"]`` entry the picker + only ever showed the currently-configured model. + +No network calls. +""" + +from __future__ import annotations + +from unittest.mock import patch + +from hermes_cli.model_switch import list_authenticated_providers +from hermes_cli.models import _PROVIDER_MODELS + + +def test_vertex_has_curated_model_list(): + """Vertex has no /models route — the picker needs a static curated list.""" + models = _PROVIDER_MODELS.get("vertex") + assert models, "_PROVIDER_MODELS must have a non-empty 'vertex' entry" + # Vertex's openapi endpoint expects the google/ publisher prefix. + assert all(m.startswith("google/") for m in models) + + +def test_vertex_appears_when_credentials_configured(): + """has_vertex_credentials() == True must surface vertex in the picker.""" + with patch("agent.vertex_adapter.has_vertex_credentials", return_value=True): + providers = list_authenticated_providers(current_provider="openrouter", max_models=50) + + vertex = next((p for p in providers if p["slug"] == "vertex"), None) + assert vertex is not None, "vertex should appear when credentials are configured" + assert vertex["models"], "vertex row must carry the curated model list" + assert "google/gemini-3-pro-preview" in vertex["models"] + + +def test_vertex_hidden_without_credentials(): + """No service-account path / project override → vertex stays hidden.""" + with patch("agent.vertex_adapter.has_vertex_credentials", return_value=False): + providers = list_authenticated_providers(current_provider="openrouter", max_models=50) + + vertex = next((p for p in providers if p["slug"] == "vertex"), None) + assert vertex is None, "vertex should not appear without credentials"