feat: unified /model and /provider into single view
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 <model-name>
Switch provider: /model <provider>:<model-name>
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.
This commit is contained in:
parent
65356003e3
commit
ec2c6dff70
3 changed files with 83 additions and 60 deletions
126
cli.py
126
cli.py
|
|
@ -2265,6 +2265,72 @@ class HermesCLI:
|
||||||
remaining = len(self.conversation_history)
|
remaining = len(self.conversation_history)
|
||||||
print(f" {remaining} message(s) remaining in 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']}:<model-name>)")
|
||||||
|
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 <model-name>")
|
||||||
|
print(" Switch provider: /model <provider>:<model-name>")
|
||||||
|
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):
|
def _handle_prompt_command(self, cmd: str):
|
||||||
"""Handle the /prompt command to view or set system prompt."""
|
"""Handle the /prompt command to view or set system prompt."""
|
||||||
parts = cmd.split(maxsplit=1)
|
parts = cmd.split(maxsplit=1)
|
||||||
|
|
@ -2776,65 +2842,9 @@ class HermesCLI:
|
||||||
print(f" Reason: {message}")
|
print(f" Reason: {message}")
|
||||||
print(" Note: Model will revert on restart. Use a verified model to save to config.")
|
print(" Note: Model will revert on restart. Use a verified model to save to config.")
|
||||||
else:
|
else:
|
||||||
from hermes_cli.models import curated_models_for_provider, normalize_provider, _PROVIDER_LABELS
|
self._show_model_and_providers()
|
||||||
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 <model-name>")
|
|
||||||
print(" /model provider:model-name (to switch provider)")
|
|
||||||
print(" Example: /model openrouter:anthropic/claude-sonnet-4.5")
|
|
||||||
print(" See /provider for available providers")
|
|
||||||
elif cmd_lower == "/provider":
|
elif cmd_lower == "/provider":
|
||||||
from hermes_cli.models import list_available_providers, normalize_provider, _PROVIDER_LABELS
|
self._show_model_and_providers()
|
||||||
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")
|
|
||||||
elif cmd_lower.startswith("/prompt"):
|
elif cmd_lower.startswith("/prompt"):
|
||||||
# Use original case so prompt text isn't lowercased
|
# Use original case so prompt text isn't lowercased
|
||||||
self._handle_prompt_command(cmd_original)
|
self._handle_prompt_command(cmd_original)
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,19 @@ OPENROUTER_MODELS: list[tuple[str, str]] = [
|
||||||
]
|
]
|
||||||
|
|
||||||
_PROVIDER_MODELS: dict[str, list[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": [
|
"zai": [
|
||||||
"glm-5",
|
"glm-5",
|
||||||
"glm-4.7",
|
"glm-4.7",
|
||||||
|
|
|
||||||
|
|
@ -93,8 +93,8 @@ class TestModelCommand:
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
assert "anthropic/claude-opus-4.6" in output
|
assert "anthropic/claude-opus-4.6" in output
|
||||||
assert "OpenRouter" in output
|
assert "OpenRouter" in output
|
||||||
assert "Available models" in output
|
assert "Authenticated providers" in output or "Switch model" in output
|
||||||
assert "provider:model-name" in output
|
assert "provider" in output and "model" in output
|
||||||
|
|
||||||
# -- provider switching tests -------------------------------------------
|
# -- provider switching tests -------------------------------------------
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue