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.
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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("/"))
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user