"""Read-only provider-pool status derived from Switchyard and local auth metadata.""" from __future__ import annotations import base64 import json import os import time from datetime import datetime, timezone from pathlib import Path from typing import Any from urllib.error import HTTPError, URLError from urllib.request import Request, urlopen SWITCHYARD_ROOT = os.environ.get( "HERMES_SWITCHYARD_STATUS_URL", "http://hermes-switchyard.hermes.svc.cluster.local:9005", ).rstrip("/") CODEX_USAGE_URL = "https://chatgpt.com/codex/settings/usage" CLAUDE_USAGE_URL = "https://claude.ai/settings/usage" CODEX_AUTH_PATH = Path( os.environ.get("CODEX_HOME", "/opt/data/home/.codex") ) / "auth.json" CLAUDE_AUTH_PATH = Path( os.environ.get("CLAUDE_CONFIG_DIR", "/opt/data/home/.claude") ) / ".credentials.json" ROUTING_CATALOG_PATH = Path( os.environ.get("HERMES_ROUTING_CATALOG_PATH", "/routing-catalog/catalog.json") ) CLAUDE_HEALTH_PATH = Path( os.environ.get( "HERMES_CLAUDE_HEALTH_PATH", "/opt/data/provider-health/claude.json" ) ) CODEX_HEALTH_PATH = Path( os.environ.get("HERMES_CODEX_HEALTH_PATH", "/opt/data/provider-health/codex.json") ) def _read_json(path: Path) -> dict[str, Any]: """Return one local JSON object without leaking parsing details.""" try: value = json.loads(path.read_text(encoding="utf-8")) except (OSError, TypeError, ValueError, json.JSONDecodeError): return {} return value if isinstance(value, dict) else {} def _get_json(url: str, timeout: float = 3.0) -> dict[str, Any]: """Fetch a bounded JSON status response from the in-cluster router.""" request = Request(url, headers={"Accept": "application/json"}) try: with urlopen(request, timeout=timeout) as response: value = json.load(response) except (HTTPError, URLError, OSError, TimeoutError, ValueError): return {} return value if isinstance(value, dict) else {} def _jwt_claims(token: Any) -> dict[str, Any]: """Decode trusted local token metadata for display; do not verify or return it.""" if not isinstance(token, str): return {} try: encoded = token.split(".")[1] encoded += "=" * (-len(encoded) % 4) value = json.loads(base64.urlsafe_b64decode(encoded)) except ( IndexError, TypeError, ValueError, json.JSONDecodeError, base64.binascii.Error, ): return {} return value if isinstance(value, dict) else {} def _timestamp(value: Any) -> tuple[str | None, bool | None]: """Normalize an epoch or ISO-8601 timestamp and report whether it is live.""" if isinstance(value, str): rendered_value = value.strip() if rendered_value and not rendered_value.replace(".", "", 1).isdigit(): try: parsed = datetime.fromisoformat(rendered_value.replace("Z", "+00:00")) if parsed.tzinfo is None: parsed = parsed.replace(tzinfo=timezone.utc) except ValueError: return None, None rendered = parsed.astimezone(timezone.utc).isoformat() return rendered, parsed.timestamp() > time.time() try: raw = float(value) except (TypeError, ValueError): return None, None if raw > 100_000_000_000: raw /= 1000 try: rendered = datetime.fromtimestamp(raw, tz=timezone.utc).isoformat() except (OverflowError, OSError, ValueError): return None, None return rendered, raw > time.time() def _codex_account() -> dict[str, Any]: """Return non-secret Codex plan and credential health metadata.""" auth = _read_json(CODEX_AUTH_PATH) tokens = auth.get("tokens") if isinstance(auth.get("tokens"), dict) else {} access = _jwt_claims(tokens.get("access_token")) identity = _jwt_claims(tokens.get("id_token")) claims = identity.get("https://api.openai.com/auth") claims = claims if isinstance(claims, dict) else {} expires_at, token_live = _timestamp(access.get("exp")) subscription_until, subscription_live = _timestamp( claims.get("chatgpt_subscription_active_until") ) refreshable = bool(tokens.get("refresh_token")) authenticated = ( bool(tokens.get("access_token")) and token_live is not False ) or refreshable return { "authenticated": authenticated, "access_token_live": token_live, "refreshable": refreshable, "auth_mode": auth.get("auth_mode") or "unknown", "plan": claims.get("chatgpt_plan_type") or "unknown", "token_expires_at": expires_at, "subscription_active": subscription_live, "subscription_until": subscription_until, "last_refresh": auth.get("last_refresh"), "quota_reported": False, "usage_url": CODEX_USAGE_URL, } def _claude_account() -> dict[str, Any]: """Return non-secret Claude plan and credential health metadata.""" auth = _read_json(CLAUDE_AUTH_PATH) oauth = auth.get("claudeAiOauth") oauth = oauth if isinstance(oauth, dict) else {} expires_at, token_live = _timestamp(oauth.get("expiresAt")) refresh_expires_at, refresh_live = _timestamp(oauth.get("refreshTokenExpiresAt")) refreshable = bool(oauth.get("refreshToken")) and refresh_live is not False authenticated = ( bool(oauth.get("accessToken")) and token_live is not False ) or refreshable return { "authenticated": authenticated, "access_token_live": token_live, "refreshable": refreshable, "plan": oauth.get("subscriptionType") or "unknown", "rate_limit_tier": oauth.get("rateLimitTier") or "unknown", "token_expires_at": expires_at, "refresh_expires_at": refresh_expires_at, "refresh_live": refresh_live, "quota_reported": False, "usage_url": CLAUDE_USAGE_URL, } def _provider_for_model(model_id: str) -> str: """Map Switchyard target identifiers to their operator-facing provider.""" value = model_id.lower() if "/codex/" in value or value.startswith("openai"): return "codex" if "/claude/" in value or value.startswith("anthropic"): return "claude" if "/local/" in value or "qwen" in value or "ollama" in value: return "local" return "other" def _number(value: Any) -> int: """Coerce non-negative counters from Switchyard's JSON safely.""" try: return max(0, int(value or 0)) except (TypeError, ValueError): return 0 def _provider_summary(name: str, models: dict[str, Any]) -> dict[str, Any]: """Aggregate successful calls, errors, tokens, and latency for one lane.""" selected: list[dict[str, Any]] = [] totals = { "calls": 0, "errors": 0, "prompt_tokens": 0, "completion_tokens": 0, "reasoning_tokens": 0, "total_tokens": 0, } latency_weight = 0.0 latency_calls = 0 for model_id, raw in sorted(models.items()): if _provider_for_model(str(model_id)) != name or not isinstance(raw, dict): continue item = {"id": str(model_id)} for key in totals: item[key] = _number(raw.get(key)) totals[key] += item[key] try: item["avg_latency_ms"] = round(float(raw.get("avg_latency_ms") or 0), 1) except (TypeError, ValueError): item["avg_latency_ms"] = 0.0 latency_weight += item["avg_latency_ms"] * item["calls"] latency_calls += item["calls"] selected.append(item) calls = totals["calls"] errors = totals["errors"] # Switchyard counters span the router process lifetime. One old transient # failure must not leave an otherwise healthy provider permanently yellow. total_boundaries = calls + errors error_ratio = errors / total_boundaries if total_boundaries else 0.0 if calls and error_ratio >= 0.05: state = "degraded" elif calls: state = "available" elif errors: state = "unavailable" else: state = "unobserved" return { "state": state, **totals, "avg_latency_ms": round(latency_weight / latency_calls, 1) if latency_calls else 0.0, "models": selected, } def _configured_models(provider: str) -> list[str]: """Return the stewarded model catalog independently of observed traffic.""" catalog = _read_json(ROUTING_CATALOG_PATH) providers = catalog.get("providers") providers = providers if isinstance(providers, dict) else {} record = providers.get(provider) record = record if isinstance(record, dict) else {} models = record.get("models") if not isinstance(models, list): return [] return sorted({str(model) for model in models if isinstance(model, str)}) def _fresh_health(path: Path, maximum_age: float = 86400.0) -> dict[str, Any]: """Read recent broker health without treating stale state as authoritative.""" value = _read_json(path) try: age = time.time() - path.stat().st_mtime except OSError: return {} return value if age <= maximum_age else {} def _apply_native_health(provider: dict[str, Any], health: dict[str, Any]) -> None: """Prefer current native transport health over lifetime router counters.""" provider["native_health"] = health native_state = health.get("state") if native_state == "available": provider["state"] = "available" elif native_state in {"capacity-limited", "degraded"}: provider["state"] = "degraded" elif native_state == "unavailable": provider["state"] = "unavailable" def provider_status_payload() -> dict[str, Any]: """Build the owner-safe status document shared by dashboard and TUI.""" health = _get_json(f"{SWITCHYARD_ROOT}/health") stats = _get_json(f"{SWITCHYARD_ROOT}/v1/stats") router_ok = health.get("status") == "ok" and bool(stats) models = stats.get("models") if isinstance(stats.get("models"), dict) else {} providers = { name: _provider_summary(name, models) for name in ("codex", "claude", "local") } for name, item in providers.items(): item["configured_models"] = _configured_models(name) item["supported_efforts"] = ( ["low", "medium", "high", "xhigh"] if name in {"codex", "claude"} else ["medium"] ) providers["codex"]["account"] = _codex_account() providers["claude"]["account"] = _claude_account() _apply_native_health(providers["codex"], _fresh_health(CODEX_HEALTH_PATH)) _apply_native_health(providers["claude"], _fresh_health(CLAUDE_HEALTH_PATH)) classifier = stats.get("classifier") classifier = classifier if isinstance(classifier, dict) else {} fallbacks = stats.get("routing_fallbacks") fallbacks = fallbacks if isinstance(fallbacks, dict) else {} return { "generated_at": datetime.now(timezone.utc).isoformat(), "window": "Since the last Switchyard restart", "quota_note": ( "Codex uses the owner's ChatGPT Codex OAuth and Claude uses the " "owner's native first-party Claude Code subscription; the Claude " "lane does not use the metered Anthropic API key. Open each official " "usage page for authoritative remaining capacity. The counters here " "show actual work observed by Switchyard." ), "router": { "state": "available" if router_ok else "unavailable", "total_requests": _number(stats.get("total_requests")), "total_errors": _number(stats.get("total_errors")), "total_tokens": _number( (stats.get("total_tokens") or {}).get("total") if isinstance(stats.get("total_tokens"), dict) else 0 ), "fallbacks": {str(key): _number(value) for key, value in fallbacks.items()}, "classifier_calls": _number(classifier.get("total_requests")), "classifier_errors": _number(classifier.get("total_errors")), }, "providers": providers, } def provider_status_text() -> str: """Render the same status as a compact native Hermes command response.""" payload = provider_status_payload() router = payload["router"] labels = {"codex": "Codex", "claude": "Claude", "local": "Local"} lines = [ f"Provider pool: Switchyard {router['state']}", str(payload["window"]), ] for key in ("codex", "claude", "local"): item = payload["providers"][key] account = item.get("account") or {} plan = f" · plan {account.get('plan', 'unknown')}" if account else "" auth = "" if account: if account.get("access_token_live") is False and account.get("refreshable"): auth = " · auth refreshable" else: auth = ( " · auth ready" if account.get("authenticated") else " · auth unavailable" ) lines.append( f"{labels[key]}: {item['state']} · {item['calls']} completed · " f"{item['errors']} errors · {item['total_tokens']} tokens{plan}{auth}" ) lines.extend( [ f"Fallbacks: {sum(router['fallbacks'].values())} · " f"classifier: {router['classifier_calls']} calls / " f"{router['classifier_errors']} errors", str(payload["quota_note"]), f"Codex usage: {CODEX_USAGE_URL}", f"Claude usage: {CLAUDE_USAGE_URL}", "Dashboard: Provider Status", ] ) return "\n".join(lines)