import { Hono } from "hono"; import { rollUpByModel } from "./rollup.js"; import type { ObservabilityStore } from "./spans.js"; function escapeHtml(s: string): string { return s .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } export function buildApp(store: ObservabilityStore): Hono { const app = new Hono(); app.post("/trace", async (c) => { try { const body = await c.req.json(); const counters = store.ingest(body); return c.json({ status: "accepted", counters }, 202); } catch (err) { return c.json({ error: "bad_request", message: String(err) }, 400); } }); app.get("/", (c) => c.html(renderDashboardHtml(store))); app.get("/dashboard", (c) => c.html(renderDashboardHtml(store))); app.get("/dashboard.json", (c) => c.json({ counters: store.counters(), models: rollUpByModel(store.snapshot()), }), ); app.get("/healthz", (c) => c.json({ status: "ok", counters: store.counters() }), ); return app; } export function renderDashboardHtml(store: ObservabilityStore): string { const rollups = rollUpByModel(store.snapshot()); const counters = store.counters(); const rows = rollups .map( (r) => `
| model | spans | errors | input tok | output tok | ", "cost | p50 ms | p95 ms | p99 ms | ", "
|---|
POST OTel-GenAI spans to /trace. JSON roll-up at /dashboard.json.
", "", ].join("\n"); }