mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-24 10:52:21 +00:00
* feat(providers): remove google-gemini-cli + google-antigravity OAuth providers Google now actively bans accounts for third-party tools that piggyback on Gemini CLI / Antigravity / Code Assist OAuth, and because abuse prevention sits at a backend layer the ban can extend to the entire Google account (Gmail/Drive), with a second violation being permanent. Ref: https://github.com/google-gemini/gemini-cli/discussions/20632 Removes both OAuth inference providers entirely (modules, provider profiles, auth/runtime/config/models wiring, the /gquota Code Assist quota command, the antigravity-cli optional skill, desktop + docs surface in en + zh-Hans). The API-key 'gemini' provider (GOOGLE_API_KEY/GEMINI_API_KEY against generativelanguage.googleapis.com) is unaffected and stays fully supported. * fix(skills): keep the antigravity-cli skill — only the OAuth provider is removed The antigravity-cli optional skill orchestrates the external `agy` binary as a coding-agent tool via the terminal tool — it does NOT wrap Hermes inference through the banned google-antigravity OAuth provider, so it carries none of the account-ban risk that motivated removing that provider. Restore the skill, its docs page, the sidebar entry, and the optional-skills catalog row. The google-antigravity / google-gemini-cli inference providers stay fully removed.
125 lines
5 KiB
Python
125 lines
5 KiB
Python
"""Tests for the unified provider catalog (hermes_cli.provider_catalog).
|
|
|
|
These are invariant tests, not snapshots: they assert the parity *contract*
|
|
between what ``hermes model`` shows (``CANONICAL_PROVIDERS``) and what the
|
|
catalog exposes, plus how each provider's ``auth_type`` maps to a desktop tab —
|
|
never a specific provider count or a frozen vendor list (both change over time).
|
|
"""
|
|
|
|
from hermes_cli.models import CANONICAL_PROVIDERS
|
|
from hermes_cli.provider_catalog import (
|
|
ProviderDescriptor,
|
|
provider_catalog,
|
|
provider_catalog_by_slug,
|
|
tab_for_auth_type,
|
|
)
|
|
|
|
|
|
def test_catalog_covers_every_hermes_model_provider():
|
|
"""PARITY CONTRACT: the catalog == the `hermes model` universe."""
|
|
slugs = {d.slug for d in provider_catalog()}
|
|
for entry in CANONICAL_PROVIDERS:
|
|
assert entry.slug in slugs, (
|
|
f"{entry.slug} is shown in `hermes model` but missing from provider_catalog()"
|
|
)
|
|
|
|
|
|
def test_catalog_has_no_providers_outside_hermes_model():
|
|
"""The catalog must not invent providers `hermes model` doesn't show."""
|
|
canonical = {e.slug for e in CANONICAL_PROVIDERS}
|
|
for d in provider_catalog():
|
|
assert d.slug in canonical, f"{d.slug} in catalog but not in CANONICAL_PROVIDERS"
|
|
|
|
|
|
def test_every_descriptor_lands_on_exactly_one_known_tab():
|
|
for d in provider_catalog():
|
|
assert d.tab in {"keys", "accounts"}, f"{d.slug} has bad tab {d.tab!r}"
|
|
|
|
|
|
def test_descriptor_count_matches_canonical():
|
|
"""One descriptor per canonical entry (no dupes, no drops)."""
|
|
cat = provider_catalog()
|
|
assert len(cat) == len(CANONICAL_PROVIDERS)
|
|
assert len({d.slug for d in cat}) == len(cat)
|
|
|
|
|
|
def test_profileless_providers_still_present():
|
|
"""Providers without a ProviderProfile must still resolve via fallbacks.
|
|
|
|
lmstudio / openai-api / tencent-tokenhub / xai-oauth have no profile on
|
|
main; they exist only as registry + canonical entries. The catalog must
|
|
not require a profile to include a provider.
|
|
"""
|
|
by = provider_catalog_by_slug()
|
|
for slug in ("lmstudio", "openai-api", "tencent-tokenhub", "xai-oauth"):
|
|
assert slug in by, f"{slug} dropped from catalog (profile-less provider)"
|
|
assert by[slug].label, f"{slug} has empty label despite canonical fallback"
|
|
assert by[slug].description, f"{slug} has empty description despite fallback"
|
|
|
|
|
|
def test_api_key_providers_route_to_keys_oauth_to_accounts():
|
|
by = provider_catalog_by_slug()
|
|
# api_key → keys
|
|
assert by["kilocode"].tab == "keys"
|
|
assert by["openai-api"].tab == "keys"
|
|
assert by["copilot-acp"].tab == "accounts"
|
|
|
|
|
|
def test_copilot_surfaces_as_a_provider_with_its_own_token_var():
|
|
"""Regression for the reported bug: a GitHub Copilot login showed up under
|
|
tools, never as a provider, because the shared GITHUB_TOKEN is tool-category.
|
|
|
|
Copilot authenticates via the `copilot`/api_key path, so it belongs on the
|
|
keys tab — but its PRIMARY credential var must be the provider-owned
|
|
COPILOT_GITHUB_TOKEN, not the shared tool-category GITHUB_TOKEN. That is what
|
|
lets the desktop render Copilot as its own provider card.
|
|
"""
|
|
by = provider_catalog_by_slug()
|
|
assert "copilot" in by
|
|
d = by["copilot"]
|
|
assert d.tab == "keys"
|
|
assert d.api_key_env_vars, "Copilot must expose a credential env var"
|
|
assert d.api_key_env_vars[0] == "COPILOT_GITHUB_TOKEN", (
|
|
"Copilot's primary var must be the provider-owned token, not shared GITHUB_TOKEN"
|
|
)
|
|
|
|
|
|
def test_bedrock_routes_to_keys():
|
|
"""Bedrock is aws_sdk (AWS_REGION/AWS_PROFILE), configured on the keys tab."""
|
|
by = provider_catalog_by_slug()
|
|
assert by["bedrock"].tab == "keys"
|
|
|
|
|
|
def test_api_key_providers_expose_a_credential_env_var():
|
|
"""Every keys-tab provider that authenticates via a pasted API key must
|
|
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.
|
|
"""
|
|
exempt = {"custom"}
|
|
for d in provider_catalog():
|
|
if d.auth_type == "api_key" and d.slug not in exempt:
|
|
assert d.api_key_env_vars, f"{d.slug} is api_key but exposes no env var"
|
|
|
|
|
|
def test_order_mirrors_canonical_declaration():
|
|
cat = provider_catalog()
|
|
assert [d.order for d in cat] == list(range(len(cat)))
|
|
assert [d.slug for d in cat] == [e.slug for e in CANONICAL_PROVIDERS]
|
|
|
|
|
|
def test_descriptors_are_provider_descriptor_instances():
|
|
for d in provider_catalog():
|
|
assert isinstance(d, ProviderDescriptor)
|
|
|
|
|
|
def test_tab_for_auth_type_helper():
|
|
assert tab_for_auth_type("api_key") == "keys"
|
|
assert tab_for_auth_type("aws_sdk") == "keys"
|
|
assert tab_for_auth_type("oauth_external") == "accounts"
|
|
assert tab_for_auth_type("oauth_device_code") == "accounts"
|
|
assert tab_for_auth_type("copilot") == "accounts"
|
|
assert tab_for_auth_type("external_process") == "accounts"
|