From 1058341dd15e89e180bbfa72f8da8d891dd912e2 Mon Sep 17 00:00:00 2001 From: drew Date: Tue, 19 May 2026 19:18:23 -0400 Subject: [PATCH] feat(telemetry): per-1M-token rate annotations + friendly uninitialized-DB note The cost table now shows the resolved in/out/cached per-1M-token rates in parens next to each priced model, so an operator can see how a row's cost was derived. And controller-DB query failures distinguish "DB reachable but no schema yet" (the normal pre-first-run state, before the master runs create_all) from a real query error, instead of surfacing a raw SQL "no such table" dump. --- .opencode/telemetry/app.js | 17 ++++++++++++- .opencode/telemetry/server.py | 28 ++++++++++++++++++---- tests/auto_agents/test_telemetry_server.py | 19 +++++++++++++++ 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/.opencode/telemetry/app.js b/.opencode/telemetry/app.js index 251c7cb5a..027879c55 100644 --- a/.opencode/telemetry/app.js +++ b/.opencode/telemetry/app.js @@ -709,6 +709,13 @@ document.getElementById('cost-group-select').addEventListener('change', () => { PANES['cost'].refresh(); }); +// Format a $/1M-token rate: up to 3 decimals, trailing zeros stripped +// ($5, $0.125, $0.05) so the rates stay readable at a glance. +function fmtRate(n) { + const s = (Number(n) || 0).toFixed(3).replace(/\.?0+$/, ''); + return '$' + (s || '0'); +} + function renderCost(targetId, data) { const host = document.getElementById(targetId); host.innerHTML = ''; @@ -736,6 +743,14 @@ function renderCost(targetId, data) { el('th', { class: 'num' }, 'cost USD')))); const tbody = el('tbody'); for (const r of rows) { + // Per-1M-token rates in parens so the operator can see how the + // row's cost was derived. Shown only for priced models — the + // "unpriced" pill already conveys the all-zero case. + const priceAnno = (!byPr && r.priced && r.price) + ? el('span', { class: 'muted small', title: 'per 1M tokens' }, + ` (in ${fmtRate(r.price.in)} · out ${fmtRate(r.price.out)}` + + ` · cached ${fmtRate(r.price.cached_in)} /M)`) + : null; const firstCell = byPr ? el('td', {}, r.pr_number == null @@ -746,7 +761,7 @@ function renderCost(targetId, data) { : el('td', {}, el('code', {}, r.model || '_unknown'), ' ', - r.priced ? null : pill('unpriced', 'warn')); + r.priced ? priceAnno : pill('unpriced', 'warn')); tbody.appendChild(el('tr', {}, firstCell, byPr ? el('td', { class: 'muted small' }, diff --git a/.opencode/telemetry/server.py b/.opencode/telemetry/server.py index 45ab6523c..e80c75860 100644 --- a/.opencode/telemetry/server.py +++ b/.opencode/telemetry/server.py @@ -190,6 +190,18 @@ def _iso(value: Any) -> str | None: return value.isoformat() if hasattr(value, "isoformat") else str(value) +def _controller_db_note(exc: Exception) -> str: + """Friendly note for a controller-DB query failure. A DB that exists + but has no schema yet (the controller master runs create_all on + boot) is the common pre-first-run state — distinguish it from a real + query error so the operator isn't shown a raw SQL dump.""" + msg = str(exc) + if any(s in msg for s in ("no such table", "does not exist", "UndefinedTable")): + return ("controller DB reachable but not initialized yet — " + "start the controller (its master creates the schema on boot)") + return f"controller DB query failed: {exc}" + + # ─── Forgejo API (read-only) ───────────────────────────────────────────── @@ -646,7 +658,7 @@ def _api_prs(state: str | None) -> dict[str, Any]: ] except Exception as exc: # noqa: BLE001 return {"rows": [], "states": [], - "note": f"controller DB query failed: {exc}"} + "note": _controller_db_note(exc)} rows: list[dict[str, Any]] = [] for issue in body: @@ -1844,12 +1856,20 @@ def _api_cost(days: int, group_by: str = "model") -> dict[str, Any]: provider, model, prices, in_tok, out_tok, cached, ) key = _price_key(provider, model) + # Resolved per-1M-token rates (same lookup chain _cost_usd + # uses) so the UI can show how each row's cost was derived. + p = prices.get(key) or prices.get(model) or prices.get("_unknown", {}) rows.append( { "model": model, "provider": provider, "n": r["n"], "tokens_in": in_tok, "tokens_out": out_tok, "cached": cached, "usd": usd, "priced": (key in prices or model in prices), + "price": { + "in": p.get("in", 0.0), + "out": p.get("out", 0.0), + "cached_in": p.get("cached_in", p.get("in", 0.0)), + }, } ) total_usd += usd @@ -2171,7 +2191,7 @@ def _api_attention() -> dict[str, Any]: except Exception as exc: # noqa: BLE001 return { "by_state": [], "reaped_recent": [], - "note": f"controller DB query failed: {exc}", + "note": _controller_db_note(exc), "instrumentation_pending": True, } return { @@ -2246,7 +2266,7 @@ def _api_workflows(state: str | None) -> dict[str, Any]: ).all() except Exception as exc: # noqa: BLE001 return {"rows": [], "states": [], - "note": f"controller DB query failed: {exc}", + "note": _controller_db_note(exc), "instrumentation_pending": True} return { "rows": [ @@ -2317,7 +2337,7 @@ def _api_workflow_events(workflow_id: int) -> dict[str, Any]: ).all() except Exception as exc: # noqa: BLE001 return {"workflow": None, "events": [], "attempts": [], - "note": f"controller DB query failed: {exc}"} + "note": _controller_db_note(exc)} return { "workflow": { "workflow_id": int(wf.workflow_id), diff --git a/tests/auto_agents/test_telemetry_server.py b/tests/auto_agents/test_telemetry_server.py index 7668b6121..ca9c5516c 100644 --- a/tests/auto_agents/test_telemetry_server.py +++ b/tests/auto_agents/test_telemetry_server.py @@ -224,6 +224,25 @@ def test_api_workflows_empty_without_db_url(telemetry, monkeypatch): assert payload["instrumentation_pending"] is True +def test_api_workflows_uninitialized_db_returns_friendly_note( + telemetry, tmp_path, monkeypatch +): + """A controller DB that exists but has no schema yet (the controller + has never run create_all) degrades to a friendly note — not a raw + SQL 'no such table' dump.""" + server, _cache = telemetry + db_path = tmp_path / "empty_controller.sqlite" + db_path.touch() # 0-byte file is a valid empty SQLite DB + monkeypatch.setenv("CLEVERAGENTS_DB_URL", f"sqlite:///{db_path}") + monkeypatch.setattr(server, "_CONTROLLER_ENGINE", None) + monkeypatch.setattr(server, "_CONTROLLER_ENGINE_URL", None) + payload = server._api_workflows(None) + assert payload["rows"] == [] + assert payload["instrumentation_pending"] is True + assert "not initialized" in payload["note"] + assert "no such table" not in payload["note"] + + def test_api_workflows_lists_seeded_workflows_with_state_summary(controller_db): """Seeded workflows round-trip with their attempt counts, and the state summary aggregates every workflow regardless of the filter."""