From d59b79fadd1e9edd7afc5c679cc3b143838e7c01 Mon Sep 17 00:00:00 2001 From: Austin Pickett Date: Fri, 17 Jul 2026 19:16:48 -0400 Subject: [PATCH] fix(model-picker): show exhausted-pool providers in interactive /model picker (#66584) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Salvages #66257 by @oppih (CI attribution check blocked the external branch from merging). When a provider's credential pool has entries but all are temporarily rate-limited (exhausted), list_authenticated_providers() excluded the provider from the interactive /model picker. Rate limits are per-model for many providers (e.g. Google Gemini), so an exhausted key for model-A may still work for model-B — the user should still be able to select a different model under the same provider. Adds a for_picker flag to list_authenticated_providers() that relaxes the credential-pool availability check for the picker path only, falling back to pool.has_credentials() when the pool has entries but none are currently available. The runtime resolution path (get_authenticated_provider_slugs) is unchanged, preserving the #45759 invariant that exhausted pools do not count as authenticated. Co-authored-by: oppih --- hermes_cli/model_switch.py | 16 ++++++++++++++++ ..._authenticated_providers_exhausted_pool.py | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/hermes_cli/model_switch.py b/hermes_cli/model_switch.py index d0071c7489ad..f83089b83853 100644 --- a/hermes_cli/model_switch.py +++ b/hermes_cli/model_switch.py @@ -1496,6 +1496,7 @@ def list_authenticated_providers( refresh: bool = False, probe_custom_providers: bool = True, probe_current_custom_provider: bool = False, + for_picker: bool = False, ) -> List[dict]: """Detect which providers have credentials and list their curated models. @@ -1843,6 +1844,20 @@ def list_authenticated_providers( try: if _credential_pool_is_usable(hermes_slug): has_creds = True + elif for_picker: + # For the interactive /model picker, also show providers + # whose credential pool has entries but all are temporarily + # rate-limited. Rate limits are per-model for many + # providers (e.g. Google Gemini) — switching to a different + # model under the same provider may work even when all keys + # are in cooldown. + try: + from agent.credential_pool import load_pool + _pool = load_pool(hermes_slug) + if _pool.has_credentials(): + has_creds = True + except Exception: + pass except Exception as exc: logger.debug("Credential pool check failed for %s: %s", hermes_slug, exc) # Fallback: check external credential files directly. @@ -2488,6 +2503,7 @@ def list_picker_providers( custom_providers=custom_providers, max_models=max_models, current_model=current_model, + for_picker=True, ) if include_moa: providers = _prepend_moa_picker_provider(providers, current_provider=current_provider) diff --git a/tests/hermes_cli/test_authenticated_providers_exhausted_pool.py b/tests/hermes_cli/test_authenticated_providers_exhausted_pool.py index eeb1a44056c3..988de9c72f10 100644 --- a/tests/hermes_cli/test_authenticated_providers_exhausted_pool.py +++ b/tests/hermes_cli/test_authenticated_providers_exhausted_pool.py @@ -95,3 +95,22 @@ def test_opaque_legacy_pool_value_stays_visible(monkeypatch): ) assert _credential_pool_is_usable("opencode-go", raw_pool_present=True) + + +def test_picker_shows_exhausted_pool_provider(monkeypatch): + """The interactive picker must include providers whose credential pool + entries are all exhausted, so the user can still switch to a different + model under the same provider.""" + from hermes_cli.model_switch import list_picker_providers + + _patch_opencode_pool(monkeypatch, available=False) + providers = list_picker_providers( + current_provider="alibaba", + user_providers={}, + custom_providers=[], + ) + slugs = [p["slug"] for p in providers] + assert "opencode-go" in slugs, ( + "Picker must show exhausted-pool providers so the user can select " + "a different model under the same provider" + )