fix: remove hardcoded OpenRouter/opus defaults

No model, base_url, or provider is assumed when the user hasn't
configured one.  Previously the defaults dict in cli.py, AIAgent
constructor args, and several fallback paths all hardcoded
anthropic/claude-opus-4.6 + openrouter.ai/api/v1 — silently routing
unconfigured users to OpenRouter, which 404s for anyone using a
different provider.

Now empty defaults force the setup wizard to run, and existing users
who already completed setup are unaffected (their config.yaml has
the model they chose).

Files changed:
- cli.py: defaults dict, _DEFAULT_CONFIG_MODEL
- run_agent.py: AIAgent.__init__ defaults, main() defaults
- hermes_cli/config.py: DEFAULT_CONFIG
- hermes_cli/runtime_provider.py: is_fallback sentinel
- acp_adapter/session.py: default_model
- tests: updated to reflect empty defaults
This commit is contained in:
Teknium 2026-04-01 15:22:05 -07:00
parent 3628ccc8c4
commit de9bba8d7c
No known key found for this signature in database
7 changed files with 19 additions and 20 deletions

View file

@ -426,7 +426,7 @@ class SessionManager:
config = load_config() config = load_config()
model_cfg = config.get("model") model_cfg = config.get("model")
default_model = "anthropic/claude-opus-4.6" default_model = ""
config_provider = None config_provider = None
if isinstance(model_cfg, dict): if isinstance(model_cfg, dict):
default_model = str(model_cfg.get("default") or default_model) default_model = str(model_cfg.get("default") or default_model)

6
cli.py
View file

