fix(secrets): route auxiliary-client provider key reads through the profile secret scope
OPENROUTER_API_KEY (client construction, unavailability description, max-tokens param heuristic), OPENAI_API_KEY (custom runtime + explicit custom endpoint), named-custom-provider key_env, and auxiliary task-config key_env now resolve via a shared _scoped_key_env helper (get_secret with UnscopedSecretError → os.environ fallback for unscoped CLI/startup probes).
This commit is contained in:
parent
5c6cc38010
commit
ca5ce1110b
|
|
@ -1133,6 +1133,29 @@ def _nous_min_key_ttl_seconds() -> int:
|
|||
return 1800
|
||||
|
||||
|
||||
def _scoped_key_env(name: str) -> str:
|
||||
"""Read a provider API key env var through the profile secret scope.
|
||||
|
||||
Auxiliary-client resolution runs both inside agent turns (secret scope
|
||||
installed — its verdict is authoritative under multiplex, so a scoped
|
||||
miss must NOT borrow another profile's process-env key) and on unscoped
|
||||
startup/CLI probe paths, which keep the legacy ``os.environ`` read via
|
||||
the ``UnscopedSecretError`` fallback (Slack pattern, #59739).
|
||||
"""
|
||||
if not name:
|
||||
return ""
|
||||
try:
|
||||
from agent.secret_scope import UnscopedSecretError, get_secret
|
||||
|
||||
try:
|
||||
return (get_secret(name) or "").strip()
|
||||
except UnscopedSecretError:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
return (os.getenv(name) or "").strip()
|
||||
|
||||
|
||||
# ── Codex Responses → chat.completions adapter ─────────────────────────────
|
||||
# All auxiliary consumers call client.chat.completions.create(**kwargs) and
|
||||
# read response.choices[0].message.content. This adapter translates those
|
||||
|
|
@ -2470,7 +2493,7 @@ def _try_openrouter(explicit_api_key: str = None, model: str = None) -> Tuple[Op
|
|||
# the OPENROUTER_API_KEY env-var path rather than failing outright.
|
||||
logger.debug("Auxiliary client: OpenRouter pool exhausted, trying OPENROUTER_API_KEY")
|
||||
|
||||
or_key = explicit_api_key or os.getenv("OPENROUTER_API_KEY")
|
||||
or_key = explicit_api_key or _scoped_key_env("OPENROUTER_API_KEY")
|
||||
if not or_key:
|
||||
_mark_provider_unhealthy("openrouter", ttl=60)
|
||||
return None, None
|
||||
|
|
@ -2487,7 +2510,7 @@ def _describe_openrouter_unavailable() -> str:
|
|||
return "OpenRouter credential pool has no usable entries (credentials may be exhausted)"
|
||||
if not _pool_runtime_api_key(entry):
|
||||
return "OpenRouter credential pool entry is missing a runtime API key"
|
||||
if not str(os.getenv("OPENROUTER_API_KEY") or "").strip():
|
||||
if not _scoped_key_env("OPENROUTER_API_KEY"):
|
||||
return "OPENROUTER_API_KEY not set"
|
||||
return "no usable OpenRouter credentials found"
|
||||
|
||||
|
|
@ -3119,7 +3142,7 @@ def _resolve_custom_runtime() -> Tuple[Optional[str], Optional[str], Optional[st
|
|||
|
||||
if not isinstance(runtime, dict):
|
||||
openai_base = os.getenv("OPENAI_BASE_URL", "").strip().rstrip("/")
|
||||
openai_key = os.getenv("OPENAI_API_KEY", "").strip()
|
||||
openai_key = _scoped_key_env("OPENAI_API_KEY")
|
||||
if not openai_base:
|
||||
return None, None, None
|
||||
runtime = {
|
||||
|
|
@ -5925,7 +5948,7 @@ def resolve_provider_client(
|
|||
custom_base = _to_openai_base_url(explicit_base_url).strip()
|
||||
custom_key = (
|
||||
(explicit_api_key or "").strip()
|
||||
or os.getenv("OPENAI_API_KEY", "").strip()
|
||||
or _scoped_key_env("OPENAI_API_KEY")
|
||||
or _read_main_api_key_if_same_host(custom_base)
|
||||
or "no-key-required" # local servers don't need auth
|
||||
)
|
||||
|
|
@ -6021,7 +6044,7 @@ def resolve_provider_client(
|
|||
custom_key = (custom_entry.get("api_key") or "").strip()
|
||||
custom_key_env = (custom_entry.get("key_env") or custom_entry.get("api_key_env") or "").strip()
|
||||
if not custom_key and custom_key_env:
|
||||
custom_key = os.getenv(custom_key_env, "").strip()
|
||||
custom_key = _scoped_key_env(custom_key_env)
|
||||
custom_key = custom_key or "no-key-required"
|
||||
if custom_key == "no-key-required":
|
||||
logger.warning(
|
||||
|
|
@ -6843,7 +6866,7 @@ def auxiliary_max_tokens_param(value: int, *, model: Optional[str] = None) -> di
|
|||
misses the case where a custom base URL serves e.g. ``gpt-5.4``.
|
||||
"""
|
||||
custom_base = _current_custom_base_url()
|
||||
or_key = os.getenv("OPENROUTER_API_KEY")
|
||||
or_key = _scoped_key_env("OPENROUTER_API_KEY")
|
||||
# Use max_completion_tokens for direct OpenAI-compatible providers that reject
|
||||
# max_tokens on newer GPT-4o/o-series/GPT-5-style models.
|
||||
_custom_host = base_url_hostname(custom_base) or ""
|
||||
|
|
@ -7304,7 +7327,7 @@ def _resolve_task_provider_model(
|
|||
task_config.get("key_env") or task_config.get("api_key_env") or ""
|
||||
).strip()
|
||||
if cfg_key_env:
|
||||
cfg_api_key = os.getenv(cfg_key_env, "").strip() or None
|
||||
cfg_api_key = _scoped_key_env(cfg_key_env) or None
|
||||
cfg_api_mode = str(task_config.get("api_mode", "")).strip() or None
|
||||
|
||||
# 'auto' is a sentinel meaning "inherit from main runtime / auto-detect", not
|
||||
|
|
|
|||
Loading…
Reference in New Issue