diff --git a/hermes_cli/providers.py b/hermes_cli/providers.py index 4811f4fd71..3840d9efa1 100644 --- a/hermes_cli/providers.py +++ b/hermes_cli/providers.py @@ -480,8 +480,9 @@ def _llamacpp_pdef() -> Optional[ProviderDef]: one) resolves — reachability is the credential. Without this rung model-switch rejected the very provider the Local Models 'Use' flow writes to config.""" try: + from hermes_cli.config import load_config_readonly from hermes_cli.local_runtime.endpoint import resolve_llamacpp_endpoint - endpoint = resolve_llamacpp_endpoint(wait_for_boot_s=0) + endpoint = resolve_llamacpp_endpoint(load_config_readonly(), wait_for_boot_s=0) except Exception: endpoint = None if not endpoint: diff --git a/hermes_cli/runtime_provider_custom.py b/hermes_cli/runtime_provider_custom.py index 43b79e55fd..e9f90ff49c 100644 --- a/hermes_cli/runtime_provider_custom.py +++ b/hermes_cli/runtime_provider_custom.py @@ -432,7 +432,7 @@ def _resolve_llamacpp_runtime(requested_provider: str, explicit_api_key: Optiona rp = _rp() try: from hermes_cli.local_runtime.endpoint import resolve_llamacpp_endpoint - endpoint = resolve_llamacpp_endpoint() + endpoint = resolve_llamacpp_endpoint(rp.load_config()) except Exception: # noqa: BLE001 — resolution is best-effort endpoint = None if endpoint: @@ -533,13 +533,16 @@ def _resolve_named_custom_runtime(*, requested_provider: str, explicit_api_key: # treated identically here, so a YAML `provider: ollama` with a LAN/WireGuard `base_url` doesn't # silently fall through to OpenRouter. requested_norm = (requested_provider or "").strip().lower() + custom_provider = None if requested_norm in _LLAMACPP_ALIASES and not explicit_base_url: - return _resolve_llamacpp_runtime(requested_provider, explicit_api_key) + custom_provider = rp._get_named_custom_provider(requested_provider) + if not custom_provider: + return _resolve_llamacpp_runtime(requested_provider, explicit_api_key) if requested_norm and requested_norm != "custom" and rp._resolves_to_custom(requested_norm): requested_norm = "custom" if requested_norm == "custom" and explicit_base_url: return _resolve_direct_alias_runtime(requested_provider, explicit_api_key, explicit_base_url) - custom_provider = rp._get_named_custom_provider(requested_provider) + custom_provider = custom_provider or rp._get_named_custom_provider(requested_provider) if not custom_provider: return None base_url = ((explicit_base_url or "").strip() or custom_provider.get("base_url", "")).rstrip("/") diff --git a/tests/hermes_cli/test_local_runtime.py b/tests/hermes_cli/test_local_runtime.py index 50fb9753f4..67e201729b 100644 --- a/tests/hermes_cli/test_local_runtime.py +++ b/tests/hermes_cli/test_local_runtime.py @@ -786,6 +786,61 @@ def test_runtime_provider_seam_llamacpp_alias(tmp_path, monkeypatch, stub_server assert runtime["provider"] == "custom" +def test_configured_llamacpp_provider_wins_over_managed_alias(tmp_path, monkeypatch): + """A providers.llamacpp endpoint is explicit configuration, not a managed-runtime request.""" + monkeypatch.setenv("HERMES_HOME", str(tmp_path / ".hermes")) + monkeypatch.setattr( + "hermes_cli.config.load_config", + lambda: { + "providers": { + "llamacpp": { + "base_url": "http://127.0.0.1:8081/v1", + "default_model": "configured-model", + } + } + }, + ) + + def _managed_alias_must_not_run(*args, **kwargs): + raise AssertionError("configured providers.llamacpp must resolve before managed detection") + + monkeypatch.setattr( + "hermes_cli.local_runtime.endpoint.resolve_llamacpp_endpoint", + _managed_alias_must_not_run, + ) + from hermes_cli.runtime_provider import _resolve_named_custom_runtime + + runtime = _resolve_named_custom_runtime(requested_provider="llamacpp") + + assert runtime is not None + assert runtime["base_url"] == "http://127.0.0.1:8081/v1" + assert runtime["model"] == "configured-model" + assert runtime["source"].startswith("custom_provider:") + + +def test_llamacpp_runtime_forwards_detect_ports_config(monkeypatch): + """The managed-alias fallback passes local_runtime.detect_ports to endpoint detection.""" + config = {"local_runtime": {"enabled": True, "detect_ports": [8081]}} + monkeypatch.setattr("hermes_cli.config.load_config", lambda: config) + seen = [] + + def _resolve(passed_config, **kwargs): + seen.append(passed_config) + return {"base_url": "http://127.0.0.1:8081/v1", "api_key": ""} + + monkeypatch.setattr( + "hermes_cli.local_runtime.endpoint.resolve_llamacpp_endpoint", + _resolve, + ) + from hermes_cli.runtime_provider import _resolve_named_custom_runtime + + runtime = _resolve_named_custom_runtime(requested_provider="llamacpp") + + assert seen == [config] + assert runtime is not None + assert runtime["base_url"] == "http://127.0.0.1:8081/v1" + + def test_runtime_provider_seam_explicit_base_url_wins(tmp_path, monkeypatch): """A user-specified base_url must never be overridden by the managed endpoint — pointing at a specific server means that server."""