fix(picker): remove max_models=50 cap in interactive model pickers

The interactive model pickers (Desktop REST API, TUI model.options, CLI
/model) were hard-capped at max_models=50, which truncated large provider
catalogs like Kilo Gateway (336 models) to just 50 entries. This made
most models undiscoverable via the picker search box.

Changes:
- Change build_models_payload() default from max_models=50 to None (unlimited)
- Change list_authenticated_providers() default from max_models=8 to None
- Change list_picker_providers() default from max_models=8 to None
- Fix all [:max_models] slicing to handle None as 'no limit'
- Remove max_models=50 from 5 interactive picker callers:
  * web_server.py: get_model_options (Desktop /api/model/options)
  * web_server.py: get_recommended_default_model
  * model_switch.py: prewarm_picker_cache_async
  * tui_gateway/server.py: model.options JSON-RPC
  * cli.py: HermesCLI model picker
- Telegram/Discord inline keyboard picker (gateway/slash_commands.py)
  still passes max_models=50 explicitly — unchanged behavior.

The total_models field was already in the response payload and is now
meaningful since models.length == total_models for interactive pickers.

Fixes #48279
This commit is contained in:
islam666 2026-06-18 07:35:08 +00:00 committed by Teknium
parent 4ed2f33994
commit 9705e7944a
6 changed files with 37 additions and 12 deletions

2
cli.py
View file

@ -6971,7 +6971,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin):
try:
if ctx is None:
raise RuntimeError("inventory context unavailable")
providers = build_models_payload(ctx, max_models=50)["providers"]
providers = build_models_payload(ctx)["providers"]
except Exception:
providers = []

View file

@ -117,7 +117,7 @@ def build_models_payload(
pricing: bool = False,
capabilities: bool = False,
force_fresh_nous_tier: bool = False,
max_models: int = 50,
max_models: int | None = None,
) -> dict:
"""Build the ``{providers, model, provider}`` shape every consumer
needs from a single substrate call.

View file

@ -1188,7 +1188,6 @@ def prewarm_picker_cache_async() -> Optional["_threading.Thread"]:
current_model=ctx.current_model,
user_providers=ctx.user_providers,
custom_providers=ctx.custom_providers,
max_models=50,
)
except Exception:
# Best-effort warmup — never surface errors into the session.
@ -1206,7 +1205,7 @@ def list_authenticated_providers(
custom_providers: list | None = None,
*,
force_fresh_nous_tier: bool = False,
max_models: int = 8,
max_models: int | None = None,
current_model: str = "",
) -> List[dict]:
"""Detect which providers have credentials and list their curated models.
@ -1426,7 +1425,7 @@ def list_authenticated_providers(
if hermes_id in _MODELS_DEV_PREFERRED:
model_ids = _merge_with_models_dev(hermes_id, model_ids)
total = len(model_ids)
top = model_ids[:max_models]
top = model_ids[:max_models] if max_models else model_ids
slug = hermes_id
pinfo = _mdev_pinfo(mdev_id)
@ -1589,7 +1588,7 @@ def list_authenticated_providers(
if hermes_slug in _MODELS_DEV_PREFERRED:
model_ids = _merge_with_models_dev(hermes_slug, model_ids)
total = len(model_ids)
top = model_ids[:max_models]
top = model_ids[:max_models] if max_models else model_ids
results.append({
"slug": hermes_slug,
@ -1664,7 +1663,7 @@ def list_authenticated_providers(
if not _cp_model_ids:
_cp_model_ids = curated.get(_cp.slug, [])
_cp_total = len(_cp_model_ids)
_cp_top = _cp_model_ids[:max_models]
_cp_top = _cp_model_ids[:max_models] if max_models else _cp_model_ids
results.append({
"slug": _cp.slug,
@ -2040,7 +2039,7 @@ def list_picker_providers(
current_base_url: str = "",
user_providers: dict = None,
custom_providers: list | None = None,
max_models: int = 8,
max_models: int | None = None,
current_model: str = "",
) -> List[dict]:
"""Interactive-picker variant of :func:`list_authenticated_providers`.
@ -2083,7 +2082,7 @@ def list_picker_providers(
except Exception:
live_ids = list(p.get("models", []))
p = dict(p)
p["models"] = live_ids[:max_models]
p["models"] = live_ids[:max_models] if max_models else live_ids
p["total_models"] = len(live_ids)
has_models = bool(p.get("models"))

View file

@ -3323,7 +3323,6 @@ def get_model_options(profile: Optional[str] = None):
with _profile_scope(profile):
return build_models_payload(
load_picker_context(),
max_models=50,
include_unconfigured=True,
picker_hints=True,
canonical_order=True,
@ -3398,7 +3397,7 @@ def get_recommended_default_model(provider: str = ""):
try:
from hermes_cli.inventory import build_models_payload, load_picker_context
payload = build_models_payload(load_picker_context(), max_models=50)
payload = build_models_payload(load_picker_context())
for row in payload.get("providers", []):
if str(row.get("slug", "")).lower() == slug:
models = row.get("models") or []

View file

@ -660,3 +660,31 @@ def test_two_custom_providers_with_overlap_both_survive():
assert a_row["total_models"] == 2
assert b_row["total_models"] == 2
def test_build_models_payload_no_max_models_returns_full_list():
"""When max_models is not passed (None), build_models_payload must
return the full model list not truncate to the old default of 50.
Regression for #48279: Kilo Gateway picker was capped at 50 of 336
models, making most models undiscoverable via search."""
full_models = [f"model-{i}" for i in range(100)]
rows = [
{
"slug": "kilocode",
"name": "Kilo Code",
"models": full_models,
"total_models": len(full_models),
"is_current": False,
"is_user_defined": False,
"source": "built-in",
},
]
ctx = _empty_ctx()
with _list_auth_returning(rows):
# No max_models argument — should return all 100 models
payload = build_models_payload(ctx)
kilo_row = next(r for r in payload["providers"] if r["slug"] == "kilocode")
assert kilo_row["models"] == full_models
assert kilo_row["total_models"] == 100
assert len(kilo_row["models"]) == 100

View file

@ -9496,7 +9496,6 @@ def _(rid, params: dict) -> dict:
canonical_order=True,
pricing=True,
capabilities=True,
max_models=50,
)
return _ok(rid, payload)
except Exception as e: