fix(model_switch): group custom_providers by endpoint in /model picker#9210
Closed
davidvv wants to merge 2 commits into
Closed
fix(model_switch): group custom_providers by endpoint in /model picker#9210davidvv wants to merge 2 commits into
davidvv wants to merge 2 commits into
Conversation
list_authenticated_providers() had two issues with custom_providers:
1. Missing current_base_url parameter — the function had no way to tell
which custom entry matches the active endpoint, so is_current was
always False and the slug was a name-derived hash that did not match
the config provider value (e.g. "custom"). Switching via the picker
therefore resolved wrong credentials.
2. One entry per config item instead of grouping by endpoint — a single
Ollama host with 4 model entries appeared as 4 separate providers in
the picker, each with 1 model, instead of one group with 4 models.
Fix:
- Add current_base_url: str = "" parameter.
- Replace section 4 with a grouping pass: entries are bucketed by
(base_url, api_key). When a bucket matches the active endpoint
(base_url == current_base_url) the slug is set to current_provider
so that credential resolution in the existing switch pipeline works
without any extra lookup. Display name is derived from the first
entry in the group, stripping per-model suffixes ("Ollama — GLM" ->
"Ollama"). Models from all entries in the group are merged into a
single list.
- Pass current_base_url through both list_authenticated_providers call
sites in gateway/run.py (_handle_model_command picker + text list).
Tested against a config with 4 Ollama cloud models on one endpoint:
before: 0 providers shown (TypeError swallowed), picker never opened.
after: 1 provider group "Ollama" with all 4 models, correctly marked
as current, inline keyboard picker opens and switching works.
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes the /model interactive picker failing (often silently) when the active config uses custom_providers pointing at an OpenAI-compatible endpoint (e.g., Ollama), by correctly identifying the active endpoint and grouping custom provider entries by endpoint.
Changes:
- Extend
list_authenticated_providers()to acceptcurrent_base_urland use it to mark the active custom endpoint correctly. - Replace per-entry custom provider listing with grouping by
(base_url, api_key)and merging models into a single picker entry per endpoint. - Pass
current_base_urlintolist_authenticated_providers()from both/modelcall sites ingateway/run.py.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
hermes_cli/model_switch.py |
Adds current_base_url support and groups custom_providers by endpoint for the /model picker output. |
gateway/run.py |
Threads current_base_url through to provider listing so the picker can correctly mark/select the active custom endpoint. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for entry in custom_providers: | ||
| from collections import OrderedDict | ||
| groups: dict = OrderedDict() | ||
| for i, entry in enumerate(custom_providers): |
Comment on lines
+1008
to
+1011
| "models": [], | ||
| "source": "custom_providers", | ||
| "api_url": b_url, | ||
| } |
Comment on lines
+1024
to
+1027
| for m in g["models"]: | ||
| if m not in existing["models"]: | ||
| existing["models"].append(m) | ||
| existing["total_models"] = len(existing["models"]) |
Comment on lines
972
to
+1000
| # --- 4. Saved custom providers from config --- | ||
| # Entries are grouped by (base_url, api_key) so a single Ollama host | ||
| # with several models appears as one provider group in the picker. | ||
| # When the entry's base_url matches the currently active endpoint the | ||
| # slug is set to current_provider so that switching via the picker | ||
| # resolves credentials correctly through the existing runtime provider | ||
| # logic (no extra credential lookup needed). | ||
| if custom_providers and isinstance(custom_providers, list): | ||
| for entry in custom_providers: | ||
| from collections import OrderedDict | ||
| groups: dict = OrderedDict() | ||
| for i, entry in enumerate(custom_providers): | ||
| if not isinstance(entry, dict): | ||
| continue | ||
|
|
||
| display_name = (entry.get("name") or "").strip() | ||
| api_url = ( | ||
| entry.get("base_url", "") | ||
| or entry.get("url", "") | ||
| or entry.get("api", "") | ||
| or "" | ||
| ).strip() | ||
| if not display_name or not api_url: | ||
| b_url = ( | ||
| entry.get("base_url") or entry.get("url") or entry.get("api") or "" | ||
| ).rstrip("/") | ||
| a_key = entry.get("api_key") or "" | ||
| model_id = (entry.get("model") or "").strip() | ||
| if not b_url: | ||
| continue | ||
|
|
||
| slug = custom_provider_slug(display_name) | ||
| if slug in seen_slugs: | ||
| continue | ||
|
|
||
| models_list = [] | ||
| default_model = (entry.get("model") or "").strip() | ||
| if default_model: | ||
| models_list.append(default_model) | ||
|
|
||
| results.append({ | ||
| "slug": slug, | ||
| "name": display_name, | ||
| "is_current": slug == current_provider, | ||
| "is_user_defined": True, | ||
| "models": models_list, | ||
| "total_models": len(models_list), | ||
| "source": "user-config", | ||
| "api_url": api_url, | ||
| }) | ||
| seen_slugs.add(slug) | ||
| if current_base_url and b_url == current_base_url.rstrip("/"): | ||
| slug = current_provider or "custom" | ||
| else: | ||
| slug = custom_provider_slug(entry.get("name") or b_url) | ||
| group_key = (b_url, a_key) | ||
| if group_key not in groups: | ||
| raw_name = (entry.get("name") or "").strip() | ||
| # Strip per-model suffix ("Ollama — GLM 5.1" -> "Ollama") | ||
| display_name = raw_name.split("—")[0].strip() if "—" in raw_name else raw_name |
- Remove unused enumerate() in section 4 loop
- Normalise source value to "user-config" (consistent with existing taxonomy);
update docstring to document this
- Apply max_models cap after merging models in the slug-collision path
(total_models keeps the full deduplicated count)
- Add three regression tests:
test_list_authenticated_providers_groups_same_endpoint
test_list_authenticated_providers_current_endpoint_slug_and_is_current
test_list_authenticated_providers_max_models_cap_after_merge
teknium1
pushed a commit
that referenced
this pull request
Apr 23, 2026
#9210) Multiple custom_providers entries sharing the same base_url + api_key are now grouped into a single picker row. A local Ollama host with per-model display names ("Ollama — GLM 5.1", "Ollama — Qwen3-coder", "Ollama — Kimi K2", "Ollama — MiniMax M2.7") previously produced four near-duplicate picker rows that differed only by suffix; now it appears as one "Ollama" row with four models. Key changes: - Grouping key changed from slug-by-name to (base_url, api_key). Names frequently differ per model while the endpoint stays the same. - When the grouped endpoint matches current_base_url, the row's slug is set to current_provider so picker-driven switches route through the live credential pipeline (no re-resolution needed). - Per-model suffix is stripped from the display name ("Ollama — X" → "Ollama") via em-dash / " - " separators. - Two groups with different api_keys at the same base_url (or otherwise colliding on cleaned name) are disambiguated with a numeric suffix (custom:openai, custom:openai-2) so both stay visible. - current_base_url parameter plumbed through both gateway call sites. Existing #8216, #11499, #13509 regressions covered (dict/list shapes of models:, section-3/section-4 dedup, normalized list-format entries). Salvaged from @davidvv's PR #9210 — the underlying code had diverged ~1400 commits since that PR was opened, so this is a reconstruction of the same approach on current main rather than a clean cherry-pick. Authorship preserved via --author on this commit. Closes #9210
Collaborator
|
Superseded by #14505 which salvaged this fix onto current main. |
ulasbilgen
pushed a commit
to ulasbilgen/hermes-adhd-agent
that referenced
this pull request
May 1, 2026
NousResearch#9210) Multiple custom_providers entries sharing the same base_url + api_key are now grouped into a single picker row. A local Ollama host with per-model display names ("Ollama — GLM 5.1", "Ollama — Qwen3-coder", "Ollama — Kimi K2", "Ollama — MiniMax M2.7") previously produced four near-duplicate picker rows that differed only by suffix; now it appears as one "Ollama" row with four models. Key changes: - Grouping key changed from slug-by-name to (base_url, api_key). Names frequently differ per model while the endpoint stays the same. - When the grouped endpoint matches current_base_url, the row's slug is set to current_provider so picker-driven switches route through the live credential pipeline (no re-resolution needed). - Per-model suffix is stripped from the display name ("Ollama — X" → "Ollama") via em-dash / " - " separators. - Two groups with different api_keys at the same base_url (or otherwise colliding on cleaned name) are disambiguated with a numeric suffix (custom:openai, custom:openai-2) so both stay visible. - current_base_url parameter plumbed through both gateway call sites. Existing NousResearch#8216, NousResearch#11499, NousResearch#13509 regressions covered (dict/list shapes of models:, section-3/section-4 dedup, normalized list-format entries). Salvaged from @davidvv's PR NousResearch#9210 — the underlying code had diverged ~1400 commits since that PR was opened, so this is a reconstruction of the same approach on current main rather than a clean cherry-pick. Authorship preserved via --author on this commit. Closes NousResearch#9210
aj-nt
pushed a commit
to aj-nt/hermes-agent
that referenced
this pull request
May 1, 2026
NousResearch#9210) Multiple custom_providers entries sharing the same base_url + api_key are now grouped into a single picker row. A local Ollama host with per-model display names ("Ollama — GLM 5.1", "Ollama — Qwen3-coder", "Ollama — Kimi K2", "Ollama — MiniMax M2.7") previously produced four near-duplicate picker rows that differed only by suffix; now it appears as one "Ollama" row with four models. Key changes: - Grouping key changed from slug-by-name to (base_url, api_key). Names frequently differ per model while the endpoint stays the same. - When the grouped endpoint matches current_base_url, the row's slug is set to current_provider so picker-driven switches route through the live credential pipeline (no re-resolution needed). - Per-model suffix is stripped from the display name ("Ollama — X" → "Ollama") via em-dash / " - " separators. - Two groups with different api_keys at the same base_url (or otherwise colliding on cleaned name) are disambiguated with a numeric suffix (custom:openai, custom:openai-2) so both stay visible. - current_base_url parameter plumbed through both gateway call sites. Existing NousResearch#8216, NousResearch#11499, NousResearch#13509 regressions covered (dict/list shapes of models:, section-3/section-4 dedup, normalized list-format entries). Salvaged from @davidvv's PR NousResearch#9210 — the underlying code had diverged ~1400 commits since that PR was opened, so this is a reconstruction of the same approach on current main rather than a clean cherry-pick. Authorship preserved via --author on this commit. Closes NousResearch#9210
donald131
pushed a commit
to donald131/hermes-agent
that referenced
this pull request
May 2, 2026
NousResearch#9210) Multiple custom_providers entries sharing the same base_url + api_key are now grouped into a single picker row. A local Ollama host with per-model display names ("Ollama — GLM 5.1", "Ollama — Qwen3-coder", "Ollama — Kimi K2", "Ollama — MiniMax M2.7") previously produced four near-duplicate picker rows that differed only by suffix; now it appears as one "Ollama" row with four models. Key changes: - Grouping key changed from slug-by-name to (base_url, api_key). Names frequently differ per model while the endpoint stays the same. - When the grouped endpoint matches current_base_url, the row's slug is set to current_provider so picker-driven switches route through the live credential pipeline (no re-resolution needed). - Per-model suffix is stripped from the display name ("Ollama — X" → "Ollama") via em-dash / " - " separators. - Two groups with different api_keys at the same base_url (or otherwise colliding on cleaned name) are disambiguated with a numeric suffix (custom:openai, custom:openai-2) so both stay visible. - current_base_url parameter plumbed through both gateway call sites. Existing NousResearch#8216, NousResearch#11499, NousResearch#13509 regressions covered (dict/list shapes of models:, section-3/section-4 dedup, normalized list-format entries). Salvaged from @davidvv's PR NousResearch#9210 — the underlying code had diverged ~1400 commits since that PR was opened, so this is a reconstruction of the same approach on current main rather than a clean cherry-pick. Authorship preserved via --author on this commit. Closes NousResearch#9210
02356abc
pushed a commit
to 02356abc/hermes-agent
that referenced
this pull request
May 14, 2026
NousResearch#9210) Multiple custom_providers entries sharing the same base_url + api_key are now grouped into a single picker row. A local Ollama host with per-model display names ("Ollama — GLM 5.1", "Ollama — Qwen3-coder", "Ollama — Kimi K2", "Ollama — MiniMax M2.7") previously produced four near-duplicate picker rows that differed only by suffix; now it appears as one "Ollama" row with four models. Key changes: - Grouping key changed from slug-by-name to (base_url, api_key). Names frequently differ per model while the endpoint stays the same. - When the grouped endpoint matches current_base_url, the row's slug is set to current_provider so picker-driven switches route through the live credential pipeline (no re-resolution needed). - Per-model suffix is stripped from the display name ("Ollama — X" → "Ollama") via em-dash / " - " separators. - Two groups with different api_keys at the same base_url (or otherwise colliding on cleaned name) are disambiguated with a numeric suffix (custom:openai, custom:openai-2) so both stay visible. - current_base_url parameter plumbed through both gateway call sites. Existing NousResearch#8216, NousResearch#11499, NousResearch#13509 regressions covered (dict/list shapes of models:, section-3/section-4 dedup, normalized list-format entries). Salvaged from @davidvv's PR NousResearch#9210 — the underlying code had diverged ~1400 commits since that PR was opened, so this is a reconstruction of the same approach on current main rather than a clean cherry-pick. Authorship preserved via --author on this commit. Closes NousResearch#9210
gweeteve
pushed a commit
to gweeteve/hermes-agent
that referenced
this pull request
Jun 2, 2026
NousResearch#9210) Multiple custom_providers entries sharing the same base_url + api_key are now grouped into a single picker row. A local Ollama host with per-model display names ("Ollama — GLM 5.1", "Ollama — Qwen3-coder", "Ollama — Kimi K2", "Ollama — MiniMax M2.7") previously produced four near-duplicate picker rows that differed only by suffix; now it appears as one "Ollama" row with four models. Key changes: - Grouping key changed from slug-by-name to (base_url, api_key). Names frequently differ per model while the endpoint stays the same. - When the grouped endpoint matches current_base_url, the row's slug is set to current_provider so picker-driven switches route through the live credential pipeline (no re-resolution needed). - Per-model suffix is stripped from the display name ("Ollama — X" → "Ollama") via em-dash / " - " separators. - Two groups with different api_keys at the same base_url (or otherwise colliding on cleaned name) are disambiguated with a numeric suffix (custom:openai, custom:openai-2) so both stay visible. - current_base_url parameter plumbed through both gateway call sites. Existing NousResearch#8216, NousResearch#11499, NousResearch#13509 regressions covered (dict/list shapes of models:, section-3/section-4 dedup, normalized list-format entries). Salvaged from @davidvv's PR NousResearch#9210 — the underlying code had diverged ~1400 commits since that PR was opened, so this is a reconstruction of the same approach on current main rather than a clean cherry-pick. Authorship preserved via --author on this commit. Closes NousResearch#9210
Egavasyug
pushed a commit
to Egavasyug/hermes-agent
that referenced
this pull request
Jun 10, 2026
NousResearch#9210) Multiple custom_providers entries sharing the same base_url + api_key are now grouped into a single picker row. A local Ollama host with per-model display names ("Ollama — GLM 5.1", "Ollama — Qwen3-coder", "Ollama — Kimi K2", "Ollama — MiniMax M2.7") previously produced four near-duplicate picker rows that differed only by suffix; now it appears as one "Ollama" row with four models. Key changes: - Grouping key changed from slug-by-name to (base_url, api_key). Names frequently differ per model while the endpoint stays the same. - When the grouped endpoint matches current_base_url, the row's slug is set to current_provider so picker-driven switches route through the live credential pipeline (no re-resolution needed). - Per-model suffix is stripped from the display name ("Ollama — X" → "Ollama") via em-dash / " - " separators. - Two groups with different api_keys at the same base_url (or otherwise colliding on cleaned name) are disambiguated with a numeric suffix (custom:openai, custom:openai-2) so both stay visible. - current_base_url parameter plumbed through both gateway call sites. Existing NousResearch#8216, NousResearch#11499, NousResearch#13509 regressions covered (dict/list shapes of models:, section-3/section-4 dedup, normalized list-format entries). Salvaged from @davidvv's PR NousResearch#9210 — the underlying code had diverged ~1400 commits since that PR was opened, so this is a reconstruction of the same approach on current main rather than a clean cherry-pick. Authorship preserved via --author on this commit. Closes NousResearch#9210
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the
/modelcommand producing no output (or no picker) when the active configuration usescustom_providerswith an Ollama or other OpenAI-compatible endpoint.Root cause
list_authenticated_providers()had two problems in section 4 (custom_providers handling):1. Missing
current_base_urlparameterThe function had no way to tell which custom entry corresponds to the active endpoint. As a result
is_currentwas alwaysFalseand the slug was a name-derived hash (e.g.ollama-glm-5-1) that did not match the configmodel.providervalue (custom). Switching via the picker therefore sent the wrong provider slug toswitch_model, causing credential resolution to fail.2. One picker entry per config entry instead of grouping by endpoint
A single Ollama host with 4 model entries appeared as 4 separate single-model providers in the picker instead of one group with 4 models to choose from.
3. Silent TypeError at call site (same symptom on older gateway builds)
Both call sites in
gateway/run.pyalready passedcustom_providers=...as a keyword arg, but the old signature did not declare that parameter — resulting in aTypeErrorthat was silently swallowed by the surroundingtry/except, leavingproviders = []and the picker never opening.Fix
current_base_url: str =parameter.(base_url, api_key). When a bucket matches the active endpoint (base_url == current_base_url) the slug is set tocurrent_providerso that credential resolution in the existingswitch_modelpipeline works without any extra lookup.Ollama — GLM 5.1→Ollama). All model names from matching entries are merged into a single list.current_base_urlthrough bothlist_authenticated_providerscall sites ingateway/run.py.Result
Before: 0 providers shown, picker never opened (TypeError swallowed).
After: 1 provider group Ollama with all 4 models, correctly marked as current, inline keyboard picker opens and model switching works.