From 093162b063e46434393dd9d9610c52719bb627ef Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Wed, 2 Sep 2026 20:42:43 -0700 Subject: [PATCH] =?UTF-8?q?refactor(hermes=5Fcli):=20nous=5Fbilling/proces?= =?UTF-8?q?s=5Fidentity=20=E2=80=94=20compact=20signatures=20and=20literal?= =?UTF-8?q?=20tables=20(941->870=20LOC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hermes_cli/nous_billing.py | 78 ++++++++-------------------------- hermes_cli/process_identity.py | 53 ++++++----------------- 2 files changed, 30 insertions(+), 101 deletions(-) diff --git a/hermes_cli/nous_billing.py b/hermes_cli/nous_billing.py index 1150fd530e..f8d1d83b33 100644 --- a/hermes_cli/nous_billing.py +++ b/hermes_cli/nous_billing.py @@ -36,17 +36,10 @@ class BillingError(Exception): """ def __init__( - self, - message: str, - *, - status: Optional[int] = None, - error: Optional[str] = None, - portal_url: Optional[str] = None, - retry_after: Optional[int] = None, - payload: Optional[dict[str, Any]] = None, - actor: Optional[str] = None, - code: Optional[str] = None, - recovery: Optional[str] = None, + self, message: str, *, status: Optional[int] = None, error: Optional[str] = None, + portal_url: Optional[str] = None, retry_after: Optional[int] = None, + payload: Optional[dict[str, Any]] = None, actor: Optional[str] = None, + code: Optional[str] = None, recovery: Optional[str] = None, ) -> None: super().__init__(message) self.status = status @@ -170,11 +163,7 @@ def invalidate_cached_token() -> None: def _billing_not_logged_in(exc: Optional[BaseException] = None) -> "BillingAuthError": """Build the canonical 'not logged in' BillingAuthError (single source).""" - err = BillingAuthError( - "Not logged into Nous Portal — run `hermes portal` to log in.", - status=401, - error="invalid_token", - ) + err = BillingAuthError("Not logged into Nous Portal — run `hermes portal` to log in.", status=401, error="invalid_token") if exc is not None: err.__cause__ = exc return err @@ -191,16 +180,13 @@ def _resolve_token_and_base(*, use_cache: bool = True) -> tuple[str, str]: cached_at, token, base = _token_cache if (time.time() - cached_at) < _TOKEN_CACHE_TTL_SECONDS: return token, base - try: from hermes_cli.auth import get_provider_auth_state state = get_provider_auth_state("nous") or {} except Exception: state = {} - base = resolve_portal_base_url(state) - try: from hermes_cli.auth import AuthError, resolve_nous_access_token except ImportError: @@ -259,14 +245,9 @@ def _raise_for_error(status: int, payload: dict[str, Any], headers: Any = None) error = p.get("error") message = p.get("message") common = { - "status": status, - "error": error, - "portal_url": _absolutize_portal_url(p.get("portalUrl")), - "retry_after": _retry_after_seconds(headers), - "payload": p, - "actor": p.get("actor"), - "code": p.get("code"), - "recovery": p.get("recovery"), + "status": status, "error": error, "portal_url": _absolutize_portal_url(p.get("portalUrl")), + "retry_after": _retry_after_seconds(headers), "payload": p, + "actor": p.get("actor"), "code": p.get("code"), "recovery": p.get("recovery"), } key = error if isinstance(error, str) else None cls, fallback = ( @@ -279,13 +260,8 @@ def _raise_for_error(status: int, payload: dict[str, Any], headers: Any = None) def _request( - method: str, - path: str, - *, - body: Optional[dict[str, Any]] = None, - extra_headers: Optional[dict[str, str]] = None, - timeout: float = DEFAULT_TIMEOUT, - _retried_auth: bool = False, + method: str, path: str, *, body: Optional[dict[str, Any]] = None, + extra_headers: Optional[dict[str, str]] = None, timeout: float = DEFAULT_TIMEOUT, _retried_auth: bool = False, ) -> dict[str, Any]: """Authenticated billing request -> parsed JSON dict (``{}`` for an empty 2xx body). @@ -297,12 +273,9 @@ def _request( headers = {"Authorization": f"Bearer {token}", "Accept": "application/json"} if body is not None: headers["Content-Type"] = "application/json" - if extra_headers: - headers.update(extra_headers) - + headers.update(extra_headers or {}) data = json.dumps(body).encode("utf-8") if body is not None else None req = urllib.request.Request(url, data=data, headers=headers, method=method) - try: with urllib.request.urlopen(req, timeout=timeout) as resp: raw = resp.read().decode("utf-8") @@ -314,10 +287,8 @@ def _request( # A 2xx non-JSON body (SPA/reverse-proxy fallback HTML when the route isn't # deployed) is a typed non-auth error so callers degrade to "unavailable". raise BillingError( - "Billing endpoint returned a non-JSON response " - "(it may not be available on this deployment).", - error="endpoint_unavailable", - status=getattr(resp, "status", None), + "Billing endpoint returned a non-JSON response (it may not be available on this deployment).", + error="endpoint_unavailable", status=getattr(resp, "status", None), ) from exc except urllib.error.HTTPError as exc: # 401 on a cached token → drop the cache and retry once with a fresh (refresh-aware) resolve. @@ -366,11 +337,7 @@ def get_billing_state(*, timeout: float = DEFAULT_TIMEOUT) -> dict[str, Any]: def patch_auto_top_up( - *, - enabled: bool, - threshold: float | str, - top_up_amount: float | str, - timeout: float = DEFAULT_TIMEOUT, + *, enabled: bool, threshold: float | str, top_up_amount: float | str, timeout: float = DEFAULT_TIMEOUT ) -> dict[str, Any]: """``PATCH /api/billing/auto-top-up`` — configure auto-reload (scope required). @@ -380,12 +347,7 @@ def patch_auto_top_up( return _request("PATCH", "/api/billing/auto-top-up", body=body, timeout=timeout) -def post_charge( - *, - amount_usd: float | str, - idempotency_key: str, - timeout: float = DEFAULT_TIMEOUT, -) -> dict[str, Any]: +def post_charge(*, amount_usd: float | str, idempotency_key: str, timeout: float = DEFAULT_TIMEOUT) -> dict[str, Any]: """``POST /api/billing/charge`` — buy credits (scope required). Generate a UUID ``idempotency_key`` per user-confirmed purchase and reuse it on retry. Returns @@ -430,10 +392,7 @@ def post_subscription_preview(*, subscription_type_id: str, timeout: float = DEF def put_subscription_pending_change( - *, - subscription_type_id: str | None = None, - cancel: bool = False, - timeout: float = DEFAULT_TIMEOUT, + *, subscription_type_id: str | None = None, cancel: bool = False, timeout: float = DEFAULT_TIMEOUT ) -> dict[str, Any]: """``PUT /api/billing/subscription/pending-change`` — set the single end-of-period intent. @@ -460,10 +419,7 @@ def delete_subscription_pending_change(*, timeout: float = DEFAULT_TIMEOUT) -> d def post_subscription_upgrade( - *, - subscription_type_id: str, - idempotency_key: str, - timeout: float = DEFAULT_TIMEOUT, + *, subscription_type_id: str, idempotency_key: str, timeout: float = DEFAULT_TIMEOUT ) -> dict[str, Any]: """``POST /api/billing/subscription/upgrade`` — immediate paid upgrade, the SINGLE money route. diff --git a/hermes_cli/process_identity.py b/hermes_cli/process_identity.py index d75a8d6a2d..95b975e918 100644 --- a/hermes_cli/process_identity.py +++ b/hermes_cli/process_identity.py @@ -196,12 +196,7 @@ def _pid_alive_matches(pid: int, create_time: Optional[float]) -> Optional[bool] return None -def register_self( - purpose: str, - *, - project_root: Optional[Path] = None, - detail: Optional[dict] = None, -) -> bool: +def register_self(purpose: str, *, project_root: Optional[Path] = None, detail: Optional[dict] = None) -> bool: """Record this process in the machine spawn ledger. Best-effort. Called at the top of every long-lived entry point; dead ``(pid, create_time)`` entries are @@ -230,8 +225,7 @@ def register_self( if detail: try: entry.host = str(detail.get("host") or "") - port = detail.get("port") - entry.port = int(port) if port is not None else None + entry.port = int(detail["port"]) if detail.get("port") is not None else None entry.profile = str(detail.get("profile") or "") except (TypeError, ValueError): pass @@ -248,12 +242,8 @@ def register_self( def _new_entry( - pid: int, - create_time: Optional[float], - purpose: str, - project_root: Optional[Path], - spawner_pid: Optional[int], - spawner_create: Optional[float], + pid: int, create_time: Optional[float], purpose: str, project_root: Optional[Path], + spawner_pid: Optional[int], spawner_create: Optional[float], ) -> LedgerEntry: return LedgerEntry( pid, create_time, purpose, install_id(project_root), spawner_pid, spawner_create, time.time(), argv="" @@ -288,12 +278,7 @@ def _append_entry(entry: LedgerEntry) -> bool: return False -def register_child( - pid: int, - purpose: str, - *, - project_root: Optional[Path] = None, -) -> bool: +def register_child(pid: int, purpose: str, *, project_root: Optional[Path] = None) -> bool: """Record a CHILD process this process just spawned. Best-effort. Mirror of :func:`register_self` for children that cannot register themselves (stdio MCP @@ -328,8 +313,7 @@ def ledger_entries(*, project_root: Optional[Path] = None) -> list[dict]: if entries is None: return [] return [ - e - for e in entries + e for e in entries if e.get("install") == want_install and isinstance(e.get("pid"), int) and _pid_alive_matches(e["pid"], e.get("create_time")) is not False @@ -345,11 +329,7 @@ def spawner_is_dead(entry: dict) -> Optional[bool]: return None if alive is None else not alive -def reap_orphaned_mcp_helpers( - *, - project_root: Optional[Path] = None, - kill_fn=None, -) -> list[int]: +def reap_orphaned_mcp_helpers(*, project_root: Optional[Path] = None, kill_fn=None) -> list[int]: """Kill ledger-registered stdio MCP helpers whose spawner is provably dead. Ledger-driven startup-sweep rung (not cmdline-heuristic): a helper is reaped ONLY when it has a @@ -377,9 +357,8 @@ def reap_orphaned_mcp_helpers( import psutil proc = psutil.Process(pid) - # Re-verify identity at the moment of kill (PID-reuse guard). if not _same_incarnation(proc, entry.get("create_time")): - continue + continue # PID reused since registration proc.terminate() try: proc.wait(timeout=2.0) @@ -424,14 +403,10 @@ def attach_self_to_kill_on_close_job() -> bool: class JOBOBJECT_BASIC_LIMIT_INFORMATION(ctypes.Structure): _fields_ = [ - ("PerProcessUserTimeLimit", wintypes.LARGE_INTEGER), - ("PerJobUserTimeLimit", wintypes.LARGE_INTEGER), - ("LimitFlags", wintypes.DWORD), - ("MinimumWorkingSetSize", ctypes.c_size_t), - ("MaximumWorkingSetSize", ctypes.c_size_t), - ("ActiveProcessLimit", wintypes.DWORD), - ("Affinity", ctypes.POINTER(wintypes.ULONG)), - ("PriorityClass", wintypes.DWORD), + ("PerProcessUserTimeLimit", wintypes.LARGE_INTEGER), ("PerJobUserTimeLimit", wintypes.LARGE_INTEGER), + ("LimitFlags", wintypes.DWORD), ("MinimumWorkingSetSize", ctypes.c_size_t), + ("MaximumWorkingSetSize", ctypes.c_size_t), ("ActiveProcessLimit", wintypes.DWORD), + ("Affinity", ctypes.POINTER(wintypes.ULONG)), ("PriorityClass", wintypes.DWORD), ("SchedulingClass", wintypes.DWORD), ] @@ -448,9 +423,7 @@ def attach_self_to_kill_on_close_job() -> bool: return False info = JOBOBJECT_EXTENDED_LIMIT_INFORMATION() info.BasicLimitInformation.LimitFlags = ( - JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE - | JOB_OBJECT_LIMIT_BREAKAWAY_OK - | JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_BREAKAWAY_OK | JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK ) ok = kernel32.SetInformationJobObject( job, JobObjectExtendedLimitInformation, ctypes.byref(info), ctypes.sizeof(info)