"""Claude Code and opencode usage, relayed from llm-cost-dashboard's API. Everything here is read-only. The dashboard stays the owner of the data and the pricing; deck-it only renders it. """ import json import urllib.request from ..module import Module ENDPOINTS = ("/api/claude_limits", "/api/claude_session", "/api/summary", "/api/last_session") def _fetch_for(base): def fetch(endpoint): with urllib.request.urlopen(base + endpoint, timeout=2) as r: return json.loads(r.read()) return fetch def _opencode(rows) -> dict: """Aggregate /api/summary's per-model rows into opencode's cost and tokens.""" picked = [r for r in rows if isinstance(r, dict) and r.get("program") == "opencode"] models = [ {"provider": r.get("provider") or "", "model": r.get("model") or "", "cost": r.get("total_cost_usd") or 0, "tokens": r.get("tokens") or 0, "calls": r.get("calls") or 0} for r in picked ] models.sort(key=lambda r: r["cost"], reverse=True) return { "cost": round(sum(r["cost"] for r in models), 4), "tokens": sum(r["tokens"] for r in models), "calls": sum(r["calls"] for r in models), "models": models, } def _claude_model(session) -> str | None: models = (session or {}).get("models") or [] return models[0].get("model") if models else None def _opencode_session(sessions) -> dict: """The latest opencode session's dominant model from /api/last_session.""" for block in sessions or []: if not isinstance(block, dict) or block.get("program") != "opencode": continue rows = block.get("rows") or [] top = max(rows, key=lambda r: r.get("calls") or 0) if rows else {} return { "model": top.get("model"), "provider": top.get("provider"), "session_cost": round(sum(r.get("cost_usd") or 0 for r in rows), 4), "session_tokens": sum(r.get("tokens") or 0 for r in rows), "session_calls": sum(r.get("calls") or 0 for r in rows), } return {"model": None, "provider": None, "session_cost": None, "session_tokens": 0, "session_calls": 0} def make(cfg: dict) -> Module: base = cfg.get("llm", {}).get("url", "http://127.0.0.1:8420").rstrip("/") fetch = cfg.get("fetch") or _fetch_for(base) def state(): try: data = {ep: fetch(ep) for ep in ENDPOINTS} except (OSError, ValueError): return {"offline": True} limits = data["/api/claude_limits"] if isinstance(limits, dict): latest = limits.get("latest") or {} elif isinstance(limits, list) and limits: latest = limits[-1] else: latest = {} claude_session = (data.get("/api/claude_session") or {}).get("session") sessions = (data.get("/api/last_session") or {}).get("sessions") or [] return { "offline": False, "claude": { "context_pct": latest.get("context_pct"), "five_hour_pct": latest.get("five_hour_pct"), "five_hour_resets_at": latest.get("five_hour_resets_at"), "seven_day_pct": latest.get("seven_day_pct"), "seven_day_resets_at": latest.get("seven_day_resets_at"), "extra_used": latest.get("extra_used_credits"), "extra_limit": latest.get("extra_monthly_limit"), "extra_utilization": latest.get("extra_utilization"), "extra_currency": latest.get("extra_currency"), "model": _claude_model(claude_session), }, "opencode": {**_opencode(data.get("/api/summary") or []), **_opencode_session(sessions)}, } return Module(name="llm", title="LLM usage", state=state)