diff --git a/cli.py b/cli.py index 53bf7bf5dea..678574984bc 100644 --- a/cli.py +++ b/cli.py @@ -468,7 +468,9 @@ def load_cli_config() -> Dict[str, Any]: if config_path.exists(): try: with open(config_path, "r", encoding="utf-8") as f: - file_config = yaml.safe_load(f) or {} + from hermes_cli.config import _normalize_root_model_keys + + file_config = _normalize_root_model_keys(yaml.safe_load(f) or {}) _file_has_terminal_config = "terminal" in file_config @@ -489,21 +491,6 @@ def load_cli_config() -> Dict[str, Any]: if "model" in file_config["model"] and "default" not in file_config["model"]: defaults["model"]["default"] = file_config["model"]["model"] - # Legacy root-level provider/base_url fallback. - # Some users (or old code) put provider: / base_url: at the - # config root instead of inside the model: section. These are - # only used as a FALLBACK when model.provider / model.base_url - # is not already set — never as an override. The canonical - # location is model.provider (written by `hermes model`). - if not defaults["model"].get("provider"): - root_provider = file_config.get("provider") - if root_provider: - defaults["model"]["provider"] = root_provider - if not defaults["model"].get("base_url"): - root_base_url = file_config.get("base_url") - if root_base_url: - defaults["model"]["base_url"] = root_base_url - # Deep merge file_config into defaults. # First: merge keys that exist in both (deep-merge dicts, overwrite scalars) for key in defaults: diff --git a/tests/cli/test_cli_init.py b/tests/cli/test_cli_init.py index 2775eacbbcb..5f163942283 100644 --- a/tests/cli/test_cli_init.py +++ b/tests/cli/test_cli_init.py @@ -470,8 +470,8 @@ class TestRootLevelProviderOverride: assert cfg["model"]["provider"] == "openrouter" - def test_root_provider_ignored_when_default_model_provider_exists(self, tmp_path, monkeypatch): - """Even when model.provider is the default 'auto', root-level provider is ignored.""" + def test_root_provider_used_as_fallback_when_model_provider_missing(self, tmp_path, monkeypatch): + """Legacy root-level provider still populates model.provider in the CLI loader.""" import yaml hermes_home = tmp_path / ".hermes" @@ -491,8 +491,29 @@ class TestRootLevelProviderOverride: monkeypatch.setattr(cli, "_hermes_home", hermes_home) cfg = cli.load_cli_config() - # Root-level "opencode-go" must NOT leak through - assert cfg["model"]["provider"] != "opencode-go" + assert cfg["model"]["provider"] == "opencode-go" + + def test_root_base_url_used_as_fallback_when_model_base_url_missing(self, tmp_path, monkeypatch): + """Legacy root-level base_url still populates model.base_url in the CLI loader.""" + import yaml + + hermes_home = tmp_path / ".hermes" + hermes_home.mkdir() + monkeypatch.setenv("HERMES_HOME", str(hermes_home)) + + config_path = hermes_home / "config.yaml" + config_path.write_text(yaml.safe_dump({ + "base_url": "https://example.com/v1", + "model": { + "default": "google/gemini-3-flash-preview", + }, + })) + + import cli + monkeypatch.setattr(cli, "_hermes_home", hermes_home) + cfg = cli.load_cli_config() + + assert cfg["model"]["base_url"] == "https://example.com/v1" def test_terminal_vercel_runtime_bridged_to_env(self, tmp_path, monkeypatch): """Classic CLI must expose terminal.vercel_runtime to terminal_tool.py."""