diff --git a/cli.py b/cli.py index 602844e49f..e945652e58 100644 --- a/cli.py +++ b/cli.py @@ -3759,6 +3759,91 @@ def process_command(self, command: str) -> bool: self._handle_resume_command(cmd_original) elif canonical == "provider": self._show_model_and_providers() + elif canonical == "model": + # Use original case so model names like "Anthropic/Claude-Opus-4" are preserved + parts = cmd_original.split(maxsplit=1) + if len(parts) > 1: + from hermes_cli.model_switch import switch_model, switch_to_custom_provider + + raw_input = parts[1].strip() + + # Handle bare "/model custom" — switch to custom provider + # and auto-detect the model from the endpoint. + if raw_input.strip().lower() == "custom": + result = switch_to_custom_provider() + if result.success: + self.model = result.model + self.requested_provider = "custom" + self.provider = "custom" + self.api_key = result.api_key + self.base_url = result.base_url + self.agent = None + save_config_value("model.default", result.model) + save_config_value("model.provider", "custom") + save_config_value("model.base_url", result.base_url) + print(f"(^_^)b Model changed to: {result.model} [provider: Custom]") + print(f" Endpoint: {result.base_url}") + print(f" Status: connected (model auto-detected)") + else: + print(f"(>_<) {result.error_message}") + return True + + # Core model-switching pipeline (shared with gateway) + current_provider = self.provider or self.requested_provider or "openrouter" + result = switch_model( + raw_input, + current_provider, + current_base_url=self.base_url or "", + current_api_key=self.api_key or "", + ) + + if not result.success: + print(f"(>_<) {result.error_message}") + if "Did you mean" not in result.error_message: + print(f" Model unchanged: {self.model}") + if "credentials" not in result.error_message.lower(): + print(" Tip: Use /model to see available models, /provider to see providers") + else: + self.model = result.new_model + self.agent = None # Force re-init + + if result.provider_changed: + self.requested_provider = result.target_provider + self.provider = result.target_provider + self.api_key = result.api_key + self.base_url = result.base_url + + provider_note = f" [provider: {result.provider_label}]" if result.provider_changed else "" + + if result.persist: + saved_model = save_config_value("model.default", result.new_model) + if result.provider_changed: + save_config_value("model.provider", result.target_provider) + # Persist base_url for custom endpoints; clear + # when switching away from custom (#2562 Phase 2). + if result.base_url and "openrouter.ai" not in (result.base_url or ""): + save_config_value("model.base_url", result.base_url) + else: + save_config_value("model.base_url", None) + if saved_model: + print(f"(^_^)b Model changed to: {result.new_model}{provider_note} (saved to config)") + else: + print(f"(^_^) Model changed to: {result.new_model}{provider_note} (this session only)") + else: + print(f"(^_^) Model changed to: {result.new_model}{provider_note} (this session only)") + if result.warning_message: + print(f" Reason: {result.warning_message}") + print(" Note: Model will revert on restart. Use a verified model to save to config.") + + # Show endpoint info for custom providers + if result.is_custom_target: + endpoint = result.base_url or self.base_url or "custom endpoint" + print(f" Endpoint: {endpoint}") + if not result.provider_changed: + print(f" Tip: To switch providers, use /model provider:model") + print(f" e.g. /model openai-codex:gpt-5.2-codex") + else: + self._show_model_and_providers() elif canonical == "prompt": # Use original case so prompt text isn't lowercased self._handle_prompt_command(cmd_original) diff --git a/hermes_cli/commands.py b/hermes_cli/commands.py index d442f7f94d..813ee8d70d 100644 --- a/hermes_cli/commands.py +++ b/hermes_cli/commands.py @@ -81,6 +81,8 @@ class CommandDef: cli_only=True), CommandDef("provider", "Show available providers and current provider", "Configuration"), + CommandDef("model", "Show or switch the active model", "Configuration", + args_hint="[provider:model]"), CommandDef("prompt", "View/set custom system prompt", "Configuration", cli_only=True, args_hint="[text]", subcommands=("clear",)), CommandDef("personality", "Set a predefined personality", "Configuration",