feat(model-picker): show short description on grouped provider rows

The 7 consolidated provider families (OpenAI, xAI Grok, GitHub Copilot,
Google Gemini, Kimi / Moonshot, MiniMax, OpenCode) collapse to one
top-level picker row. Previously that row showed only the bare group
label (e.g. `OpenAI ▸`); now it carries a short blurb describing the
endpoints folded inside (e.g. `OpenAI ▸ (Codex CLI or direct OpenAI API)`).

- models.py: extend PROVIDER_GROUPS tuples to (label, description, members);
  group_providers() emits the description on group rows.
- main.py: CLI picker renders `<label> ▸ (<description>)` for group rows.
- telegram.py: update the group tuple unpack (button text keeps the member
  count, which fits inline keyboards better than a long blurb).
- tests: assert every group has a non-empty description and the fold emits it.

Member-specific detail still lives in each member's tui_desc and shows in
the drill-down sub-picker. Slug identity, --provider, /model paths unchanged.
This commit is contained in:
kshitijk4poor
2026-05-31 13:35:01 +05:30
committed by Teknium
parent 47d2d05892
commit 84d82453ae
4 changed files with 28 additions and 19 deletions

View File

@ -3126,7 +3126,7 @@ class TelegramAdapter(BasePlatformAdapter):
group_id = data[4:]
try:
from hermes_cli.models import PROVIDER_GROUPS
_label, member_slugs = PROVIDER_GROUPS.get(group_id, ("", []))
_label, _desc, member_slugs = PROVIDER_GROUPS.get(group_id, ("", "", []))
except Exception:
_label, member_slugs = "", []

View File

@ -2434,7 +2434,8 @@ def select_provider_and_model(args=None):
for row in grouped_rows:
if row["kind"] == "group":
gid = row["group_id"]
label = f"{row['label']}"
group_desc = row.get("description", "")
label = f"{row['label']} ▸ ({group_desc})" if group_desc else f"{row['label']}"
key = f"group:{gid}"
is_active = bool(active_group) and gid == active_group
members = row["members"]

View File

