refactor(secrets): drop scope-aware env shims; runtime_provider and the voice/xai tools read the canonical getters
hermes_cli/runtime_provider._getenv was a 4-line copy of get_secret(name, default) or default; it becomes agent.secret_scope.get_secret_str (returns default only when the secret is genuinely unset, still raises UnscopedSecretError — a child's unscoped read is a spawn-site bug). The runtime_provider_backends/_custom siblings call it directly instead of via the origin module. tools/tts_tool, tools/transcription_tools and tools/xai_http each carried an identical get_env_value re-export kept "so tests can patch" it; the seam is hermes_cli.config.get_env_value, read lazily at call time. Callers (tts_streaming, tts_tool_providers, transcription_cloud, voice_client_config, tools_config) go there directly; resolve_provider_secret already defaults to it so the env_getter kwarg is gone. Tests repointed at the canonical; the two tests that only proved the shim forwarded are deleted. Behavior change: none.
This commit is contained in:
@@ -20,7 +20,7 @@ from agent.credential_pool import ( # custom_provider_pool_key_candidates is re
|
||||
CredentialPool, PooledCredential, credential_pool_matches_provider, custom_provider_pool_key_candidates, # noqa: F401
|
||||
load_pool,
|
||||
)
|
||||
from agent.secret_scope import get_secret as _get_secret
|
||||
from agent.secret_scope import get_secret_str
|
||||
from hermes_cli.auth import ( # resolve_external_process_provider_credentials is read via origin by runtime_provider_backends
|
||||
ACTUAL_LOCAL_NOAUTH_PLACEHOLDER, AuthError, DEFAULT_CODEX_BASE_URL, DEFAULT_QWEN_BASE_URL, DEFAULT_XAI_OAUTH_BASE_URL,
|
||||
PROVIDER_REGISTRY, _agent_key_is_usable, _nous_inference_env_override, format_auth_error, resolve_provider,
|
||||
@@ -51,13 +51,6 @@ def normalize_extra_headers(value):
|
||||
return _config_mod.normalize_extra_headers(value)
|
||||
|
||||
|
||||
def _getenv(name: str, default: str = "") -> str:
|
||||
"""Profile-scoped ``os.getenv`` for credential/provider reads: identical to ``os.getenv`` when
|
||||
multiplexing is off; scope-aware (fail-closed on an unscoped read) when on."""
|
||||
val = _get_secret(name, default)
|
||||
return val if val is not None else default
|
||||
|
||||
|
||||
def _loopback_hostname(host: str) -> bool:
|
||||
return (host or "").lower().rstrip(".") in {"localhost", "127.0.0.1", "::1", "0.0.0.0"}
|
||||
|
||||
@@ -309,7 +302,7 @@ def _host_derived_api_key(base_url: str) -> str:
|
||||
sanitized = "".join(ch if ch.isalnum() else "_" for ch in labels[-2]).upper() if len(labels) >= 2 else ""
|
||||
if not sanitized or not sanitized[0].isalpha() or sanitized in ("OPENAI", "OPENROUTER", "OLLAMA"):
|
||||
return ""
|
||||
return (_getenv(f"{sanitized}_API_KEY", "") or "").strip()
|
||||
return (get_secret_str(f"{sanitized}_API_KEY", "") or "").strip()
|
||||
|
||||
|
||||
def _host_gated_env_key_candidates(base_url: str, *, ollama: bool) -> list:
|
||||
@@ -318,9 +311,9 @@ def _host_gated_env_key_candidates(base_url: str, *, ollama: bool) -> list:
|
||||
(GHSA-76xc-57q6-vm5m); match on HOST, not substring. ``_host_derived_api_key`` skips OLLAMA, so
|
||||
callers that want it opt in via ``ollama``."""
|
||||
is_openai = base_url_host_matches(base_url, "openai.com") or base_url_host_matches(base_url, "openai.azure.com")
|
||||
candidates = [_getenv("OLLAMA_API_KEY", "").strip() if base_url_host_matches(base_url, "ollama.com") else ""] if ollama else []
|
||||
return candidates + [_getenv("OPENAI_API_KEY", "").strip() if is_openai else "",
|
||||
_getenv("OPENROUTER_API_KEY", "").strip() if base_url_host_matches(base_url, "openrouter.ai") else "",
|
||||
candidates = [get_secret_str("OLLAMA_API_KEY", "").strip() if base_url_host_matches(base_url, "ollama.com") else ""] if ollama else []
|
||||
return candidates + [get_secret_str("OPENAI_API_KEY", "").strip() if is_openai else "",
|
||||
get_secret_str("OPENROUTER_API_KEY", "").strip() if base_url_host_matches(base_url, "openrouter.ai") else "",
|
||||
_host_derived_api_key(base_url)]
|
||||
|
||||
|
||||
@@ -341,7 +334,7 @@ def _nous_min_key_ttl() -> int:
|
||||
|
||||
|
||||
def _resolve_nous_creds() -> Dict[str, Any]:
|
||||
return resolve_nous_runtime_credentials(timeout_seconds=float(_getenv("HERMES_NOUS_TIMEOUT_SECONDS", "15")))
|
||||
return resolve_nous_runtime_credentials(timeout_seconds=float(get_secret_str("HERMES_NOUS_TIMEOUT_SECONDS", "15")))
|
||||
|
||||
|
||||
def _finalize_base_url(provider: str, api_mode: str, base_url: str) -> str:
|
||||
@@ -414,7 +407,7 @@ def resolve_requested_provider(requested: Optional[str] = None) -> str:
|
||||
cfg_provider = _get_model_config().get("provider")
|
||||
if isinstance(cfg_provider, str) and cfg_provider.strip():
|
||||
return cfg_provider.strip().lower()
|
||||
return _getenv("HERMES_INFERENCE_PROVIDER", "").strip().lower() or "auto"
|
||||
return get_secret_str("HERMES_INFERENCE_PROVIDER", "").strip().lower() or "auto"
|
||||
|
||||
|
||||
# ── extracted collaborators (re-exported; see module docstring) ────────────────────────────
|
||||
@@ -490,7 +483,7 @@ def _resolve_runtime_from_pool_entry(*, provider: str, entry: PooledCredential,
|
||||
def _openrouter_should_use_pool(requested_provider, model_cfg, explicit_api_key, explicit_base_url) -> bool:
|
||||
"""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()
|
||||
env_base_urls = get_secret_str("OPENAI_BASE_URL", "").strip() or get_secret_str("OPENROUTER_BASE_URL", "").strip()
|
||||
# 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).
|
||||
@@ -602,7 +595,7 @@ def _explicit_api_key_provider(provider, pconfig, requested_provider, model_cfg,
|
||||
elif provider in {"kimi-coding", "kimi-coding-cn"}:
|
||||
base_url = resolve_api_key_provider_credentials(provider).get("base_url", "").rstrip("/")
|
||||
else:
|
||||
env_url = _getenv(pconfig.base_url_env_var, "").strip().rstrip("/") if pconfig.base_url_env_var else ""
|
||||
env_url = get_secret_str(pconfig.base_url_env_var, "").strip().rstrip("/") if pconfig.base_url_env_var else ""
|
||||
base_url = env_url or pconfig.inference_base_url
|
||||
base_url = _actual_url(provider, base_url)
|
||||
if not api_key:
|
||||
@@ -705,10 +698,10 @@ def _azure_anthropic_env_key(model_cfg: Dict[str, Any]) -> str:
|
||||
api_key (multi-profile setups), then the historical fixed names."""
|
||||
for hint_key in ("key_env", "api_key_env"):
|
||||
env_var = str(model_cfg.get(hint_key) or "").strip()
|
||||
if env_var and (token := _getenv(env_var, "").strip()):
|
||||
if env_var and (token := get_secret_str(env_var, "").strip()):
|
||||
return token
|
||||
return (str(model_cfg.get("api_key") or "").strip() or _getenv("AZURE_ANTHROPIC_KEY", "").strip()
|
||||
or _getenv("ANTHROPIC_API_KEY", "").strip())
|
||||
return (str(model_cfg.get("api_key") or "").strip() or get_secret_str("AZURE_ANTHROPIC_KEY", "").strip()
|
||||
or get_secret_str("ANTHROPIC_API_KEY", "").strip())
|
||||
|
||||
|
||||
def _anthropic_env_runtime(requested_provider: str, model_cfg: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
||||
Reference in New Issue
Block a user