diff --git a/hermes_cli/provider_catalog.py b/hermes_cli/provider_catalog.py index a164ac84ce..b7323894d1 100644 --- a/hermes_cli/provider_catalog.py +++ b/hermes_cli/provider_catalog.py @@ -66,6 +66,7 @@ class ProviderDescriptor: base_url_env_var: str # base-URL override env var (may be "") signup_url: str # signup / console URL (may be "") order: int # CANONICAL_PROVIDERS index — mirrors `hermes model` + keyless: bool = False # served anonymously — no credential exists to configure def tab_for_auth_type(auth_type: str) -> str: @@ -171,6 +172,10 @@ def provider_catalog() -> list[ProviderDescriptor]: base_url_env_var=base_url_var, signup_url=signup_url, order=order, + # Keyless providers (e.g. opencode-free) are served + # anonymously: there is no credential to configure, so the + # GUI renders no key card and contract tests exempt them. + keyless=bool(getattr(overlay, "keyless", False)), ) ) return out diff --git a/hermes_cli/providers.py b/hermes_cli/providers.py index 2533afdcde..d8b8e28774 100644 --- a/hermes_cli/providers.py +++ b/hermes_cli/providers.py @@ -41,6 +41,7 @@ class HermesOverlay: extra_env_vars: Tuple[str, ...] = () # env vars models.dev doesn't list base_url_override: str = "" # override if models.dev URL is wrong/missing base_url_env_var: str = "" # env var for user-custom base URL + keyless: bool = False # served anonymously — no credential exists to configure HERMES_OVERLAYS: Dict[str, HermesOverlay] = { @@ -160,6 +161,7 @@ HERMES_OVERLAYS: Dict[str, HermesOverlay] = { transport="openai_chat", is_aggregator=True, base_url_override="https://opencode.ai/zen/v1", + keyless=True, ), "kilo": HermesOverlay( transport="openai_chat", diff --git a/tests/hermes_cli/test_provider_catalog.py b/tests/hermes_cli/test_provider_catalog.py index aff6e550e7..89fe2a31f6 100644 --- a/tests/hermes_cli/test_provider_catalog.py +++ b/tests/hermes_cli/test_provider_catalog.py @@ -57,13 +57,14 @@ def test_api_key_providers_expose_a_credential_env_var(): surface at least one env var to write the key into (otherwise the GUI can't configure it). - Exemptions: ``aws_sdk`` (bedrock — uses AWS_REGION/AWS_PROFILE) and the - ``custom`` bring-your-own-endpoint pseudo-provider, which is configured - inline via the local-endpoint flow rather than a fixed env var. + Exemptions: ``aws_sdk`` (bedrock — uses AWS_REGION/AWS_PROFILE), the + ``custom`` bring-your-own-endpoint pseudo-provider (configured inline via + the local-endpoint flow), and keyless providers (``d.keyless`` — e.g. + opencode-free, served anonymously: there is no credential to write). """ exempt = {"custom"} for d in provider_catalog(): - if d.auth_type == "api_key" and d.slug not in exempt: + if d.auth_type == "api_key" and d.slug not in exempt and not d.keyless: assert d.api_key_env_vars, f"{d.slug} is api_key but exposes no env var" diff --git a/tests/hermes_cli/test_provider_parity.py b/tests/hermes_cli/test_provider_parity.py index 017c46b88b..f5961184cd 100644 --- a/tests/hermes_cli/test_provider_parity.py +++ b/tests/hermes_cli/test_provider_parity.py @@ -31,7 +31,11 @@ HEADERS = {"X-Hermes-Session-Token": _SESSION_TOKEN} # derived from the catalog so any future virtual provider is covered without a # hardcoded slug. _VIRTUAL = {d.slug for d in provider_catalog() if d.auth_type == "virtual"} -_EXEMPT = {"custom"} | _VIRTUAL +# Keyless providers (opencode-free) are served anonymously: no credential +# exists, so there is nothing to configure on either Providers tab. Derived +# from the catalog flag so any future keyless provider is covered. +_KEYLESS = {d.slug for d in provider_catalog() if d.keyless} +_EXEMPT = {"custom"} | _VIRTUAL | _KEYLESS # Providers that legitimately offer BOTH auth methods and so intentionally # appear on both desktop tabs (an API-key card AND an account sign-in card).