@ -144,8 +144,8 @@ def load_cli_config() -> Dict[str, Any]:
# Default configuration # Default configuration
defaults = { defaults = {
"model": { "model": {
"default": "anthropic/claude-opus-4.6", "default": "",
"base_url": OPENROUTER_BASE_URL, "base_url": "",
"provider": "auto", "provider": "auto",
}, },
"terminal": { "terminal": {
@ -1103,7 +1103,7 @@ class HermesCLI:
# env vars would stomp each other. # env vars would stomp each other.
_model_config = CLI_CONFIG.get("model", {}) _model_config = CLI_CONFIG.get("model", {})
_config_model = (_model_config.get("default") or _model_config.get("model") or "") if isinstance(_model_config, dict) else (_model_config or "") _config_model = (_model_config.get("default") or _model_config.get("model") or "") if isinstance(_model_config, dict) else (_model_config or "")
_DEFAULT_CONFIG_MODEL = "anthropic/claude-opus-4.6" _DEFAULT_CONFIG_MODEL = ""
self.model = model or _config_model or _DEFAULT_CONFIG_MODEL self.model = model or _config_model or _DEFAULT_CONFIG_MODEL
# Auto-detect model from local server if still on default # Auto-detect model from local server if still on default
if self.model == _DEFAULT_CONFIG_MODEL: if self.model == _DEFAULT_CONFIG_MODEL:

View file

@ -196,7 +196,7 @@ def ensure_hermes_home():
# ============================================================================= # =============================================================================
DEFAULT_CONFIG = { DEFAULT_CONFIG = {
"model": "anthropic/claude-opus-4.6", "model": "",
"fallback_providers": [], "fallback_providers": [],
"credential_pool_strategies": {}, "credential_pool_strategies": {},
"toolsets": ["hermes-cli"], "toolsets": ["hermes-cli"],

View file

@ -71,7 +71,7 @@ def _get_model_config() -> Dict[str, Any]:
default = (cfg.get("default") or "").strip() default = (cfg.get("default") or "").strip()
base_url = (cfg.get("base_url") or "").strip() base_url = (cfg.get("base_url") or "").strip()
is_local = "localhost" in base_url or "127.0.0.1" in base_url is_local = "localhost" in base_url or "127.0.0.1" in base_url
is_fallback = not default or default == "anthropic/claude-opus-4.6" is_fallback = not default
if is_local and is_fallback and base_url: if is_local and is_fallback and base_url:
detected = _auto_detect_local_model(base_url) detected = _auto_detect_local_model(base_url)
if detected: if detected:

View file

@ -471,7 +471,7 @@ class AIAgent:
acp_args: list[str] | None = None, acp_args: list[str] | None = None,
command: str = None, command: str = None,
args: list[str] | None = None, args: list[str] | None = None,
model: str = "anthropic/claude-opus-4.6", # OpenRouter format model: str = "",
max_iterations: int = 90, # Default tool-calling iterations (shared with subagents) max_iterations: int = 90, # Default tool-calling iterations (shared with subagents)
tool_delay: float = 1.0, tool_delay: float = 1.0,
enabled_toolsets: List[str] = None, enabled_toolsets: List[str] = None,
@ -586,10 +586,9 @@ class AIAgent:
self.log_prefix_chars = log_prefix_chars self.log_prefix_chars = log_prefix_chars
self.log_prefix = f"{log_prefix} " if log_prefix else "" self.log_prefix = f"{log_prefix} " if log_prefix else ""
# Store effective base URL for feature detection (prompt caching, reasoning, etc.) # Store effective base URL for feature detection (prompt caching, reasoning, etc.)
# When no base_url is provided, the client defaults to OpenRouter, so reflect that here. self.base_url = base_url or ""
self.base_url = base_url or OPENROUTER_BASE_URL
provider_name = provider.strip().lower() if isinstance(provider, str) and provider.strip() else None provider_name = provider.strip().lower() if isinstance(provider, str) and provider.strip() else None
self.provider = provider_name or "openrouter" self.provider = provider_name or ""
self.acp_command = acp_command or command self.acp_command = acp_command or command
self.acp_args = list(acp_args or args or []) self.acp_args = list(acp_args or args or [])
if api_mode in {"chat_completions", "codex_responses", "anthropic_messages"}: if api_mode in {"chat_completions", "codex_responses", "anthropic_messages"}:
@ -8520,9 +8519,9 @@ class AIAgent:
def main( def main(
query: str = None, query: str = None,
model: str = "anthropic/claude-opus-4.6", model: str = "",
api_key: str = None, api_key: str = None,
base_url: str = "https://openrouter.ai/api/v1", base_url: str = "",
max_turns: int = 10, max_turns: int = 10,
enabled_toolsets: str = None, enabled_toolsets: str = None,
disabled_toolsets: str = None, disabled_toolsets: str = None,

View file

@ -704,14 +704,14 @@ class TestHasAnyProviderConfigured:
assert _has_any_provider_configured() is True assert _has_any_provider_configured() is True
def test_config_dict_no_provider_no_creds_still_false(self, monkeypatch, tmp_path): def test_config_dict_no_provider_no_creds_still_false(self, monkeypatch, tmp_path):
"""config.yaml model dict with only 'default' key and no creds stays false.""" """config.yaml model dict with empty default and no creds stays false."""
import yaml import yaml
from hermes_cli import config as config_module from hermes_cli import config as config_module
hermes_home = tmp_path / ".hermes" hermes_home = tmp_path / ".hermes"
hermes_home.mkdir() hermes_home.mkdir()
config_file = hermes_home / "config.yaml" config_file = hermes_home / "config.yaml"
config_file.write_text(yaml.dump({ config_file.write_text(yaml.dump({
"model": {"default": "anthropic/claude-opus-4.6"}, "model": {"default": ""},
})) }))
monkeypatch.setattr(config_module, "get_env_path", lambda: hermes_home / ".env") monkeypatch.setattr(config_module, "get_env_path", lambda: hermes_home / ".env")
monkeypatch.setattr(config_module, "get_hermes_home", lambda: hermes_home) monkeypatch.setattr(config_module, "get_hermes_home", lambda: hermes_home)

View file

@ -187,12 +187,12 @@ class TestNormalizeModelForProvider:
assert cli.model == "claude-opus-4.6" assert cli.model == "claude-opus-4.6"
def test_default_model_replaced(self): def test_default_model_replaced(self):
"""The untouched default (anthropic/claude-opus-4.6) gets swapped.""" """No model configured (empty default) gets swapped for codex."""
import cli as _cli_mod import cli as _cli_mod
_clean_config = { _clean_config = {
"model": { "model": {
"default": "anthropic/claude-opus-4.6", "default": "",
"base_url": "https://openrouter.ai/api/v1", "base_url": "",
"provider": "auto", "provider": "auto",
}, },
"display": {"compact": False, "tool_progress": "all", "resume_display": "full"}, "display": {"compact": False, "tool_progress": "all", "resume_display": "full"},
@ -219,12 +219,12 @@ class TestNormalizeModelForProvider:
assert cli.model == "gpt-5.3-codex" assert cli.model == "gpt-5.3-codex"
def test_default_fallback_when_api_fails(self): def test_default_fallback_when_api_fails(self):
"""Default model falls back to gpt-5.3-codex when API unreachable.""" """No model configured falls back to gpt-5.3-codex when API unreachable."""
import cli as _cli_mod import cli as _cli_mod
_clean_config = { _clean_config = {
"model": { "model": {
"default": "anthropic/claude-opus-4.6", "default": "",
"base_url": "https://openrouter.ai/api/v1", "base_url": "",
"provider": "auto", "provider": "auto",
}, },
"display": {"compact": False, "tool_progress": "all", "resume_display": "full"}, "display": {"compact": False, "tool_progress": "all", "resume_display": "full"},