fix(cli): honor config base_url mirror for explicit openrouter provider
When config.yaml sets `model.provider: openrouter` together with a `model.base_url` mirror/proxy, an explicit `--provider openrouter` request ignored the mirror and sent traffic to the public OpenRouter endpoint: the config base_url was only trusted for auto/custom, the credential pool was still consulted (so a pooled key won over the mirror), and even when the mirror URL was used its host failed the openrouter.ai match so OPENROUTER_API_KEY was not selected for it. Trust the config base_url for the explicit openrouter case, treat that mirror as an OpenRouter context for key selection, and bypass the pool like the other custom-endpoint cases already do. Fixes #10622
This commit is contained in:
@@ -491,7 +491,8 @@ 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"}))
|
||||
has_custom_endpoint = bool(explicit_base_url or env_base_urls
|
||||
or (cfg_base_url and _cfg_provider(model_cfg) in {"auto", "custom", "openrouter"}))
|
||||
return requested_provider in {"openrouter", "auto"} and not has_custom_endpoint and not bool(explicit_api_key or explicit_base_url)
|
||||
|
||||
|
||||
|
||||
@@ -131,6 +131,8 @@ def _resolve_openrouter_runtime(
|
||||
use_config_base_url = bool(cfg_base_url.strip()) and not explicit_base_url and (
|
||||
(requested_norm == "auto" and cfg_provider in ("", "auto"))
|
||||
or (requested_norm == "custom" and rp._config_base_url_trustworthy_for_bare_custom(cfg_base_url, cfg_provider))
|
||||
# provider: openrouter + base_url in config.yaml is a deliberate mirror/proxy (#10622).
|
||||
or (requested_norm == "openrouter" and cfg_provider == "openrouter")
|
||||
)
|
||||
base_url = ((explicit_base_url or "").strip() or env_custom_base_url or (cfg_base_url.strip() if use_config_base_url else "")
|
||||
or env_openrouter_base_url or OPENROUTER_BASE_URL).rstrip("/")
|
||||
@@ -138,11 +140,16 @@ def _resolve_openrouter_runtime(
|
||||
# prefer OPENROUTER_API_KEY (issue #289). When hitting a custom endpoint (e.g. Z.ai, local LLM), prefer
|
||||
# OPENAI_API_KEY so the OpenRouter key doesn't leak to an unrelated provider (issues #420, #560).
|
||||
is_openrouter_url = base_url_host_matches(base_url, "openrouter.ai")
|
||||
# Explicitly-configured OpenRouter mirrors (OPENROUTER_BASE_URL + provider=openrouter) still
|
||||
# count as OpenRouter for key selection.
|
||||
# Explicitly-configured OpenRouter mirrors (OPENROUTER_BASE_URL, or a config.yaml base_url under
|
||||
# provider: openrouter) still count as OpenRouter for key selection — otherwise the mirror's host
|
||||
# fails the openrouter.ai match and the generic custom-endpoint branch never selects
|
||||
# OPENROUTER_API_KEY for it (#10622).
|
||||
is_openrouter_context = is_openrouter_url or (
|
||||
requested_norm == "openrouter" and (env_openrouter_base_url or base_url == env_openrouter_base_url)
|
||||
and base_url == (env_openrouter_base_url or "").rstrip("/")
|
||||
requested_norm == "openrouter" and (
|
||||
(use_config_base_url and cfg_provider == "openrouter")
|
||||
or ((env_openrouter_base_url or base_url == env_openrouter_base_url)
|
||||
and base_url == (env_openrouter_base_url or "").rstrip("/"))
|
||||
)
|
||||
)
|
||||
if is_openrouter_context:
|
||||
candidates = [explicit_api_key, rp._getenv("OPENROUTER_API_KEY"), rp._getenv("OPENAI_API_KEY")]
|
||||
|
||||
@@ -868,6 +868,71 @@ def test_explicit_openrouter_skips_openai_base_url(monkeypatch):
|
||||
assert resolved["api_key"] == "or-test-key"
|
||||
|
||||
|
||||
def test_explicit_openrouter_honors_config_base_url_mirror(monkeypatch):
|
||||
"""requested='openrouter' + config provider='openrouter' + config base_url must
|
||||
resolve to that mirror AND still select OPENROUTER_API_KEY for it — a
|
||||
config-sourced mirror is an OpenRouter context for credential selection just
|
||||
like the OPENROUTER_BASE_URL env path already is. Regression test for #10622."""
|
||||
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "openrouter")
|
||||
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.delenv("OPENAI_BASE_URL", raising=False)
|
||||
monkeypatch.delenv("OPENROUTER_BASE_URL", raising=False)
|
||||
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
||||
monkeypatch.setenv("OPENROUTER_API_KEY", "router-key")
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="openrouter")
|
||||
|
||||
assert resolved["provider"] == "openrouter"
|
||||
assert resolved["base_url"] == "https://openrouter-mirror.example.com/api/v1"
|
||||
assert resolved["api_key"] == "router-key"
|
||||
|
||||
|
||||
def test_explicit_openrouter_config_mirror_bypasses_pool(monkeypatch):
|
||||
"""A config.yaml mirror under provider='openrouter' is a custom endpoint: the
|
||||
OpenRouter credential pool must be skipped rather than silently routing the
|
||||
request to openrouter.ai with a pooled key (#10622)."""
|
||||
class _Entry:
|
||||
access_token = "pool-key"
|
||||
source = "manual"
|
||||
base_url = "https://openrouter.ai/api/v1"
|
||||
|
||||
class _Pool:
|
||||
def has_credentials(self):
|
||||
return True
|
||||
|
||||
def select(self):
|
||||
return _Entry()
|
||||
|
||||
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "openrouter")
|
||||
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: _Pool())
|
||||
monkeypatch.delenv("OPENAI_BASE_URL", raising=False)
|
||||
monkeypatch.delenv("OPENROUTER_BASE_URL", raising=False)
|
||||
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
||||
monkeypatch.setenv("OPENROUTER_API_KEY", "router-key")
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="openrouter")
|
||||
|
||||
assert resolved["base_url"] == "https://openrouter-mirror.example.com/api/v1"
|
||||
assert resolved["api_key"] == "router-key"
|
||||
assert resolved.get("credential_pool") is None
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user