fix(model-picker): show exhausted-pool providers in interactive /model picker (#66584)

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 <oppih@users.noreply.github.com>
This commit is contained in:
Austin Pickett 2026-07-17 19:16:48 -04:00 committed by GitHub
parent 5122ddd478
commit d59b79fadd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 35 additions and 0 deletions

View file

@ -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)

View file

@ -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"
)