fix(runtime): read the OpenRouter fallback keys from .env after an exhausted pool entry

This commit is contained in:
liuhao1024
2026-09-21 06:46:09 +08:00
committed by brooklyn!
parent 5b671e29a4
commit b6e8e657cf
2 changed files with 35 additions and 2 deletions

View File

@@ -157,18 +157,23 @@ def _resolve_openrouter_runtime(
)
)
if is_openrouter_context:
# Same read order as the credential pool and _resolve_api_key_provider_secret
# (.env, then scope-aware os.environ, raw op:// references yielding to the resolved
# scoped value). get_secret_str sees os.environ only, so a key living solely in
# ~/.hermes/.env was lost once the pool entry went exhausted/benched (#117667).
# OPENAI_API_KEY is a legacy home for an OpenRouter key. When OPENAI_BASE_URL binds it, it
# goes only to that origin: another scheme or port on the same host is another endpoint.
# Unbound, openrouter.ai gets it only when it is OpenRouter-shaped (sk-or-), so a real OpenAI
# key never reaches a third party.
openai_key = get_secret_str("OPENAI_API_KEY")
from hermes_cli.config import get_env_value_prefer_dotenv
openai_key = get_env_value_prefer_dotenv("OPENAI_API_KEY") or ""
openai_base_url = get_secret_str("OPENAI_BASE_URL", "").strip()
if base_url_hostname(openai_base_url):
openai_origin = base_url_origin(openai_base_url) # empty on a bad port: bound, matches nothing
openai_key_ok = bool(openai_origin[1]) and openai_origin == base_url_origin(base_url)
else:
openai_key_ok = not is_openrouter_url or rp.looks_like_openrouter_key(openai_key)
candidates = [explicit_api_key, get_secret_str("OPENROUTER_API_KEY"),
candidates = [explicit_api_key, get_env_value_prefer_dotenv("OPENROUTER_API_KEY"),
openai_key if openai_key_ok else ""]
else:
# ``model.api_key`` and ``model.key_env`` back a trusted config base_url only; the key_env

View File

@@ -726,6 +726,34 @@ def test_openai_key_bound_to_another_host_never_reaches_openrouter(monkeypatch,
assert resolved["api_key"] == expected_key
def test_openrouter_env_file_key_survives_exhausted_pool(monkeypatch):
"""A key living only in ~/.hermes/.env must survive an exhausted pool entry.
Regression test for #117667: with the pool's OPENROUTER_API_KEY entry benched
(select() -> None), the terminal resolver read os.environ only, so the documented
.env location was skipped and `hermes chat --provider openrouter` failed with
"No API key found" until the key was exported in the shell.
"""
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "openrouter")
monkeypatch.setattr(rp, "_get_model_config", lambda: {})
monkeypatch.setattr(
rp, "load_pool",
lambda _provider: SimpleNamespace(has_credentials=lambda: True, select=lambda **_kw: None),
)
monkeypatch.delenv("OPENAI_BASE_URL", raising=False)
monkeypatch.delenv("OPENROUTER_BASE_URL", raising=False)
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
or_env_name = "OPENROUTER_" + "API_KEY"
monkeypatch.delenv(or_env_name, raising=False)
monkeypatch.setattr(
"hermes_cli.config.load_env", lambda: {or_env_name: "dotenv-or-key"}
)
resolved = rp.resolve_runtime_provider(requested="openrouter")
assert resolved["api_key"] == "dotenv-or-key"
def test_custom_endpoint_uses_saved_config_base_url_when_env_missing(monkeypatch):
"""Persisted custom endpoints in config.yaml must still resolve when
OPENAI_BASE_URL is absent from the current environment.