From ec2c6dff7073b1369ac71f405901dabb893e650f Mon Sep 17 00:00:00 2001 From: teknium1 Date: Wed, 11 Mar 2026 23:06:06 -0700 Subject: [PATCH] feat: unified /model and /provider into single view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both /model and /provider now show the same unified display: Current: anthropic/claude-opus-4.6 via OpenRouter Authenticated providers & models: [openrouter] ← active anthropic/claude-opus-4.6 ← current anthropic/claude-sonnet-4.5 ... [nous] claude-opus-4-6 gemini-3-flash ... [openai-codex] gpt-5.2-codex gpt-5.1-codex-mini ... Not configured: Z.AI / GLM, Kimi / Moonshot, ... Switch model: /model Switch provider: /model : Example: /model nous:claude-opus-4-6 Users can see all authenticated providers and their models at a glance, making it easy to switch mid-conversation. Also added curated model lists for Nous Portal and OpenAI Codex to hermes_cli/models.py. --- cli.py | 126 +++++++++++++++++--------------- hermes_cli/models.py | 13 ++++ tests/test_cli_model_command.py | 4 +- 3 files changed, 83 insertions(+), 60 deletions(-) diff --git a/cli.py b/cli.py index d62da32fc..723a7554d 100755 --- a/cli.py +++ b/cli.py @@ -2265,6 +2265,72 @@ class HermesCLI: remaining = len(self.conversation_history) print(f" {remaining} message(s) remaining in history.") + def _show_model_and_providers(self): + """Unified /model and /provider display. + + Shows current model + provider, then lists all authenticated + providers with their available models so users can switch easily. + """ + from hermes_cli.models import ( + curated_models_for_provider, list_available_providers, + normalize_provider, _PROVIDER_LABELS, + ) + from hermes_cli.auth import resolve_provider as _resolve_provider + + # Resolve current provider + raw_provider = normalize_provider(self.provider) + if raw_provider == "auto": + try: + current = _resolve_provider( + self.requested_provider, + explicit_api_key=self._explicit_api_key, + explicit_base_url=self._explicit_base_url, + ) + except Exception: + current = "openrouter" + else: + current = raw_provider + current_label = _PROVIDER_LABELS.get(current, current) + + print(f"\n Current: {self.model} via {current_label}") + print() + + # Show all authenticated providers with their models + providers = list_available_providers() + authed = [p for p in providers if p["authenticated"]] + unauthed = [p for p in providers if not p["authenticated"]] + + if authed: + print(" Authenticated providers & models:") + for p in authed: + is_active = p["id"] == current + marker = " ← active" if is_active else "" + print(f" [{p['id']}]{marker}") + curated = curated_models_for_provider(p["id"]) + if curated: + for mid, desc in curated: + current_marker = " ← current" if (is_active and mid == self.model) else "" + print(f" {mid}{current_marker}") + else: + print(f" (use /model {p['id']}:)") + print() + + if unauthed: + names = ", ".join(p["label"] for p in unauthed) + print(f" Not configured: {names}") + print(f" Run: hermes setup") + print() + + print(" Switch model: /model ") + print(" Switch provider: /model :") + if authed and len(authed) > 1: + # Show a concrete example with a non-active provider + other = next((p for p in authed if p["id"] != current), authed[0]) + other_models = curated_models_for_provider(other["id"]) + if other_models: + example_model = other_models[0][0] + print(f" Example: /model {other['id']}:{example_model}") + def _handle_prompt_command(self, cmd: str): """Handle the /prompt command to view or set system prompt.""" parts = cmd.split(maxsplit=1) @@ -2776,65 +2842,9 @@ class HermesCLI: print(f" Reason: {message}") print(" Note: Model will revert on restart. Use a verified model to save to config.") else: - from hermes_cli.models import curated_models_for_provider, normalize_provider, _PROVIDER_LABELS - from hermes_cli.auth import resolve_provider as _resolve_provider - # Resolve "auto" to the actual provider using credential detection - raw_provider = normalize_provider(self.provider) - if raw_provider == "auto": - try: - display_provider = _resolve_provider( - self.requested_provider, - explicit_api_key=self._explicit_api_key, - explicit_base_url=self._explicit_base_url, - ) - except Exception: - display_provider = "openrouter" - else: - display_provider = raw_provider - provider_label = _PROVIDER_LABELS.get(display_provider, display_provider) - print(f"\n Current model: {self.model}") - print(f" Current provider: {provider_label}") - print() - curated = curated_models_for_provider(display_provider) - if curated: - print(f" Available models ({provider_label}):") - for mid, desc in curated: - marker = " ←" if mid == self.model else "" - label = f" {desc}" if desc else "" - print(f" {mid}{label}{marker}") - print() - print(" Usage: /model ") - print(" /model provider:model-name (to switch provider)") - print(" Example: /model openrouter:anthropic/claude-sonnet-4.5") - print(" See /provider for available providers") + self._show_model_and_providers() elif cmd_lower == "/provider": - from hermes_cli.models import list_available_providers, normalize_provider, _PROVIDER_LABELS - from hermes_cli.auth import resolve_provider as _resolve_provider - # Resolve current provider - raw_provider = normalize_provider(self.provider) - if raw_provider == "auto": - try: - current = _resolve_provider( - self.requested_provider, - explicit_api_key=self._explicit_api_key, - explicit_base_url=self._explicit_base_url, - ) - except Exception: - current = "openrouter" - else: - current = raw_provider - current_label = _PROVIDER_LABELS.get(current, current) - print(f"\n Current provider: {current_label} ({current})\n") - providers = list_available_providers() - print(" Available providers:") - for p in providers: - marker = " ← active" if p["id"] == current else "" - auth = "✓" if p["authenticated"] else "✗" - aliases = f" (also: {', '.join(p['aliases'])})" if p["aliases"] else "" - print(f" [{auth}] {p['id']:<14} {p['label']}{aliases}{marker}") - print() - print(" Switch: /model provider:model-name") - print(" Setup: hermes setup") + self._show_model_and_providers() elif cmd_lower.startswith("/prompt"): # Use original case so prompt text isn't lowercased self._handle_prompt_command(cmd_original) diff --git a/hermes_cli/models.py b/hermes_cli/models.py index 1fdde0900..0df1d3095 100644 --- a/hermes_cli/models.py +++ b/hermes_cli/models.py @@ -31,6 +31,19 @@ OPENROUTER_MODELS: list[tuple[str, str]] = [ ] _PROVIDER_MODELS: dict[str, list[str]] = { + "nous": [ + "claude-opus-4-6", + "claude-sonnet-4-6", + "gpt-5.4", + "gemini-3-flash", + "gemini-3.0-pro-preview", + "deepseek-v3.2", + ], + "openai-codex": [ + "gpt-5.2-codex", + "gpt-5.1-codex-mini", + "gpt-5.1-codex-max", + ], "zai": [ "glm-5", "glm-4.7", diff --git a/tests/test_cli_model_command.py b/tests/test_cli_model_command.py index b8b8e8d2d..477ad4292 100644 --- a/tests/test_cli_model_command.py +++ b/tests/test_cli_model_command.py @@ -93,8 +93,8 @@ class TestModelCommand: output = capsys.readouterr().out assert "anthropic/claude-opus-4.6" in output assert "OpenRouter" in output - assert "Available models" in output - assert "provider:model-name" in output + assert "Authenticated providers" in output or "Switch model" in output + assert "provider" in output and "model" in output # -- provider switching tests -------------------------------------------