mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-08 03:01:47 +00:00
feat(cli): add list_picker_providers for credential-filtered picker
The Telegram/Discord /model pickers currently call list_authenticated_providers(), which returns every provider whose credentials resolve locally and every model in its curated snapshot. Two failure modes fall out: - OpenRouter rows can include IDs the live catalog no longer carries. - Provider rows can surface with zero callable models (e.g. a slug whose credential pool entry exists but has nothing behind it). list_picker_providers() wraps the base function and post-processes the result so the interactive picker only shows models the user can actually select: - OpenRouter's models come from fetch_openrouter_models() (live-catalog filtered against the curated OPENROUTER_MODELS snapshot). - Rows with an empty models list are dropped, except custom endpoints (is_user_defined=True with an api_url) where the user may enter model ids manually. - All other fields pass through unchanged. The gateway /model handler switches to the new helper for the interactive picker payload only. Typed /model <name> and the text fallback list stay on list_authenticated_providers() so nothing is hidden from power users or platforms without a picker. Covered by nine focused unit tests in tests/hermes_cli/test_list_picker_providers.py. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cc2c820975
commit
60235dba5e
3 changed files with 274 additions and 1 deletions
|
|
@ -1683,3 +1683,59 @@ def list_authenticated_providers(
|
|||
results.sort(key=lambda r: (not r["is_current"], -r["total_models"]))
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def list_picker_providers(
|
||||
current_provider: str = "",
|
||||
user_providers: dict = None,
|
||||
custom_providers: list | None = None,
|
||||
max_models: int = 8,
|
||||
) -> List[dict]:
|
||||
"""Interactive-picker variant of :func:`list_authenticated_providers`.
|
||||
|
||||
Post-processes the base list so the ``/model`` picker (Telegram/Discord
|
||||
inline keyboards) only surfaces models that are actually callable in the
|
||||
current install:
|
||||
|
||||
- OpenRouter's model list is replaced with the output of
|
||||
:func:`hermes_cli.models.fetch_openrouter_models`, which filters the
|
||||
curated ``OPENROUTER_MODELS`` snapshot against the live OpenRouter
|
||||
catalog. IDs the live catalog no longer carries drop out, so the
|
||||
picker never offers a model the user can't call.
|
||||
- Provider rows whose model list ends up empty are dropped, except
|
||||
custom endpoints (``is_user_defined=True`` with an ``api_url``) where
|
||||
the user may supply their own model set through config.
|
||||
|
||||
All other providers and metadata fields are passed through unchanged.
|
||||
The typed ``/model <name>`` path is unaffected -- only the interactive
|
||||
picker payload is narrowed.
|
||||
"""
|
||||
from hermes_cli.models import fetch_openrouter_models
|
||||
|
||||
providers = list_authenticated_providers(
|
||||
current_provider=current_provider,
|
||||
user_providers=user_providers,
|
||||
custom_providers=custom_providers,
|
||||
max_models=max_models,
|
||||
)
|
||||
|
||||
filtered: List[dict] = []
|
||||
for p in providers:
|
||||
slug = str(p.get("slug", "")).lower()
|
||||
if slug == "openrouter":
|
||||
try:
|
||||
live = fetch_openrouter_models()
|
||||
live_ids = [mid for mid, _ in live]
|
||||
except Exception:
|
||||
live_ids = list(p.get("models", []))
|
||||
p = dict(p)
|
||||
p["models"] = live_ids[:max_models]
|
||||
p["total_models"] = len(live_ids)
|
||||
|
||||
has_models = bool(p.get("models"))
|
||||
is_custom_endpoint = bool(p.get("is_user_defined")) and bool(p.get("api_url"))
|
||||
if not has_models and not is_custom_endpoint:
|
||||
continue
|
||||
filtered.append(p)
|
||||
|
||||
return filtered
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue