From ec9bf9e378b7f78b7187ce2412ad880d20aa6e95 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Sun, 12 Apr 2026 00:19:32 +0000 Subject: [PATCH] feat(model-picker): group custom_providers by name into a single row per provider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The /model picker currently renders one row per ``custom_providers`` entry. When several entries share the same provider name (e.g. four ``ollama-cloud`` entries for ``qwen3-coder``, ``glm-5.1``, ``kimi-k2``, ``minimax-m2.7``), users see four separate "Ollama Cloud" rows in the picker, which is confusing UX — there is only one Ollama Cloud provider, so there should be one row containing four models. This PR groups ``custom_providers`` entries that share the same provider name into a single picker row while keeping entries with distinct names as separate rows. So: * Four entries named ``Ollama Cloud`` → one "Ollama Cloud" row with four models inside. * One entry named ``Ollama Cloud`` and one named ``Moonshot`` → two separate rows, one model each. Implementation -------------- Replaces the single-pass loop in ``list_authenticated_providers()`` with a two-pass approach: 1. First pass: build an ``OrderedDict`` keyed by ``custom_provider_slug(name)``, accumulating ``models`` per group while preserving discovery order. 2. Second pass: iterate the groups and append one result row per group, skipping any slug that already appeared in an earlier provider source (the existing ``seen_slugs`` guard). Insertion order is preserved via ``OrderedDict``, so providers and their models still appear in the order the user listed them in ``custom_providers``. No new dependencies. Co-Authored-By: Claude Opus 4.6 (1M context) --- hermes_cli/model_switch.py | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/hermes_cli/model_switch.py b/hermes_cli/model_switch.py index c2b8b6e65..c777527f2 100644 --- a/hermes_cli/model_switch.py +++ b/hermes_cli/model_switch.py @@ -1027,7 +1027,17 @@ def list_authenticated_providers( }) # --- 4. Saved custom providers from config --- + # Each ``custom_providers`` entry represents one model under a named + # provider. Entries sharing the same provider name are grouped into a + # single picker row so that e.g. four Ollama Cloud entries + # (qwen3-coder, glm-5.1, kimi-k2, minimax-m2.7) appear as one + # "Ollama Cloud" row with four models inside instead of four + # duplicate "Ollama Cloud" rows. Entries with distinct provider names + # still produce separate rows (e.g. Ollama Cloud vs Moonshot). if custom_providers and isinstance(custom_providers, list): + from collections import OrderedDict + + groups: "OrderedDict[str, dict]" = OrderedDict() for entry in custom_providers: if not isinstance(entry, dict): continue @@ -1043,23 +1053,28 @@ def list_authenticated_providers( continue slug = custom_provider_slug(display_name) + if slug not in groups: + groups[slug] = { + "name": display_name, + "api_url": api_url, + "models": [], + } + default_model = (entry.get("model") or "").strip() + if default_model and default_model not in groups[slug]["models"]: + groups[slug]["models"].append(default_model) + + for slug, grp in groups.items(): 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, + "name": grp["name"], "is_current": slug == current_provider, "is_user_defined": True, - "models": models_list, - "total_models": len(models_list), + "models": grp["models"], + "total_models": len(grp["models"]), "source": "user-config", - "api_url": api_url, + "api_url": grp["api_url"], }) seen_slugs.add(slug)