diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index 3eadd5d70..37a971c3a 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -1541,8 +1541,20 @@ def detect_external_credentials() -> List[Dict[str, Any]]: # CLI Commands — login / logout # ============================================================================= -def _update_config_for_provider(provider_id: str, inference_base_url: str) -> Path: - """Update config.yaml and auth.json to reflect the active provider.""" +def _update_config_for_provider( + provider_id: str, + inference_base_url: str, + default_model: Optional[str] = None, +) -> Path: + """Update config.yaml and auth.json to reflect the active provider. + + When *default_model* is provided the function also writes it as the + ``model.default`` value. This prevents a race condition where the + gateway (which re-reads config per-message) picks up the new provider + before the caller has finished model selection, resulting in a + mismatched model/provider (e.g. ``anthropic/claude-opus-4.6`` sent to + MiniMax's API). + """ # Set active_provider in auth.json so auto-resolution picks this provider with _auth_store_lock(): auth_store = _load_auth_store() @@ -1576,6 +1588,15 @@ def _update_config_for_provider(provider_id: str, inference_base_url: str) -> Pa else: # Clear stale base_url to prevent contamination when switching providers model_cfg.pop("base_url", None) + + # When switching to a non-OpenRouter provider, ensure model.default is + # valid for the new provider. An OpenRouter-formatted name like + # "anthropic/claude-opus-4.6" will fail on direct-API providers. + if default_model: + cur_default = model_cfg.get("default", "") + if not cur_default or "/" in cur_default: + model_cfg["default"] = default_model + config["model"] = model_cfg config_path.write_text(yaml.safe_dump(config, sort_keys=False)) diff --git a/hermes_cli/setup.py b/hermes_cli/setup.py index 0b5a165cc..baff9e6ec 100644 --- a/hermes_cli/setup.py +++ b/hermes_cli/setup.py @@ -111,7 +111,17 @@ def _setup_provider_model_selection(config, provider_id, current_model, prompt_c custom = prompt_fn("Enter model name") if custom: _set_default_model(config, custom) - # else: keep current + else: + # "Keep current" selected — validate it's compatible with the new + # provider. OpenRouter-formatted names (containing "/") won't work + # on direct-API providers and would silently break the gateway. + if "/" in (current_model or "") and provider_models: + print_warning( + f"Current model \"{current_model}\" looks like an OpenRouter model " + f"and won't work with {pconfig.name}. " + f"Switching to {provider_models[0]}." + ) + _set_default_model(config, provider_models[0]) def _sync_model_from_disk(config: Dict[str, Any]) -> None: @@ -967,7 +977,7 @@ def setup_model_provider(config: dict): if existing_custom: save_env_value("OPENAI_BASE_URL", "") save_env_value("OPENAI_API_KEY", "") - _update_config_for_provider("zai", zai_base_url) + _update_config_for_provider("zai", zai_base_url, default_model="glm-5") _set_model_provider(config, "zai", zai_base_url) elif provider_idx == 5: # Kimi / Moonshot @@ -1000,7 +1010,7 @@ def setup_model_provider(config: dict): if existing_custom: save_env_value("OPENAI_BASE_URL", "") save_env_value("OPENAI_API_KEY", "") - _update_config_for_provider("kimi-coding", pconfig.inference_base_url) + _update_config_for_provider("kimi-coding", pconfig.inference_base_url, default_model="kimi-k2.5") _set_model_provider(config, "kimi-coding", pconfig.inference_base_url) elif provider_idx == 6: # MiniMax @@ -1033,7 +1043,7 @@ def setup_model_provider(config: dict): if existing_custom: save_env_value("OPENAI_BASE_URL", "") save_env_value("OPENAI_API_KEY", "") - _update_config_for_provider("minimax", pconfig.inference_base_url) + _update_config_for_provider("minimax", pconfig.inference_base_url, default_model="MiniMax-M2.5") _set_model_provider(config, "minimax", pconfig.inference_base_url) elif provider_idx == 7: # MiniMax China @@ -1066,7 +1076,7 @@ def setup_model_provider(config: dict): if existing_custom: save_env_value("OPENAI_BASE_URL", "") save_env_value("OPENAI_API_KEY", "") - _update_config_for_provider("minimax-cn", pconfig.inference_base_url) + _update_config_for_provider("minimax-cn", pconfig.inference_base_url, default_model="MiniMax-M2.5") _set_model_provider(config, "minimax-cn", pconfig.inference_base_url) elif provider_idx == 8: # Anthropic @@ -1170,7 +1180,7 @@ def setup_model_provider(config: dict): save_env_value("OPENAI_API_KEY", "") # Don't save base_url for Anthropic — resolve_runtime_provider() # always hardcodes it. Stale base_urls contaminate other providers. - _update_config_for_provider("anthropic", "") + _update_config_for_provider("anthropic", "", default_model="claude-opus-4-6") _set_model_provider(config, "anthropic") # else: provider_idx == 9 (Keep current) — only shown when a provider already exists