From dd6d4e2beaebbb4d24bc10ae6b160167181df532 Mon Sep 17 00:00:00 2001 From: HearthCore Date: Thu, 9 Apr 2026 11:00:30 +0200 Subject: [PATCH] fix: resolve copilot provider slug mismatch in model picker The Telegram/Discord model picker showed 'GitHub Copilot (0 models)' because list_authenticated_providers() had a slug mismatch: - HERMES_OVERLAYS key is 'github-copilot' (models.dev ID) - Curated model list key is 'copilot' (Hermes provider ID) - Config uses provider: copilot This caused: 1. curated.get('github-copilot') -> empty (key is 'copilot') 2. is_current check 'github-copilot' == 'copilot' -> False Fix: add reverse mapping from models.dev IDs to Hermes provider IDs so the overlay iteration resolves slugs correctly. Also check credential pool under both keys. --- hermes_cli/model_switch.py | 45 ++++++++++++++++++++++++++++++++------ hermes_cli/models.py | 22 ++++++++++++++++++- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/hermes_cli/model_switch.py b/hermes_cli/model_switch.py index 7d120d94f1bc5..59f748e484149 100644 --- a/hermes_cli/model_switch.py +++ b/hermes_cli/model_switch.py @@ -784,40 +784,71 @@ def list_authenticated_providers( # --- 2. Check Hermes-only providers (nous, openai-codex, copilot) --- from hermes_cli.providers import HERMES_OVERLAYS + + # Build reverse mapping: models.dev ID -> Hermes provider ID + # e.g. "github-copilot" -> "copilot", so overlay keys resolve correctly. + _mdev_to_hermes = {v: k for k, v in PROVIDER_TO_MODELS_DEV.items()} + for pid, overlay in HERMES_OVERLAYS.items(): if pid in seen_slugs: continue + + # Resolve the Hermes provider slug for this overlay. + # Overlay keys may be models.dev IDs (e.g. "github-copilot") while + # the config and curated lists use Hermes IDs (e.g. "copilot"). + hermes_slug = _mdev_to_hermes.get(pid, pid) + if hermes_slug in seen_slugs: + continue + # Check if credentials exist has_creds = False if overlay.extra_env_vars: has_creds = any(os.environ.get(ev) for ev in overlay.extra_env_vars) - if overlay.auth_type in ("oauth_device_code", "oauth_external", "external_process"): + if not has_creds and overlay.auth_type in ("oauth_device_code", "oauth_external", "external_process"): # These use auth stores, not env vars — check for auth.json entries try: from hermes_cli.auth import _load_auth_store store = _load_auth_store() - if store and (pid in store.get("providers", {}) or pid in store.get("credential_pool", {})): + providers_store = store.get("providers", {}) + pool_store = store.get("credential_pool", {}) + # Check both the overlay key AND the Hermes slug + if store and ( + pid in providers_store or pid in pool_store + or hermes_slug in providers_store or hermes_slug in pool_store + ): has_creds = True except Exception as exc: logger.debug("Auth store check failed for %s: %s", pid, exc) + if not has_creds: + # Last resort: check credential pool under Hermes slug too + if not has_creds: + try: + from hermes_cli.auth import _load_auth_store + store = _load_auth_store() + pool_store = store.get("credential_pool", {}) + if hermes_slug in pool_store or pid in pool_store: + has_creds = True + except Exception: + pass if not has_creds: continue - # Use curated list - model_ids = curated.get(pid, []) + # Use curated list — look up by both Hermes slug and overlay key + model_ids = curated.get(hermes_slug, []) or curated.get(pid, []) total = len(model_ids) top = model_ids[:max_models] results.append({ - "slug": pid, - "name": get_label(pid), - "is_current": pid == current_provider, + "slug": hermes_slug, + "name": get_label(hermes_slug), + "is_current": hermes_slug == current_provider or pid == current_provider, "is_user_defined": False, "models": top, "total_models": total, "source": "hermes", }) seen_slugs.add(pid) + seen_slugs.add(hermes_slug) # --- 3. User-defined endpoints from config --- if user_providers and isinstance(user_providers, dict): diff --git a/hermes_cli/models.py b/hermes_cli/models.py index ce89bdeac0307..0d407d7f7a88a 100644 --- a/hermes_cli/models.py +++ b/hermes_cli/models.py @@ -96,19 +96,39 @@ "copilot-acp", ], "copilot": [ + # OpenAI — GPT-5 family "gpt-5.4", "gpt-5.4-mini", - "gpt-5-mini", "gpt-5.3-codex", "gpt-5.2-codex", + "gpt-5.2", + "gpt-5.1", + "gpt-5-mini", + # OpenAI — GPT-4 family "gpt-4.1", "gpt-4o", "gpt-4o-mini", + "gpt-4.1-2025-04-14", + "gpt-4o-2024-11-20", + "gpt-4o-2024-08-06", + "gpt-4o-2024-05-13", + "gpt-4o-mini-2024-07-18", + "gpt-4-o-preview", + "gpt-4", + "gpt-4-0613", + # OpenAI — GPT-3.5 + "gpt-3.5-turbo", + "gpt-3.5-turbo-0613", + # Anthropic — Claude 4.x "claude-opus-4.6", + "claude-opus-4.5", "claude-sonnet-4.6", "claude-sonnet-4.5", + "claude-sonnet-4", "claude-haiku-4.5", + # Google "gemini-2.5-pro", + # xAI "grok-code-fast-1", ], "gemini": [