Merge pull request #37738 from NousResearch/bb/statusbar-model-menu
feat(desktop): inline model picker in the status bar
This commit is contained in:
@ -115,6 +115,7 @@ def build_models_payload(
|
||||
picker_hints: bool = False,
|
||||
canonical_order: bool = False,
|
||||
pricing: bool = False,
|
||||
capabilities: bool = False,
|
||||
max_models: int = 50,
|
||||
) -> dict:
|
||||
"""Build the ``{providers, model, provider}`` shape every consumer
|
||||
@ -134,6 +135,10 @@ def build_models_payload(
|
||||
show $/Mtok columns and gate paid models on free accounts —
|
||||
mirroring the ``hermes model`` CLI picker. Adds network calls
|
||||
(pricing fetch + Nous tier check); only set for interactive pickers.
|
||||
- ``capabilities``: add a per-row ``capabilities`` map
|
||||
``{model: {fast, reasoning}}`` so pickers can gate the model-options
|
||||
controls (fast toggle / reasoning) to what each model actually
|
||||
supports, instead of offering knobs the backend would reject.
|
||||
"""
|
||||
from hermes_cli.model_switch import list_authenticated_providers
|
||||
|
||||
@ -154,6 +159,8 @@ def build_models_payload(
|
||||
rows = _reorder_canonical(rows)
|
||||
if pricing:
|
||||
_apply_pricing(rows)
|
||||
if capabilities:
|
||||
_apply_capabilities(rows)
|
||||
|
||||
return {
|
||||
"providers": rows,
|
||||
@ -162,6 +169,44 @@ def build_models_payload(
|
||||
}
|
||||
|
||||
|
||||
def _apply_capabilities(rows: list[dict]) -> None:
|
||||
"""Attach a ``{model: {fast, reasoning}}`` map to each provider row.
|
||||
|
||||
`fast` mirrors ``model_supports_fast_mode`` (the same gate the runtime
|
||||
enforces). `reasoning` comes from the models.dev catalog when known and
|
||||
defaults to True otherwise — the effort dial is broadly accepted and a
|
||||
no-op on models that ignore it, whereas hiding it from a capable-but-
|
||||
uncatalogued model is the worse failure.
|
||||
"""
|
||||
from hermes_cli.models import model_supports_fast_mode
|
||||
|
||||
try:
|
||||
from agent.models_dev import get_model_capabilities
|
||||
except Exception:
|
||||
get_model_capabilities = None # type: ignore[assignment]
|
||||
|
||||
for row in rows:
|
||||
slug = row.get("slug") or ""
|
||||
caps: dict[str, dict[str, bool]] = {}
|
||||
|
||||
for model in row.get("models") or []:
|
||||
reasoning = True
|
||||
if get_model_capabilities is not None and slug:
|
||||
try:
|
||||
meta = get_model_capabilities(slug, model)
|
||||
if meta is not None:
|
||||
reasoning = bool(meta.supports_reasoning)
|
||||
except Exception:
|
||||
reasoning = True
|
||||
|
||||
caps[model] = {
|
||||
"fast": bool(model_supports_fast_mode(model)),
|
||||
"reasoning": reasoning,
|
||||
}
|
||||
|
||||
row["capabilities"] = caps
|
||||
|
||||
|
||||
# ─── Internal: row post-processing ──────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
@ -1868,19 +1868,21 @@ def model_supports_fast_mode(model_id: Optional[str]) -> bool:
|
||||
|
||||
|
||||
def _is_anthropic_fast_model(model_id: Optional[str]) -> bool:
|
||||
"""Return True if the model is a Claude model eligible for Anthropic Fast Mode.
|
||||
"""Return True if the model accepts the Anthropic Fast Mode ``speed`` param.
|
||||
|
||||
Fast mode is currently supported on Claude Opus 4.6 only. Per Anthropic's
|
||||
docs (https://platform.claude.com/docs/en/build-with-claude/fast-mode):
|
||||
"Fast mode is currently supported on Opus 4.6 only. Sending speed: fast
|
||||
with an unsupported model returns an error." Opus 4.7 explicitly rejects
|
||||
the ``speed`` parameter with HTTP 400.
|
||||
This gates the *speed=fast request parameter*, which Anthropic supports on
|
||||
Opus 4.6 only (Opus 4.7 explicitly 400s). It is deliberately NOT a general
|
||||
"is this a fast model" check: for Opus 4.8 the fast offering is a SEPARATE
|
||||
model id (``…-opus-4.8-fast``) selected via the model field, not the speed
|
||||
parameter — see ``agent.anthropic_adapter._supports_fast_mode`` and its
|
||||
test. Keep this in lock-step with that adapter gate so the UI never shows a
|
||||
Fast toggle that the runtime would silently drop.
|
||||
"""
|
||||
raw = _strip_vendor_prefix(str(model_id or ""))
|
||||
base = raw.split(":")[0]
|
||||
if not base.startswith("claude-"):
|
||||
return False
|
||||
# Only Opus 4.6 supports fast mode at present.
|
||||
# Only Opus 4.6 supports the speed=fast parameter at present.
|
||||
return "opus-4-6" in base or "opus-4.6" in base
|
||||
|
||||
|
||||
|
||||
@ -1665,7 +1665,9 @@ def get_model_options():
|
||||
try:
|
||||
from hermes_cli.inventory import build_models_payload, load_picker_context
|
||||
|
||||
return build_models_payload(load_picker_context(), max_models=50, pricing=True)
|
||||
return build_models_payload(
|
||||
load_picker_context(), max_models=50, pricing=True, capabilities=True
|
||||
)
|
||||
except Exception:
|
||||
_log.exception("GET /api/model/options failed")
|
||||
raise HTTPException(status_code=500, detail="Failed to list model options")
|
||||
|
||||
Reference in New Issue
Block a user