refactor(auth): relocate single-consumer helpers next to their users; _decode_jwt_claims to constants leaf

This commit is contained in:
Teknium
2026-09-02 16:09:47 -07:00
parent d32014a4a6
commit c990225fa0
6 changed files with 147 additions and 135 deletions
+14 -100
View File
@@ -14,7 +14,6 @@ import os
import shutil
import shlex
import stat
import hashlib
import threading
import time
import uuid
@@ -52,6 +51,7 @@ from hermes_cli.auth_model_picker import ( # noqa: F401 (re-exported; callers/
_save_model_choice,
)
from hermes_cli.auth_device_flow import ( # noqa: F401 (re-exported; callers/tests use hermes_cli.auth.<name>)
_CONSOLE_BROWSER_NAMES,
_can_open_graphical_browser,
_default_verify,
_is_remote_session,
@@ -92,7 +92,9 @@ from hermes_cli.auth_nous import ( # noqa: F401 (re-exported; callers/tests us
NOUS_SHARED_STORE_FILENAME,
_ALLOWED_NOUS_INFERENCE_HOSTS,
_NOUS_EFFECTIVE_STATE_IGNORED_KEYS,
_NOUS_EMPTY_AGENT_KEY_FIELDS,
_NOUS_SHARED_STATE_KEYS,
_NOUS_STALE_PORTAL_HOSTS,
_NousStatePersister,
_OAUTH_GRANT_DEAD_CODES,
_TERMINAL_REFRESH_ERROR_CODES,
@@ -101,11 +103,14 @@ from hermes_cli.auth_nous import ( # noqa: F401 (re-exported; callers/tests us
_assert_nous_inference_jwt_usable,
_clear_shared_nous_state,
_compute_nous_auth_status,
_decode_jwt_claims,
_empty_nous_auth_status,
_format_nous_entitlement_auth_error,
_healed_nous_inference_url,
_is_terminal_codex_oauth_refresh_error,
_is_terminal_nous_refresh_error,
_is_terminal_refresh_error,
_is_terminal_xai_oauth_refresh_error,
_iso_after,
_log_nous_invoke_jwt_selected,
_login_nous,
_merge_shared_nous_oauth_state,
@@ -125,6 +130,8 @@ from hermes_cli.auth_nous import ( # noqa: F401 (re-exported; callers/tests us
_nous_shared_store_lock,
_nous_shared_store_path,
_nous_status_from_state,
_oauth_trace,
_oauth_trace_enabled,
_offer_shared_nous_import,
_pick_nous_model_after_login,
_pool_first_oauth_status,
@@ -138,6 +145,7 @@ from hermes_cli.auth_nous import ( # noqa: F401 (re-exported; callers/tests us
_set_nous_agent_key_from_invoke_jwt,
_snapshot_nous_pool_status,
_sync_nous_pool_from_auth_store,
_token_fingerprint,
_try_import_shared_nous_state,
_validate_nous_inference_url_from_network,
_write_shared_nous_state,
@@ -194,6 +202,8 @@ from hermes_cli.auth_xai import ( # noqa: F401 (re-exported; callers/tests use
)
from hermes_cli.auth_codex import ( # noqa: F401 (re-exported; callers/tests use hermes_cli.auth.<name>)
CODEX_QUOTA_PROBE_MIN_INTERVAL_SECONDS,
_clear_pool_entry_status,
_codex_access_token_is_expiring,
_codex_base_url,
_codex_device_code_login,
_codex_exchange_authorization_code,
@@ -212,6 +222,7 @@ from hermes_cli.auth_codex import ( # noqa: F401 (re-exported; callers/tests u
_is_codex_rate_limit_shaped,
_load_auth_store_maybe_locked,
_login_openai_codex,
_parse_retry_after_seconds,
_pool_codex_access_token,
_pool_entries,
_probe_codex_quota_restored,
@@ -259,6 +270,7 @@ from hermes_cli.auth_qwen import ( # noqa: F401 (re-exported; callers/tests us
resolve_qwen_runtime_credentials,
)
from hermes_cli.auth_constants import ( # noqa: F401 (re-exported; callers/tests use hermes_cli.auth.<name>)
_decode_jwt_claims,
AUTH_STORE_VERSION,
AUTH_LOCK_TIMEOUT_SECONDS,
DEFAULT_NOUS_PORTAL_URL,
@@ -837,14 +849,6 @@ def is_rate_limited_auth_error(error: Exception) -> bool:
)
def _parse_retry_after_seconds(headers: Any) -> Optional[int]:
"""Best-effort parse of a ``Retry-After`` header into whole seconds."""
from agent.retry_utils import parse_retry_after_seconds
seconds = parse_retry_after_seconds(headers)
return None if seconds is None else int(seconds)
def format_auth_error(error: Exception) -> str:
"""Map auth failures to concise user-facing guidance."""
if not isinstance(error, AuthError):
@@ -886,31 +890,6 @@ def _nonempty_str(value: Any) -> bool:
return isinstance(value, str) and bool(value.strip())
def _token_fingerprint(token: Any) -> Optional[str]:
"""Return a short hash fingerprint for telemetry without leaking token bytes."""
if not isinstance(token, str):
return None
cleaned = token.strip()
if not cleaned:
return None
return hashlib.sha256(cleaned.encode("utf-8")).hexdigest()[:12]
def _oauth_trace_enabled() -> bool:
raw = os.getenv("HERMES_OAUTH_TRACE", "").strip().lower()
return raw in {"1", "true", "yes", "on"}
def _oauth_trace(event: str, *, sequence_id: Optional[str] = None, **fields: Any) -> None:
if not _oauth_trace_enabled():
return
payload: Dict[str, Any] = {"event": event}
if sequence_id:
payload["sequence_id"] = sequence_id
payload.update(fields)
logger.info("oauth_trace %s", json.dumps(payload, sort_keys=True, ensure_ascii=False))
# =============================================================================
# Auth Store — persistence layer for ~/.hermes/auth.json
# =============================================================================
@@ -1532,12 +1511,6 @@ _POOL_STATUS_FIELDS = (
)
def _clear_pool_entry_status(entry: Dict[str, Any]) -> None:
"""Reset a pool entry's cooldown / last-error metadata to healthy."""
for status_field in _POOL_STATUS_FIELDS:
entry[status_field] = None
def _merge_disk_cooldown_state(
entry: Dict[str, Any],
disk_entry: Optional[Dict[str, Any]],
@@ -2266,11 +2239,6 @@ def _is_expiring(expires_at_iso: Any, skew_seconds: int) -> bool:
return expires_epoch <= (time.time() + skew_seconds)
def _iso_after(now: datetime, ttl_seconds: int) -> str:
"""ISO timestamp *ttl_seconds* after *now* (UTC)."""
return datetime.fromtimestamp(now.timestamp() + ttl_seconds, tz=timezone.utc).isoformat()
def _tls_state_from_verify(verify: Any) -> Dict[str, Any]:
"""Persistable ``tls`` block derived from an httpx ``verify`` value."""
return {
@@ -2298,16 +2266,6 @@ def _last_auth_error_marker(
_FLAT_OAUTH_TOKEN_KEYS = ("access_token", "refresh_token", "expires_at", "expires_in", "obtained_at")
# Nous agent-key slots; a fresh login persists them as None, quarantine strips them.
_NOUS_EMPTY_AGENT_KEY_FIELDS: Dict[str, Any] = {
"agent_key": None,
"agent_key_id": None,
"agent_key_expires_at": None,
"agent_key_expires_in": None,
"agent_key_reused": None,
"agent_key_obtained_at": None,
}
def _quarantine_flat_oauth_state(state: Dict[str, Any], provider: str, exc: "AuthError") -> None:
"""Strip dead tokens from a flat OAuth state after a terminal runtime refresh failure.
@@ -2337,10 +2295,6 @@ def _optional_base_url(value: Any) -> Optional[str]:
return cleaned if cleaned else None
_NOUS_STALE_PORTAL_HOSTS: FrozenSet[str] = frozenset({
"api.nousresearch.com",
})
# Allowlist of valid Nous Portal hosts. A portal_base_url outside this
# set is treated as a misconfiguration and falls back to the default.
# "localhost" / "127.0.0.1" are valid for local development and testing.
@@ -2351,14 +2305,6 @@ _NOUS_PORTAL_ALLOWED_HOSTS: FrozenSet[str] = frozenset({
})
def _codex_access_token_is_expiring(access_token: Any, skew_seconds: int) -> bool:
claims = _decode_jwt_claims(access_token)
exp = claims.get("exp")
if not isinstance(exp, (int, float)):
return False
return float(exp) <= (time.time() + max(0, int(skew_seconds)))
# =============================================================================
# Spotify auth — PKCE tokens stored in ~/.hermes/auth.json
# =============================================================================
@@ -2369,26 +2315,6 @@ def _codex_access_token_is_expiring(access_token: Any, skew_seconds: int) -> boo
# =============================================================================
# Console/text-mode browsers that ``webbrowser`` will happily launch INSIDE
# the terminal. Opening one of these is worse than not opening anything —
# it hijacks the user's TTY with an unusable text browser (the xAI OAuth
# "Account Management" page rendered in w3m, reported May 2026) instead of
# letting them copy the URL to a real browser. When the resolved browser is
# one of these we refuse to auto-open and fall back to the print-the-URL
# path, same as a remote session.
_CONSOLE_BROWSER_NAMES: FrozenSet[str] = frozenset(
{
"w3m",
"lynx",
"links",
"links2",
"elinks",
"www-browser",
"browsh", # TUI browser — still hijacks the terminal
}
)
# =============================================================================
# OpenAI Codex auth — tokens stored in ~/.hermes/auth.json (not ~/.codex/)
#
@@ -2437,18 +2363,6 @@ _CONSOLE_BROWSER_NAMES: FrozenSet[str] = frozenset(
# -----------------------------------------------------------------------------
def _is_terminal_nous_refresh_error(exc: Exception) -> bool:
return _is_terminal_refresh_error(exc, "nous")
def _is_terminal_xai_oauth_refresh_error(exc: Exception) -> bool:
return _is_terminal_refresh_error(exc, "xai-oauth")
def _is_terminal_codex_oauth_refresh_error(exc: Exception) -> bool:
return _is_terminal_refresh_error(exc, "openai-codex")
# Per-process memo for resolve_nous_access_token. Startup runs
# check_tool_availability once per managed-tool check_fn (browser, image_gen,
# etc.), and each one independently triggers a ~15s blocking token-refresh
+27 -6
View File
@@ -18,6 +18,7 @@ from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
from hermes_cli.auth_constants import (
_decode_jwt_claims,
AUTH_LOCK_TIMEOUT_SECONDS,
AuthError,
CODEX_ACCESS_TOKEN_REFRESH_SKEW_SECONDS,
@@ -40,6 +41,29 @@ if TYPE_CHECKING: # annotation-only; the runtime import would be a cycle
logger = logging.getLogger("hermes_cli.auth")
def _parse_retry_after_seconds(headers: Any) -> Optional[int]:
"""Best-effort parse of a ``Retry-After`` header into whole seconds."""
from agent.retry_utils import parse_retry_after_seconds
seconds = parse_retry_after_seconds(headers)
return None if seconds is None else int(seconds)
def _clear_pool_entry_status(entry: Dict[str, Any]) -> None:
"""Reset a pool entry's cooldown / last-error metadata to healthy."""
from hermes_cli.auth import _POOL_STATUS_FIELDS
for status_field in _POOL_STATUS_FIELDS:
entry[status_field] = None
def _codex_access_token_is_expiring(access_token: Any, skew_seconds: int) -> bool:
claims = _decode_jwt_claims(access_token)
exp = claims.get("exp")
if not isinstance(exp, (int, float)):
return False
return float(exp) <= (time.time() + max(0, int(skew_seconds)))
def _codex_base_url() -> str:
return os.getenv("HERMES_CODEX_BASE_URL", "").strip().rstrip("/") or DEFAULT_CODEX_BASE_URL
@@ -115,7 +139,6 @@ def _sync_codex_pool_entries(
credentials (an explicit API key, a different ChatGPT account, etc.) and must not be overwritten
by a single re-auth.
"""
from hermes_cli.auth import _clear_pool_entry_status
access_token = tokens.get("access_token")
if not access_token:
return
@@ -345,7 +368,7 @@ def refresh_codex_oauth_pure(
timeout_seconds: float = 20.0,
) -> Dict[str, Any]:
"""Refresh Codex OAuth tokens without mutating Hermes auth state."""
from hermes_cli.auth import _nonempty_str, _parse_retry_after_seconds, _utc_now_z
from hermes_cli.auth import _nonempty_str, _utc_now_z
del access_token # Access token is only used by callers to decide whether to refresh.
if not _nonempty_str(refresh_token):
raise _codex_err(
@@ -634,7 +657,7 @@ def _probe_codex_quota_restored(
Probes are throttled per access token (module-local cache) so the hot selection path can fire
this freely.
"""
from hermes_cli.auth import _codex_quota_probe_cache, _decode_jwt_claims, _nonempty_str
from hermes_cli.auth import _codex_quota_probe_cache, _nonempty_str
token = str(access_token or "").strip()
if not token:
return None
@@ -704,7 +727,7 @@ def clear_codex_pool_quota_cooldowns(access_token: Optional[str] = None) -> int:
entry clears (a redeemed banked reset restores the whole account, and any entry that is
genuinely still exhausted just re-freezes with fresh metadata on its next 429).
"""
from hermes_cli.auth import _auth_store_lock, _clear_pool_entry_status, _load_auth_store, _save_auth_store
from hermes_cli.auth import _auth_store_lock, _load_auth_store, _save_auth_store
cleared = 0
try:
with _auth_store_lock():
@@ -894,7 +917,6 @@ def _login_openai_codex(
def _codex_login_rate_limited_error(response: "httpx.Response", *, during: str = "") -> AuthError:
"""AuthError for a 429 from OpenAI's device-auth endpoints (a throttle, not a credential fault)."""
from hermes_cli.auth import _parse_retry_after_seconds
retry_after = _parse_retry_after_seconds(getattr(response, "headers", None))
wait_hint = (
f" Try again in about {retry_after}s."
@@ -911,7 +933,6 @@ def _codex_login_rate_limited_error(response: "httpx.Response", *, during: str =
def _codex_request_device_code(issuer: str, client_id: str) -> Dict[str, Any]:
"""Step 1 of the Codex device flow: request a user code, retrying capped on HTTP 429."""
from hermes_cli.auth import _parse_retry_after_seconds
# OpenAI's auth endpoint rate-limits this request (HTTP 429) when login is
# attempted too often from the same IP/account — retry with capped backoff
# (honoring ``Retry-After``) before surfacing a clear, actionable message.
+16 -1
View File
@@ -6,7 +6,9 @@ Pure leaf: imports nothing from ``hermes_cli.auth`` so the per-provider modules
from __future__ import annotations
from typing import Callable, Dict, Optional
import base64
import json
from typing import Any, Callable, Dict, Optional
# httpx is imported lazily: it costs ~30ms at import time and hermes_cli.auth
# is on the interactive-CLI startup path via credential_pool → auxiliary_client
@@ -181,3 +183,16 @@ _codex_err = _provider_error_factory("openai-codex")
_spotify_err = _provider_error_factory("spotify")
_qwen_err = _provider_error_factory("qwen-oauth")
_minimax_err = _provider_error_factory("minimax-oauth")
def _decode_jwt_claims(token: Any) -> Dict[str, Any]:
if not isinstance(token, str) or token.count(".") != 2:
return {}
payload = token.split(".")[1]
payload += "=" * ((4 - len(payload) % 4) % 4)
try:
raw = base64.urlsafe_b64decode(payload.encode("utf-8"))
claims = json.loads(raw.decode("utf-8"))
except Exception:
return {}
return claims if isinstance(claims, dict) else {}
+21 -1
View File
@@ -9,6 +9,7 @@ helpers are imported lazily inside each function (no import cycle; patches on
from __future__ import annotations
import logging
from typing import FrozenSet
import os
import ssl
import sys
@@ -31,6 +32,26 @@ from utils import is_truthy_value
logger = logging.getLogger("hermes_cli.auth")
# Console/text-mode browsers that ``webbrowser`` will happily launch INSIDE
# the terminal. Opening one of these is worse than not opening anything —
# it hijacks the user's TTY with an unusable text browser (the xAI OAuth
# "Account Management" page rendered in w3m, reported May 2026) instead of
# letting them copy the URL to a real browser. When the resolved browser is
# one of these we refuse to auto-open and fall back to the print-the-URL
# path, same as a remote session.
_CONSOLE_BROWSER_NAMES: FrozenSet[str] = frozenset(
{
"w3m",
"lynx",
"links",
"links2",
"elinks",
"www-browser",
"browsh", # TUI browser — still hijacks the terminal
}
)
def _is_remote_session() -> bool:
"""Detect environments where loopback OAuth can't reach the local browser.
@@ -66,7 +87,6 @@ def _can_open_graphical_browser() -> bool:
require a display server (``$DISPLAY`` / ``$WAYLAND_DISPLAY``) unless ``$BROWSER`` points at
something graphical; no display server almost always means no GUI browser.
"""
from hermes_cli.auth import _CONSOLE_BROWSER_NAMES
import webbrowser as _webbrowser
def _names_console_browser(value: str) -> bool:
+68 -26
View File
@@ -9,7 +9,7 @@ helpers are imported lazily inside each function (no import cycle; patches on
from __future__ import annotations
import logging
import base64
import hashlib
import json
import os
import threading
@@ -22,6 +22,7 @@ from typing import Any, Callable, Dict, FrozenSet, List, Optional
from urllib.parse import urlparse
from hermes_cli.auth_codex import _pool_entries
from hermes_cli.auth_constants import (
_decode_jwt_claims,
AUTH_LOCK_TIMEOUT_SECONDS,
AuthError,
DEFAULT_NOUS_CLIENT_ID,
@@ -46,6 +47,64 @@ if TYPE_CHECKING: # annotation-only; the runtime import would be a cycle
logger = logging.getLogger("hermes_cli.auth")
def _token_fingerprint(token: Any) -> Optional[str]:
"""Return a short hash fingerprint for telemetry without leaking token bytes."""
if not isinstance(token, str):
return None
cleaned = token.strip()
if not cleaned:
return None
return hashlib.sha256(cleaned.encode("utf-8")).hexdigest()[:12]
def _oauth_trace_enabled() -> bool:
raw = os.getenv("HERMES_OAUTH_TRACE", "").strip().lower()
return raw in {"1", "true", "yes", "on"}
def _oauth_trace(event: str, *, sequence_id: Optional[str] = None, **fields: Any) -> None:
if not _oauth_trace_enabled():
return
payload: Dict[str, Any] = {"event": event}
if sequence_id:
payload["sequence_id"] = sequence_id
payload.update(fields)
logger.info("oauth_trace %s", json.dumps(payload, sort_keys=True, ensure_ascii=False))
def _iso_after(now: datetime, ttl_seconds: int) -> str:
"""ISO timestamp *ttl_seconds* after *now* (UTC)."""
return datetime.fromtimestamp(now.timestamp() + ttl_seconds, tz=timezone.utc).isoformat()
# Nous agent-key slots; a fresh login persists them as None, quarantine strips them.
_NOUS_EMPTY_AGENT_KEY_FIELDS: Dict[str, Any] = {
"agent_key": None,
"agent_key_id": None,
"agent_key_expires_at": None,
"agent_key_expires_in": None,
"agent_key_reused": None,
"agent_key_obtained_at": None,
}
_NOUS_STALE_PORTAL_HOSTS: FrozenSet[str] = frozenset({
"api.nousresearch.com",
})
def _is_terminal_nous_refresh_error(exc: Exception) -> bool:
return _is_terminal_refresh_error(exc, "nous")
def _is_terminal_xai_oauth_refresh_error(exc: Exception) -> bool:
return _is_terminal_refresh_error(exc, "xai-oauth")
def _is_terminal_codex_oauth_refresh_error(exc: Exception) -> bool:
return _is_terminal_refresh_error(exc, "openai-codex")
def _format_nous_entitlement_auth_error(error: AuthError) -> str:
try:
from hermes_cli.nous_account import (
@@ -66,7 +125,6 @@ def _format_nous_entitlement_auth_error(error: AuthError) -> str:
def _migrate_stale_nous_portal_url(providers: Dict[str, Any]) -> None:
from hermes_cli.auth import _NOUS_STALE_PORTAL_HOSTS
nous = providers.get("nous")
if not isinstance(nous, dict):
return
@@ -151,19 +209,6 @@ def _nous_portal_env_override() -> Optional[str]:
)
def _decode_jwt_claims(token: Any) -> Dict[str, Any]:
if not isinstance(token, str) or token.count(".") != 2:
return {}
payload = token.split(".")[1]
payload += "=" * ((4 - len(payload) % 4) % 4)
try:
raw = base64.urlsafe_b64decode(payload.encode("utf-8"))
claims = json.loads(raw.decode("utf-8"))
except Exception:
return {}
return claims if isinstance(claims, dict) else {}
def _scope_values(raw_scope: Any) -> set[str]:
# OAuth token responses normally return a space-separated string. Keep
# collection support for JWT ``scp`` claims and older stored test fixtures.
@@ -255,7 +300,6 @@ def _log_nous_invoke_jwt_selected(
access_token: Any,
sequence_id: Optional[str] = None,
) -> None:
from hermes_cli.auth import _oauth_trace, _token_fingerprint
logger.debug("Nous inference auth: using NAS invoke JWT")
_oauth_trace(
"nous_invoke_jwt_selected",
@@ -477,7 +521,7 @@ def _write_shared_nous_state(state: Dict[str, Any]) -> None:
Best-effort: any failure is swallowed after logging. The shared store is a convenience layer;
the per-profile auth.json remains the source of truth.
"""
from hermes_cli.auth import _nonempty_str, _oauth_trace, _token_fingerprint, _write_private_file_atomic
from hermes_cli.auth import _nonempty_str, _write_private_file_atomic
refresh_token = state.get("refresh_token")
access_token = state.get("access_token")
# No refresh_token = nothing worth sharing across profiles
@@ -532,7 +576,6 @@ def _read_shared_nous_state() -> Optional[Dict[str, Any]]:
def _clear_shared_nous_state(reason: str) -> None:
"""Remove the shared Nous OAuth store after a terminal token failure."""
from hermes_cli.auth import _oauth_trace
try:
with _nous_shared_store_lock():
path = _nous_shared_store_path()
@@ -577,7 +620,7 @@ def _quarantine_nous_oauth_state(
reason: str,
) -> None:
"""Keep routing metadata but remove dead OAuth material so it is not replayed."""
from hermes_cli.auth import _FLAT_OAUTH_TOKEN_KEYS, _NOUS_EMPTY_AGENT_KEY_FIELDS, _auth_file_path, _last_auth_error_marker, _token_fingerprint, invalidate_nous_auth_status_cache
from hermes_cli.auth import _FLAT_OAUTH_TOKEN_KEYS, _auth_file_path, _last_auth_error_marker, invalidate_nous_auth_status_cache
# Forensic logging BEFORE we clear the token material. A hosted agent
# can take a terminal invalid_grant and get quarantined here silently: the
# only downstream signal is a "No access token found" WARNING once the pool
@@ -644,7 +687,6 @@ def _quarantine_nous_pool_entries(
reason: str,
) -> bool:
"""Remove singleton-seeded Nous pool entries that contain dead OAuth state."""
from hermes_cli.auth import _oauth_trace
entries = _pool_entries(auth_store, "nous")
if entries is None:
return False
@@ -680,7 +722,7 @@ def _try_import_shared_nous_state(
Returns ``None`` on any failure (expired token, portal unreachable) so the caller falls
through to the normal device-code flow.
"""
from hermes_cli.auth import _is_terminal_nous_refresh_error, _oauth_trace, _read_shared_nous_state, _write_shared_nous_state, refresh_nous_oauth_from_state
from hermes_cli.auth import _read_shared_nous_state, _write_shared_nous_state, refresh_nous_oauth_from_state
try:
with _nous_shared_store_lock(timeout_seconds=max(timeout_seconds + 5.0, AUTH_LOCK_TIMEOUT_SECONDS)):
shared = _read_shared_nous_state()
@@ -796,7 +838,7 @@ def _refresh_nous_or_quarantine(
persist: Callable[[], None],
) -> Dict[str, Any]:
"""Redeem the Nous refresh token; on a terminal failure quarantine state + pool, persist, re-raise."""
from hermes_cli.auth import _is_terminal_nous_refresh_error, _refresh_access_token
from hermes_cli.auth import _refresh_access_token
try:
return _refresh_access_token(
client=client,
@@ -824,7 +866,7 @@ def _apply_nous_refreshed_tokens(
*inference_base_url*, when given, is the healed network-provenance URL to persist alongside
the rotated tokens (key order in auth.json is preserved from the original login shape).
"""
from hermes_cli.auth import _coerce_ttl_seconds, _iso_after
from hermes_cli.auth import _coerce_ttl_seconds
now = datetime.now(timezone.utc)
access_ttl = _coerce_ttl_seconds(refreshed.get("expires_in"))
state["access_token"] = refreshed["access_token"]
@@ -1106,7 +1148,7 @@ class _NousStatePersister:
self.persisted_any = False
def persist(self, reason: str) -> None:
from hermes_cli.auth import _oauth_trace, _save_provider_state_to_source, _token_fingerprint, _write_shared_nous_state
from hermes_cli.auth import _save_provider_state_to_source, _write_shared_nous_state
state = self._state
if (
_nous_effective_provider_state(state)
@@ -1217,7 +1259,7 @@ def resolve_nous_runtime_credentials(
of rotating the shared grant again (otherwise N concurrent processes at the
same expiry issue N refreshes, each invalidating a sibling's fresh token).
"""
from hermes_cli.auth import _assert_nous_inference_jwt_usable, _auth_file_path, _coerce_ttl_seconds, _nous_invoke_jwt_status, _oauth_trace, _parse_iso_timestamp, _provider_state_transaction, _resolve_verify, _select_nous_invoke_jwt, _sync_nous_pool_from_auth_store, _tls_state_from_verify, _token_fingerprint
from hermes_cli.auth import _assert_nous_inference_jwt_usable, _auth_file_path, _coerce_ttl_seconds, _nous_invoke_jwt_status, _parse_iso_timestamp, _provider_state_transaction, _resolve_verify, _select_nous_invoke_jwt, _sync_nous_pool_from_auth_store, _tls_state_from_verify
sequence_id = uuid.uuid4().hex[:12]
with _provider_state_transaction("nous") as (
@@ -1744,7 +1786,7 @@ def _nous_device_code_login(
on_verification: Optional[Callable[[str, str], None]] = None,
) -> Dict[str, Any]:
"""Run the Nous device-code flow and return full OAuth state without persisting."""
from hermes_cli.auth import PROVIDER_REGISTRY, _NOUS_EMPTY_AGENT_KEY_FIELDS, _coerce_ttl_seconds, _is_remote_session, _optional_base_url, _poll_for_token, _print_device_code_instructions, _request_device_code, _tls_state_from_verify, format_auth_error, refresh_nous_oauth_from_state
from hermes_cli.auth import PROVIDER_REGISTRY, _coerce_ttl_seconds, _is_remote_session, _optional_base_url, _poll_for_token, _print_device_code_instructions, _request_device_code, _tls_state_from_verify, format_auth_error, refresh_nous_oauth_from_state
pconfig = PROVIDER_REGISTRY["nous"]
portal_base_url = (
portal_base_url
+1 -1
View File
@@ -13,7 +13,7 @@ import json
import os
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
from hermes_cli.auth_nous import _decode_jwt_claims
from hermes_cli.auth_constants import _decode_jwt_claims
# Log-record parity with the origin module (caplog tests pin "hermes_cli.auth").
logger = logging.getLogger("hermes_cli.auth")