refactor(hermes_cli): fold lone docstring closers in auth modules

This commit is contained in:
Teknium
2026-09-02 21:34:40 -07:00
parent d11334c899
commit 3b48dbca46
2 changed files with 25 additions and 50 deletions

View File

@@ -7,8 +7,7 @@
- ``resolve_provider()`` picks the active provider via the documented priority chain.
- ``OAUTH_PROVIDER_FLOWS`` maps each OAuth provider to its resolver/status builder; the flows live in
``auth_nous``/``auth_codex``/``auth_xai``/``auth_qwen``/``auth_minimax``/``auth_spotify`` and are
re-imported here so ``hermes_cli.auth.<name>`` stays the public/patchable surface.
"""
re-imported here so ``hermes_cli.auth.<name>`` stays the public/patchable surface."""
from __future__ import annotations
@@ -295,8 +294,7 @@ except Exception:
def get_anthropic_key() -> str:
"""First usable Anthropic credential (``.env`` preferred over a stale shell export), or ``""``.
Order mirrors ``PROVIDER_REGISTRY["anthropic"].api_key_env_vars``.
"""
Order mirrors ``PROVIDER_REGISTRY["anthropic"].api_key_env_vars``."""
from hermes_cli.config import get_env_value_prefer_dotenv
for var in PROVIDER_REGISTRY["anthropic"].api_key_env_vars:
@@ -474,8 +472,7 @@ def _global_auth_file_path() -> Optional[Path]:
(classic mode, or a custom HERMES_HOME that is not a profile).
Read-only fallback path, so no pytest seat belt here: ``_load_global_auth_store()`` wraps the
read in try/except; the write-side seat belt lives on ``_auth_file_path()``.
"""
read in try/except; the write-side seat belt lives on ``_auth_file_path()``."""
try:
from hermes_constants import get_default_hermes_root
global_root = get_default_hermes_root()
@@ -558,8 +555,7 @@ def _file_lock(
Falls back to a depth-only guard when neither ``fcntl`` nor ``msvcrt`` is available. Callers
supply their own ``threading.local`` so independent locks (profile store vs global root vs the
shared Nous store) track reentrancy separately.
"""
shared Nous store) track reentrancy separately."""
if getattr(holder, "depth", 0) > 0:
holder.depth += 1
try:
@@ -614,8 +610,7 @@ def _auth_store_lock(
Lock ordering invariant: when held together with ``_nous_shared_store_lock``, acquire
``_auth_store_lock`` FIRST (outer) and the shared Nous lock SECOND (inner). Violating it risks
deadlock against a concurrent import on the shared store.
"""
deadlock against a concurrent import on the shared store."""
auth_path = target_path if target_path is not None else _auth_file_path()
with _file_lock(
auth_path.with_suffix(".lock"), _auth_lock_holder_for(auth_path), timeout_seconds,
@@ -690,8 +685,7 @@ def _write_private_file_atomic(
``os.open(O_EXCL, 0o600)`` closes the TOCTOU window where ``write_text()`` + post-write
``chmod`` briefly exposed tokens at process umask. The per-process random temp suffix avoids
collisions between concurrent writers and stale leftovers from a crashed prior write.
"""
collisions between concurrent writers and stale leftovers from a crashed prior write."""
target.parent.mkdir(parents=True, exist_ok=True)
secure_parent_dir(target) # refuses to chmod /, top-level dirs, or the install tree
tmp_path = target.with_name(f"{target.name}.tmp.{os.getpid()}.{uuid.uuid4().hex}")
@@ -755,8 +749,7 @@ def _load_provider_state_with_source(
"""Provider state plus the auth.json path it came from (profile first, then the global root).
Refresh paths that rotate single-use OAuth refresh tokens must write the updated chain back to
the same store they read.
"""
the same store they read."""
state = _provider_state_in(auth_store, provider_id)
if state is not None:
return state, _auth_file_path()
@@ -777,8 +770,7 @@ def _provider_state_transaction(provider_id: str):
Profile-backed refresh paths must take the global auth-store lock before any provider-specific
shared-store lock. Re-reading the source after the target lock is acquired prevents both stale
refreshes and whole-file lost updates without inverting the documented auth -> shared lock
order.
"""
order."""
with _auth_store_lock():
auth_store = _load_auth_store()
state, source_path = _load_provider_state_with_source(auth_store, provider_id)
@@ -875,8 +867,7 @@ def read_credential_pool(provider_id: Optional[str] = None) -> Dict[str, Any]:
In profile mode the profile's pool is authoritative; the global-root ``auth.json`` is a
read-only fallback applied per provider ONLY when the profile has zero entries for that
provider, so profile workers can see globally-authed providers while ``hermes auth add`` inside
the profile fully shadows global on the next read.
"""
the profile fully shadows global on the next read."""
pool = _load_auth_store().get("credential_pool")
pool = pool if isinstance(pool, dict) else {}
global_store = _load_global_auth_store()
@@ -913,8 +904,7 @@ def _merge_disk_cooldown_state(
``write_credential_pool`` persists an in-memory snapshot that may predate another process
marking the same credential exhausted/dead; without this merge the later rewrite resurrects a
rate-limited key as healthy and both processes resume hammering it.
"""
rate-limited key as healthy and both processes resume hammering it."""
if not isinstance(disk_entry, dict):
return entry
try:
@@ -958,8 +948,7 @@ def write_credential_pool(
Entries present on disk but missing from *entries* (added by another process after the caller's
snapshot) are merged back in unless listed in *removed_ids*, so a rotation/exhaustion rewrite
never drops a concurrent credential.
"""
never drops a concurrent credential."""
removed = {rid for rid in (removed_ids or ()) if rid}
with _auth_store_lock():
auth_store = _load_auth_store()
@@ -1060,8 +1049,7 @@ def _config_selects_provider(normalized: str) -> bool:
MoA presets are explicit model selections too: ``provider: anthropic`` in a MoA slot opts into
Anthropic credentials for that slot even when the main model is another provider; otherwise
Claude Code OAuth entries get pruned by ``load_pool("anthropic")`` and MoA advisors fail with
"no ANTHROPIC_API_KEY" while the picker says Anthropic is logged in.
"""
"no ANTHROPIC_API_KEY" while the picker says Anthropic is logged in."""
from hermes_cli.config import load_config
cfg = load_config()
if _slot_selects(cfg.get("model"), normalized):
@@ -1103,8 +1091,7 @@ def _explicit_env_credentials_present(normalized: str) -> bool:
openrouter; same ``.auth_type`` / ``.api_key_env_vars`` shape). AWS SDK providers (Bedrock) have
empty ``api_key_env_vars``, so check their explicit env credentials directly — NOT boto3's full
chain: ambient EC2 IMDS / SSO profiles must not auto-surface, but AWS_BEARER_TOKEN_BEDROCK or an
access-key pair in .env is as explicit as pasting ANTHROPIC_API_KEY.
"""
access-key pair in .env is as explicit as pasting ANTHROPIC_API_KEY."""
pconfig = PROVIDER_REGISTRY.get(normalized)
if pconfig is None:
from hermes_cli.providers import get_provider
@@ -1138,8 +1125,7 @@ def _keyless_provider_has_explicit_config(normalized: str) -> bool:
Uses has_explicit_vertex_config(), NOT has_vertex_credentials(): the latter also counts an
ambient GOOGLE_APPLICATION_CREDENTIALS path (commonly set for unrelated GCP work). Only
Hermes-scoped signals (VERTEX_PROJECT_ID / vertex.project_id / VERTEX_CREDENTIALS_PATH) count
here.
"""
here."""
if normalized in _VERTEX_PROVIDER_IDS:
from agent.vertex_adapter import has_explicit_vertex_config
return bool(has_explicit_vertex_config())
@@ -1315,8 +1301,7 @@ def _scoped_key_env_reader() -> Callable[[str], str]:
Under multiplex a secondary profile's API keys live only in its secret scope, not os.environ; a
bare getenv would report "No LLM provider configured" for every secondary profile. Catch ONLY
ImportError: any other failure inside auxiliary_client must propagate, since silently falling
back to os.getenv would reintroduce that fail-open with zero trace.
"""
back to os.getenv would reintroduce that fail-open with zero trace."""
try:
from agent.auxiliary_client import _scoped_key_env
return _scoped_key_env
@@ -1358,8 +1343,7 @@ def _config_model_provider() -> Tuple[Any, Optional[str]]:
"""``(model_cfg, provider)`` from config.yaml when ``model.provider`` names a registry provider.
The normal chat/gateway path resolves config.provider upstream in resolve_requested_provider();
this is the safety net for the lone direct caller (main.py resolve_provider("auto")).
"""
this is the safety net for the lone direct caller (main.py resolve_provider("auto"))."""
try:
from hermes_cli.config import load_config
@@ -1412,8 +1396,7 @@ def resolve_provider(
provider: 1. explicit CLI api_key/base_url -> "openrouter"; 2. config.yaml ``model.provider``;
3. OPENAI_API_KEY / OPENROUTER_API_KEY env -> "openrouter"; 4. OpenRouter credential pool;
5. provider-specific env keys; 6. auth.json ``active_provider`` (OAuth); 7. AWS Bedrock
credential chain; 8. AuthError(no_provider_configured).
"""
credential chain; 8. AuthError(no_provider_configured)."""
normalized = (requested or "auto").strip().lower()
normalized = _plugin_aliases().get(normalized, normalized)
@@ -1683,8 +1666,7 @@ def get_nous_auth_status() -> Dict[str, Any]:
"""Status snapshot for Nous auth, memoised ~15s keyed on the auth.json mtime.
Prefers the auth-store provider state (the live source of truth for refresh) and validates it by
resolving runtime credentials so revoked refresh sessions do not show up as a healthy login.
"""
resolving runtime credentials so revoked refresh sessions do not show up as a healthy login."""
global _nous_auth_status_cache
now = time.monotonic()
auth_file_key, mtime = _auth_file_cache_key()
@@ -1706,8 +1688,7 @@ class OAuthProviderFlow:
Entries name module-level callables (strings) rather than binding them, so
``monkeypatch.setattr("hermes_cli.auth.resolve_codex_runtime_credentials", ...)`` and friends
keep intercepting: ``resolve()`` / ``status()`` look the name up in this module at call time.
"""
keep intercepting: ``resolve()`` / ``status()`` look the name up in this module at call time."""
provider_id: str
resolve_fn: str
@@ -1853,8 +1834,7 @@ def _external_process_auth_evidence(provider_id: str) -> tuple[bool, Optional[st
store). False means "not verifiable from here", NOT "signed out" — the Copilot CLI may hold its
session in an OS keychain Hermes can't read. Deliberately subprocess-free: this runs from status
endpoints and pickers, and spawning ``gh auth token`` there re-creates the cold-start stall
copilot_auth.py works to avoid.
"""
copilot_auth.py works to avoid."""
if provider_id != "copilot-acp":
return False, None
# 1. Supported env tokens — the same vars the Copilot CLI itself honors.
@@ -1898,8 +1878,7 @@ def _external_process_spec(
How to launch the CLI comes from the provider's own profile, so a provider shipped outside this
tree describes its binary/args instead of inheriting another vendor's (copilot-acp's
HERMES_COPILOT_ACP_COMMAND / COPILOT_CLI_PATH / HERMES_COPILOT_ACP_ARGS live in its profile).
"""
HERMES_COPILOT_ACP_COMMAND / COPILOT_CLI_PATH / HERMES_COPILOT_ACP_ARGS live in its profile)."""
base_url = _provider_env_base_url(pconfig) or pconfig.inference_base_url
try:
from providers import get_provider_profile as _get_provider_profile
@@ -1923,8 +1902,7 @@ def get_external_process_provider_status(provider_id: str) -> Dict[str, Any]:
``configured``/``logged_in`` stay structural (the executable resolves or a TCP endpoint is set)
because the spawned subprocess owns its real auth. ``auth_verified``/``auth_source`` carry
positive credential evidence when Hermes can see some — absence of evidence is not absence of
auth.
"""
auth."""
pconfig = PROVIDER_REGISTRY.get(provider_id)
if not pconfig or pconfig.auth_type != "external_process":
return {"configured": False}
@@ -1981,8 +1959,7 @@ def _get_azure_foundry_auth_status() -> Dict[str, Any]:
* ``auth_mode == "entra_id"``: ``azure-identity`` importable (no token is minted here; ``hermes
doctor`` runs the live probe). Never invokes the Entra credential chain, keeping CLI startup
latency flat regardless of token-service / az login state.
* ``auth_mode == "api_key"`` (default): ``AZURE_FOUNDRY_API_KEY`` set with a usable value.
"""
* ``auth_mode == "api_key"`` (default): ``AZURE_FOUNDRY_API_KEY`` set with a usable value."""
info: Dict[str, Any] = {"provider": "azure-foundry"}
try:
from hermes_cli.config import load_config, get_env_value_prefer_dotenv
@@ -2137,8 +2114,7 @@ def _update_config_for_provider(
*default_model*, when given, is written as ``model.default`` in the same step so the gateway
(which re-reads config per message) can't pick up the new provider before model selection
finishes and send an OpenRouter-style ``vendor/model`` name to a direct API.
"""
finishes and send an OpenRouter-style ``vendor/model`` name to a direct API."""
with _auth_store_lock(): # so auto-resolution picks this provider
auth_store = _load_auth_store()
auth_store["active_provider"] = provider_id

View File

@@ -1,8 +1,7 @@
"""Shared constants, the lazy ``httpx`` proxy and :class:`AuthError` for the auth package.
Pure leaf: imports nothing from ``hermes_cli.auth`` so the per-provider modules
(``auth_nous``, ``auth_codex``, ...) can import it at module scope without cycles.
"""
(``auth_nous``, ``auth_codex``, ...) can import it at module scope without cycles."""
from __future__ import annotations