@ -969,23 +969,26 @@ _PROVIDER_LABELS["custom"] = "Custom endpoint" # special case: not a named prov
# display affordance; ``group_providers()`` is the single fold used by all
# three picker surfaces so they stay consistent.
#
# group_id -> (display_label, [member_slug, ...])
# group_id -> (display_label, group_description, [member_slug, ...])
#
# ``group_description`` is a short blurb shown on the collapsed top-level group
# row in the interactive pickers (alongside the label). Member-specific detail
# lives in each member's ``tui_desc`` and shows in the drill-down sub-picker.
# Member order is the order shown inside the group submenu.
# ---------------------------------------------------------------------------
PROVIDER_GROUPS: dict[str, tuple[str, list[str]]] = {
"kimi": ("Kimi / Moonshot", ["kimi-coding", "kimi-coding-cn"]),
"minimax": ("MiniMax", ["minimax", "minimax-oauth", "minimax-cn"]),
"xai": ("xAI Grok", ["xai", "xai-oauth"]),
"google": ("Google Gemini", ["gemini", "google-gemini-cli"]),
"openai": ("OpenAI", ["openai-codex", "openai-api"]),
"opencode": ("OpenCode", ["opencode-zen", "opencode-go"]),
"copilot": ("GitHub Copilot", ["copilot", "copilot-acp"]),
PROVIDER_GROUPS: dict[str, tuple[str, str, list[str]]] = {
"kimi": ("Kimi / Moonshot", "Coding Plan, Moonshot global & China endpoints", ["kimi-coding", "kimi-coding-cn"]),
"minimax": ("MiniMax", "Global, OAuth Coding Plan & China endpoints", ["minimax", "minimax-oauth", "minimax-cn"]),
"xai": ("xAI Grok", "Direct API or SuperGrok / Premium+ OAuth", ["xai", "xai-oauth"]),
"google": ("Google Gemini", "AI Studio API or OAuth + Code Assist", ["gemini", "google-gemini-cli"]),
"openai": ("OpenAI", "Codex CLI or direct OpenAI API", ["openai-codex", "openai-api"]),
"opencode": ("OpenCode", "Zen pay-as-you-go or Go subscription", ["opencode-zen", "opencode-go"]),
"copilot": ("GitHub Copilot", "GitHub token API or copilot --acp process", ["copilot", "copilot-acp"]),
}
# Reverse index: member slug -> group_id. Built once at import.
_SLUG_TO_GROUP: dict[str, str] = {
slug: gid for gid, (_label, members) in PROVIDER_GROUPS.items() for slug in members
slug: gid for gid, (_label, _desc, members) in PROVIDER_GROUPS.items() for slug in members
}
@ -1006,7 +1009,7 @@ def group_providers(slugs):
{"kind": "single", "slug": <slug>} # ungrouped, or
# 1-member group
{"kind": "group", "group_id": <gid>, "label": <label>,
"members": [<slug>, ...]} # 2+ members
"description": <desc>, "members": [<slug>, ...]} # 2+ members
Rules:
* A group row appears at the position of its FIRST present member, in
@ -1023,7 +1026,7 @@ def group_providers(slugs):
seen: set[str] = set()
# Which present members each group has, in declaration order.
group_members: dict[str, list[str]] = {}
for gid, (_label, members) in PROVIDER_GROUPS.items():
for gid, (_label, _desc, members) in PROVIDER_GROUPS.items():
present = [m for m in members if m in set(slugs)]
if present:
group_members[gid] = present
@ -1046,9 +1049,10 @@ def group_providers(slugs):
if len(members) <= 1:
rows.append({"kind": "single", "slug": members[0]})
else:
label, _ = PROVIDER_GROUPS[gid]
label, desc, _ = PROVIDER_GROUPS[gid]
rows.append(
{"kind": "group", "group_id": gid, "label": label, "members": list(members)}
{"kind": "group", "group_id": gid, "label": label,
"description": desc, "members": list(members)}
)
return rows

View File

@ -29,8 +29,9 @@ def test_groups_reference_real_canonical_slugs():
"""Every group member must be an actual provider slug. Guards typos and
stale group entries after a provider is renamed/removed."""
canonical = {p.slug for p in CANONICAL_PROVIDERS}
for gid, (label, members) in PROVIDER_GROUPS.items():
for gid, (label, desc, members) in PROVIDER_GROUPS.items():
assert label, f"group {gid} has empty label"
assert desc, f"group {gid} has empty description"
assert len(members) >= 1
for m in members:
assert m in canonical, f"group {gid} member {m!r} is not a canonical slug"
@ -39,14 +40,14 @@ def test_groups_reference_real_canonical_slugs():
def test_member_slugs_are_unique_across_groups():
"""A slug may belong to at most one group."""
seen = {}
for gid, (_label, members) in PROVIDER_GROUPS.items():
for gid, (_label, _desc, members) in PROVIDER_GROUPS.items():
for m in members:
assert m not in seen, f"{m!r} in both {seen[m]!r} and {gid!r}"
seen[m] = gid
def test_reverse_index_matches_groups():
for gid, (_label, members) in PROVIDER_GROUPS.items():
for gid, (_label, _desc, members) in PROVIDER_GROUPS.items():
for m in members:
assert provider_group_for_slug(m) == gid
assert provider_group_for_slug("openrouter") == ""
@ -66,6 +67,9 @@ def test_multi_member_group_folds_to_one_row():
assert row["kind"] == "group"
assert row["group_id"] == "minimax"
assert row["members"] == ["minimax", "minimax-oauth", "minimax-cn"]
# group rows carry the short top-level description from PROVIDER_GROUPS
assert row["description"] == PROVIDER_GROUPS["minimax"][1]
assert row["description"]
def test_group_appears_at_first_member_position():