fix: resolve 'auto' provider in /model display + update gateway handler
- normalize_provider('auto') now returns 'openrouter' (the default)
so /model shows the curated model list instead of nothing
- CLI /model display uses normalize_provider before looking up labels
- Gateway /model handler now uses the same validation logic as CLI:
live API probe, provider:model syntax, curated model list display
This commit is contained in:
parent
66d3e6a0c2
commit
132e5ec179
3 changed files with 85 additions and 22 deletions
10
cli.py
10
cli.py
|
|
@ -2131,15 +2131,13 @@ class HermesCLI:
|
||||||
if message:
|
if message:
|
||||||
print(f" Warning: {message}")
|
print(f" Warning: {message}")
|
||||||
else:
|
else:
|
||||||
from hermes_cli.models import curated_models_for_provider, _PROVIDER_LABELS
|
from hermes_cli.models import curated_models_for_provider, normalize_provider, _PROVIDER_LABELS
|
||||||
provider_label = _PROVIDER_LABELS.get(
|
display_provider = normalize_provider(self.provider)
|
||||||
self.provider or "openrouter",
|
provider_label = _PROVIDER_LABELS.get(display_provider, display_provider)
|
||||||
self.provider or "openrouter",
|
|
||||||
)
|
|
||||||
print(f"\n Current model: {self.model}")
|
print(f"\n Current model: {self.model}")
|
||||||
print(f" Current provider: {provider_label}")
|
print(f" Current provider: {provider_label}")
|
||||||
print()
|
print()
|
||||||
curated = curated_models_for_provider(self.provider)
|
curated = curated_models_for_provider(display_provider)
|
||||||
if curated:
|
if curated:
|
||||||
print(f" Available models ({provider_label}):")
|
print(f" Available models ({provider_label}):")
|
||||||
for mid, desc in curated:
|
for mid, desc in curated:
|
||||||
|
|
|
||||||
|
|
@ -1291,7 +1291,7 @@ class GatewayRunner:
|
||||||
"`/reset` — Reset conversation history",
|
"`/reset` — Reset conversation history",
|
||||||
"`/status` — Show session info",
|
"`/status` — Show session info",
|
||||||
"`/stop` — Interrupt the running agent",
|
"`/stop` — Interrupt the running agent",
|
||||||
"`/model [name]` — Show or change the model",
|
"`/model [provider:model]` — Show/change model (or switch provider)",
|
||||||
"`/personality [name]` — Set a personality",
|
"`/personality [name]` — Set a personality",
|
||||||
"`/retry` — Retry your last message",
|
"`/retry` — Retry your last message",
|
||||||
"`/undo` — Remove the last exchange",
|
"`/undo` — Remove the last exchange",
|
||||||
|
|
@ -1317,13 +1317,19 @@ class GatewayRunner:
|
||||||
async def _handle_model_command(self, event: MessageEvent) -> str:
|
async def _handle_model_command(self, event: MessageEvent) -> str:
|
||||||
"""Handle /model command - show or change the current model."""
|
"""Handle /model command - show or change the current model."""
|
||||||
import yaml
|
import yaml
|
||||||
|
from hermes_cli.models import (
|
||||||
|
parse_model_input,
|
||||||
|
validate_requested_model,
|
||||||
|
curated_models_for_provider,
|
||||||
|
_PROVIDER_LABELS,
|
||||||
|
)
|
||||||
|
|
||||||
args = event.get_command_args().strip()
|
args = event.get_command_args().strip()
|
||||||
config_path = _hermes_home / 'config.yaml'
|
config_path = _hermes_home / 'config.yaml'
|
||||||
|
|
||||||
# Resolve current model the same way the agent init does:
|
# Resolve current model and provider from config
|
||||||
# env vars first, then config.yaml always overrides.
|
|
||||||
current = os.getenv("HERMES_MODEL") or os.getenv("LLM_MODEL") or "anthropic/claude-opus-4.6"
|
current = os.getenv("HERMES_MODEL") or os.getenv("LLM_MODEL") or "anthropic/claude-opus-4.6"
|
||||||
|
current_provider = "openrouter"
|
||||||
try:
|
try:
|
||||||
if config_path.exists():
|
if config_path.exists():
|
||||||
with open(config_path) as f:
|
with open(config_path) as f:
|
||||||
|
|
@ -1333,22 +1339,70 @@ class GatewayRunner:
|
||||||
current = model_cfg
|
current = model_cfg
|
||||||
elif isinstance(model_cfg, dict):
|
elif isinstance(model_cfg, dict):
|
||||||
current = model_cfg.get("default", current)
|
current = model_cfg.get("default", current)
|
||||||
|
current_provider = model_cfg.get("provider", current_provider)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not args:
|
if not args:
|
||||||
return f"🤖 **Current model:** `{current}`\n\nTo change: `/model provider/model-name`"
|
provider_label = _PROVIDER_LABELS.get(current_provider, current_provider)
|
||||||
|
lines = [
|
||||||
|
f"🤖 **Current model:** `{current}`",
|
||||||
|
f"**Provider:** {provider_label}",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
curated = curated_models_for_provider(current_provider)
|
||||||
|
if curated:
|
||||||
|
lines.append(f"**Available models ({provider_label}):**")
|
||||||
|
for mid, desc in curated:
|
||||||
|
marker = " ←" if mid == current else ""
|
||||||
|
label = f" _{desc}_" if desc else ""
|
||||||
|
lines.append(f"• `{mid}`{label}{marker}")
|
||||||
|
lines.append("")
|
||||||
|
lines.append("To change: `/model model-name`")
|
||||||
|
lines.append("Switch provider: `/model provider:model-name`")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
if "/" not in args:
|
# Parse provider:model syntax
|
||||||
return (
|
target_provider, new_model = parse_model_input(args, current_provider)
|
||||||
f"🤖 Invalid model format: `{args}`\n\n"
|
provider_changed = target_provider != current_provider
|
||||||
f"Use `provider/model-name` format, e.g.:\n"
|
|
||||||
f"• `anthropic/claude-sonnet-4`\n"
|
# Resolve credentials for the target provider (for API probe)
|
||||||
f"• `google/gemini-2.5-pro`\n"
|
api_key = os.getenv("OPENROUTER_API_KEY") or os.getenv("OPENAI_API_KEY") or ""
|
||||||
f"• `openai/gpt-4o`"
|
base_url = "https://openrouter.ai/api/v1"
|
||||||
|
if provider_changed:
|
||||||
|
try:
|
||||||
|
from hermes_cli.runtime_provider import resolve_runtime_provider
|
||||||
|
runtime = resolve_runtime_provider(requested=target_provider)
|
||||||
|
api_key = runtime.get("api_key", "")
|
||||||
|
base_url = runtime.get("base_url", "")
|
||||||
|
except Exception as e:
|
||||||
|
provider_label = _PROVIDER_LABELS.get(target_provider, target_provider)
|
||||||
|
return f"⚠️ Could not resolve credentials for provider '{provider_label}': {e}"
|
||||||
|
else:
|
||||||
|
# Use current provider's base_url from config or registry
|
||||||
|
try:
|
||||||
|
from hermes_cli.runtime_provider import resolve_runtime_provider
|
||||||
|
runtime = resolve_runtime_provider(requested=current_provider)
|
||||||
|
api_key = runtime.get("api_key", "")
|
||||||
|
base_url = runtime.get("base_url", "")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Validate the model against the live API
|
||||||
|
try:
|
||||||
|
validation = validate_requested_model(
|
||||||
|
new_model,
|
||||||
|
target_provider,
|
||||||
|
api_key=api_key,
|
||||||
|
base_url=base_url,
|
||||||
)
|
)
|
||||||
|
except Exception:
|
||||||
|
validation = {"accepted": True, "persist": True, "recognized": False, "message": None}
|
||||||
|
|
||||||
# Write to config.yaml (source of truth), same pattern as CLI save_config_value.
|
if not validation.get("accepted"):
|
||||||
|
return f"⚠️ {validation.get('message')}"
|
||||||
|
|
||||||
|
# Write to config.yaml
|
||||||
try:
|
try:
|
||||||
user_config = {}
|
user_config = {}
|
||||||
if config_path.exists():
|
if config_path.exists():
|
||||||
|
|
@ -1356,16 +1410,25 @@ class GatewayRunner:
|
||||||
user_config = yaml.safe_load(f) or {}
|
user_config = yaml.safe_load(f) or {}
|
||||||
if "model" not in user_config or not isinstance(user_config["model"], dict):
|
if "model" not in user_config or not isinstance(user_config["model"], dict):
|
||||||
user_config["model"] = {}
|
user_config["model"] = {}
|
||||||
user_config["model"]["default"] = args
|
user_config["model"]["default"] = new_model
|
||||||
|
if provider_changed:
|
||||||
|
user_config["model"]["provider"] = target_provider
|
||||||
with open(config_path, 'w') as f:
|
with open(config_path, 'w') as f:
|
||||||
yaml.dump(user_config, f, default_flow_style=False, sort_keys=False)
|
yaml.dump(user_config, f, default_flow_style=False, sort_keys=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"⚠️ Failed to save model change: {e}"
|
return f"⚠️ Failed to save model change: {e}"
|
||||||
|
|
||||||
# Also set env var so code reading it before the next agent init sees the update.
|
os.environ["HERMES_MODEL"] = new_model
|
||||||
os.environ["HERMES_MODEL"] = args
|
|
||||||
|
|
||||||
return f"🤖 Model changed to `{args}`\n_(takes effect on next message)_"
|
provider_label = _PROVIDER_LABELS.get(target_provider, target_provider)
|
||||||
|
provider_note = f"\n**Provider:** {provider_label}" if provider_changed else ""
|
||||||
|
|
||||||
|
warning = ""
|
||||||
|
if validation.get("message"):
|
||||||
|
warning = f"\n⚠️ {validation['message']}"
|
||||||
|
|
||||||
|
persist_note = "saved to config" if validation.get("persist") else "session only"
|
||||||
|
return f"🤖 Model changed to `{new_model}` ({persist_note}){provider_note}{warning}\n_(takes effect on next message)_"
|
||||||
|
|
||||||
async def _handle_personality_command(self, event: MessageEvent) -> str:
|
async def _handle_personality_command(self, event: MessageEvent) -> str:
|
||||||
"""Handle /personality command - list or set a personality."""
|
"""Handle /personality command - list or set a personality."""
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,8 @@ def curated_models_for_provider(provider: Optional[str]) -> list[tuple[str, str]
|
||||||
def normalize_provider(provider: Optional[str]) -> str:
|
def normalize_provider(provider: Optional[str]) -> str:
|
||||||
"""Normalize provider aliases to Hermes' canonical provider ids."""
|
"""Normalize provider aliases to Hermes' canonical provider ids."""
|
||||||
normalized = (provider or "openrouter").strip().lower()
|
normalized = (provider or "openrouter").strip().lower()
|
||||||
|
if normalized == "auto":
|
||||||
|
return "openrouter"
|
||||||
return _PROVIDER_ALIASES.get(normalized, normalized)
|
return _PROVIDER_ALIASES.get(normalized, normalized)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue