hermes - ✅(Solved) Fix feat(model-picker): add `picker_providers` config to filter which providers appear in /model [1 pull requests, 2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#12655Fetched 2026-04-20 12:17:34
View on GitHub
Comments
2
Participants
2
Timeline
2
Reactions
0
Timeline (top)
commented ×2

Root Cause

Our setup uses a single custom provider (AgentOne) with 23 models. The picker also shows Anthropic (8 models) because Claude Code OAuth credentials exist on disk (~/.claude/.credentials.json). This is confusing — we never use Anthropic through Hermes.

Fix Action

Fix / Workaround

There is no way to hide the Anthropic entry. The only workaround would be deleting the Claude Code credentials file, which breaks Claude Code itself.

PR fix notes

PR #13561: feat(cli): add list_picker_providers for credential-filtered picker

Description (problem / solution / changelog)

What

Adds list_picker_providers() — a thin wrapper around list_authenticated_providers() that post-processes the result so the interactive /model picker (Telegram, Discord) only surfaces models the user can actually call. _handle_model_command() in gateway/run.py switches to the new helper for the interactive picker payload only.

Why

Two failure modes today when a user opens /model:

  1. Stale OpenRouter IDsOPENROUTER_MODELS is a static snapshot. When OpenRouter drops or renames a model upstream between Hermes releases, the picker still offers it; tapping it fails at call time.
  2. Empty provider rows — a credential-pool slug can exist with no actual key material behind it (e.g. leftover from a prior provider that's been env-wiped). The picker shows the row, the user taps, nothing happens.

The filter fixes both without changing list_authenticated_providers(), which is also consumed by the CLI text fallback where the unfiltered view is arguably still the right default.

How it works

  • For the openrouter row, models are replaced with the output of fetch_openrouter_models(), which already cross-checks the curated snapshot against the live OpenRouter catalog.
  • 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.

Typed /model <name> and the text fallback list stay on list_authenticated_providers(), so nothing is hidden from power users or platforms without an interactive picker.

How to test

pytest tests/hermes_cli/test_list_picker_providers.py -v

Nine focused unit tests covering:

  • OpenRouter models replaced with live catalog
  • Fallback to base models when the live fetch raises
  • Empty live catalog drops the OpenRouter row
  • Non-OpenRouter rows passed through unchanged
  • Empty models row dropped
  • Custom endpoint with api_url kept when models empty
  • User-defined row without api_url and empty models dropped
  • max_models caps OpenRouter live output
  • All kwargs forwarded to the base function

Manual test: open /model in Telegram with a setup that exercises each branch — OpenRouter + a direct provider + a user-defined custom endpoint.

Platforms tested

  • macOS (Darwin 25.4.0, Python 3.11)

Notes

  • No config/schema changes, no new dependencies, no migration path.
  • The branch is based on an older main (fork was behind by ~800 commits and syncing required a workflow OAuth scope I didn't have). The three files touched cherry-picked onto current main cleanly in my local worktree — happy to rebase on request.

Changed files

  • gateway/run.py (modified, +2/-1)
  • hermes_cli/model_switch.py (modified, +56/-0)
  • tests/hermes_cli/test_list_picker_providers.py (added, +216/-0)

Code Example

custom:agentone    models=23  source=user-config
anthropic          models= 8  source=hermes

---

model:
  default: glm-5-zai
  provider: custom
  picker_providers:
    - custom        # only show custom providers

---

if picker_providers:
       allowed = {p.lower() for p in picker_providers}
       results = [r for r in results if r["slug"].lower() in allowed]
RAW_BUFFERClick to expand / collapse

Feature Description

Add a picker_providers config option under the model: key to control which providers appear in the /model picker output. This allows users who exclusively use custom endpoints to hide built-in provider entries (Anthropic, OpenRouter, etc.) that auto-detect via OAuth/env credentials.

Motivation

Our setup uses a single custom provider (AgentOne) with 23 models. The picker also shows Anthropic (8 models) because Claude Code OAuth credentials exist on disk (~/.claude/.credentials.json). This is confusing — we never use Anthropic through Hermes.

Current picker output:

custom:agentone    models=23  source=user-config
anthropic          models= 8  source=hermes

There is no way to hide the Anthropic entry. The only workaround would be deleting the Claude Code credentials file, which breaks Claude Code itself.

Proposed Solution

Add a picker_providers list to config.yaml:

model:
  default: glm-5-zai
  provider: custom
  picker_providers:
    - custom        # only show custom providers

Behavior

picker_providers valueEffect
Not set / nullShow all providers (current behavior, backward compatible)
["custom"]Only show custom_providers entries
["anthropic", "custom"]Only show Anthropic + custom entries
[]Show none (edge case, treated as not set)

Implementation

In hermes_cli/model_switch.py, function list_authenticated_providers():

  1. Read picker_providers from config (passed through or read from config)
  2. After building the full results list, before the final sort (around line 1145), filter results:
    if picker_providers:
        allowed = {p.lower() for p in picker_providers}
        results = [r for r in results if r["slug"].lower() in allowed]
  3. The filter matches against provider slug (custom:agentone becomes custom, anthropic, etc.)

Files to modify

  • hermes_cli/model_switch.py — add filter in list_authenticated_providers(), accept picker_providers param
  • hermes_cli/config.py — add picker_providers to default config schema (default: null)
  • Callers of list_authenticated_providers() — pass the new param from config

Size estimate

~15-20 lines of functional code + config schema entry. No new dependencies.

Alternatives Considered

  1. hide_builtin_providers: true — simpler but less flexible (binary toggle)
  2. Credential-based suppression — skip providers whose creds come from external OAuth files rather than explicit config. Too magical and fragile.
  3. Upstream contribution — searched upstream issues + PRs, no existing request for provider filtering in the picker. Related but different: PR #7267 hides providers with 0 models.

Search Confirmation

Searched this repo:

  • gh search issues "picker provider filter" — no matches
  • gh search prs "picker provider" — 10 results, all about dedup/discovery, none about filtering by config
  • Related: PR #7267 (hide 0-model providers), PRs #10327/#12321 (dedup entries)

extent analysis

TL;DR

To hide unwanted providers in the model picker, add a picker_providers config option to config.yaml and filter the results in hermes_cli/model_switch.py.

Guidance

  • Add picker_providers to config.yaml under the model key to control which providers appear in the picker output.
  • Modify hermes_cli/model_switch.py to filter results based on the picker_providers config option.
  • Update hermes_cli/config.py to include picker_providers in the default config schema.
  • Pass the picker_providers param to list_authenticated_providers() from its callers.

Example

if picker_providers:
    allowed = {p.lower() for p in picker_providers}
    results = [r for r in results if r["slug"].lower() in allowed]

Notes

This solution assumes that the picker_providers config option is correctly read and passed to list_authenticated_providers(). Additionally, this fix may not be backward compatible if other parts of the code rely on the current behavior of showing all providers.

Recommendation

Apply the proposed solution by adding the picker_providers config option and modifying the necessary code files, as it provides a flexible way to control which providers appear in the picker output.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix feat(model-picker): add `picker_providers` config to filter which providers appear in /model [1 pull requests, 2 comments, 2 participants]