From 199544e054abdb6431a61d3cf9fb00457eb31ac5 Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Sat, 12 Sep 2026 08:37:51 -0700 Subject: [PATCH] fix(openrouter): canonical config URL keeps the pool; mirror key follows the selected endpoint Two regressions in the mirror support: (1) the canonical https://openrouter.ai/api/v1 that `hermes setup` persists under provider: openrouter was treated as a custom endpoint, dropping the auth.json credential pool and returning an empty API key; (2) an unrelated CUSTOM_BASE_URL (which outranks the config mirror) still received OPENROUTER_API_KEY because key selection tested mirror eligibility, not the endpoint actually selected. A config URL is a mirror only when its host is not openrouter.ai, and the mirror key branch fires only when base_url is the config URL. Found by independent review before merge. --- hermes_cli/runtime_provider.py | 10 ++++++++-- hermes_cli/runtime_provider_backends.py | 2 +- tests/hermes_cli/test_runtime_provider_resolution.py | 12 ++++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/hermes_cli/runtime_provider.py b/hermes_cli/runtime_provider.py index 026b318299..1f65c5e5c8 100644 --- a/hermes_cli/runtime_provider.py +++ b/hermes_cli/runtime_provider.py @@ -491,8 +491,14 @@ def _openrouter_should_use_pool(requested_provider, model_cfg, explicit_api_key, """OpenRouter pool only for a plain openrouter/auto request with no custom endpoint or override.""" cfg_base_url = str(model_cfg.get("base_url") or "").strip() env_base_urls = _getenv("OPENAI_BASE_URL", "").strip() or _getenv("OPENROUTER_BASE_URL", "").strip() - has_custom_endpoint = bool(explicit_base_url or env_base_urls - or (cfg_base_url and _cfg_provider(model_cfg) in {"auto", "custom", "openrouter"})) + # A config base_url under provider: openrouter is a mirror only when it is NOT the canonical + # OpenRouter host — `hermes setup` persists https://openrouter.ai/api/v1 for plain installs, + # and treating that as custom would drop the auth.json pool (empty key). + cfg_is_mirror = bool(cfg_base_url) and ( + _cfg_provider(model_cfg) in {"auto", "custom"} + or (_cfg_provider(model_cfg) == "openrouter" and not base_url_host_matches(cfg_base_url, "openrouter.ai")) + ) + has_custom_endpoint = bool(explicit_base_url or env_base_urls or cfg_is_mirror) return requested_provider in {"openrouter", "auto"} and not has_custom_endpoint and not bool(explicit_api_key or explicit_base_url) diff --git a/hermes_cli/runtime_provider_backends.py b/hermes_cli/runtime_provider_backends.py index 75ed38b67e..17806bfced 100644 --- a/hermes_cli/runtime_provider_backends.py +++ b/hermes_cli/runtime_provider_backends.py @@ -146,7 +146,7 @@ def _resolve_openrouter_runtime( # OPENROUTER_API_KEY for it (#10622). is_openrouter_context = is_openrouter_url or ( requested_norm == "openrouter" and ( - (use_config_base_url and cfg_provider == "openrouter") + (use_config_base_url and cfg_provider == "openrouter" and base_url == cfg_base_url.strip().rstrip("/")) or ((env_openrouter_base_url or base_url == env_openrouter_base_url) and base_url == (env_openrouter_base_url or "").rstrip("/")) ) diff --git a/tests/hermes_cli/test_runtime_provider_resolution.py b/tests/hermes_cli/test_runtime_provider_resolution.py index 9023a3cb80..725465382b 100644 --- a/tests/hermes_cli/test_runtime_provider_resolution.py +++ b/tests/hermes_cli/test_runtime_provider_resolution.py @@ -932,6 +932,18 @@ def test_explicit_openrouter_config_mirror_bypasses_pool(monkeypatch): assert resolved["api_key"] == "router-key" assert resolved.get("credential_pool") is None + # The canonical URL that `hermes setup` persists is NOT a mirror: the pool must still serve it. + monkeypatch.setattr(rp, "_get_model_config", lambda: {"provider": "openrouter", "base_url": "https://openrouter.ai/api/v1"}) + canonical = rp.resolve_runtime_provider(requested="openrouter") + assert canonical["api_key"] == "pool-key" and canonical.get("credential_pool") is not None + + # An unrelated CUSTOM_BASE_URL outranks the mirror and must not receive the OpenRouter key. + monkeypatch.setattr(rp, "_get_model_config", lambda: {"provider": "openrouter", "base_url": "https://openrouter-mirror.example.com/api/v1"}) + monkeypatch.setattr(rp, "load_pool", lambda _provider: SimpleNamespace(has_credentials=lambda: False)) + monkeypatch.setenv("CUSTOM_BASE_URL", "http://localhost:11434/v1") + custom = rp.resolve_runtime_provider(requested="openrouter") + assert custom["base_url"] == "http://localhost:11434/v1" and custom["api_key"] != "router-key" +