Files
hermes-agent/tools/openrouter_client.py
beardthelion 0cbe552888 fix(scope): failed profile-scoped secret reads must not borrow os.environ
Eight secret readers wrapped the scoped get_secret() call in a broad
except Exception / contextlib.suppress and fell through to os.environ.
Under multiplex that env holds the default profile's value, so a bound
scope whose resolution fails silently borrowed another profile's
credential: the pairing allowlist reader could then persist the foreign
list into the served profile's .env, and the proxy key, tool gateway
token, OpenRouter and aux provider keys, ElevenLabs key, and Slack token
probe had the same shape.

Keep the deliberate UnscopedSecretError -> os.environ fallback (the
unscoped default-profile path legitimately reads its own env) and let
every other scoped-read failure propagate; the two availability probes
fail closed instead. config._scoped_environ_get now propagates as its
docstring already claimed.
2026-09-23 06:46:02 -07:00

43 lines
1.6 KiB
Python

"""OpenRouter API key probe shared by Hermes tools."""
import os
def check_api_key() -> bool:
"""Return True if OPENROUTER_API_KEY is present.
Scope-aware: an installed profile secret scope is authoritative under
multiplex; unscoped CLI probes fall back to the plain env read. Any other
scope failure propagates -- a failed scoped read must never silently
borrow another profile's key from ``os.environ``.
"""
from agent.secret_scope import UnscopedSecretError, get_secret
try:
return bool(get_secret("OPENROUTER_API_KEY"))
except UnscopedSecretError:
return bool(os.getenv("OPENROUTER_API_KEY"))
# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ----
# Names external plugins imported from this module before the Sep 2026 decomposition.
# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does).
# The whole block is removed by reverting the commit that added it.
def get_async_client():
"""Return a shared async OpenAI-compatible client for OpenRouter.
The client is created lazily on first call and reused thereafter.
Uses the centralized provider router for auth and client construction.
Raises ValueError if OPENROUTER_API_KEY is not set.
"""
global _client
if _client is None:
from agent.auxiliary_client import resolve_provider_client
client, _model = resolve_provider_client("openrouter", async_mode=True)
if client is None:
raise ValueError("OPENROUTER_API_KEY environment variable not set")
_client = client
return _client
# ---- END PLUGIN-COMPAT ----