import type { FastifyInstance } from 'fastify'; import type { Sql } from '../db.js'; // token-analyzer-ui: aggregate token/cost analytics across all agent_sessions. // v1 — global view only (no per-project or per-user filtering). export interface AnalyticsSummary { total_input_tokens: number; total_output_tokens: number; total_cost: number; session_count: number; } export interface SessionAnalyticsRow { session_id: string; session_name: string; total_input_tokens: number; total_output_tokens: number; total_cost: number; last_active_at: string | null; } export interface TokenBreakdownAgg { category: string; total_tokens: number; } export function registerAnalyticsRoutes(app: FastifyInstance, sql: Sql): void { // GET /api/analytics/summary — aggregate totals across all agent_sessions. app.get('/api/analytics/summary', async () => { const [row] = await sql` SELECT COALESCE(SUM(a.input_tokens), 0)::BIGINT AS total_input_tokens, COALESCE(SUM(a.output_tokens), 0)::BIGINT AS total_output_tokens, COALESCE(SUM(a.cost), 0)::DOUBLE PRECISION AS total_cost, COUNT(DISTINCT c.session_id)::INT AS session_count FROM agent_sessions a JOIN chats c ON c.id = a.chat_id `; return row ?? { total_input_tokens: 0, total_output_tokens: 0, total_cost: 0, session_count: 0 }; }); // GET /api/analytics/sessions — per-session token/cost breakdown. app.get('/api/analytics/sessions', async () => { const rows = await sql` SELECT c.session_id AS session_id, s.name AS session_name, COALESCE(SUM(a.input_tokens), 0)::BIGINT AS total_input_tokens, COALESCE(SUM(a.output_tokens), 0)::BIGINT AS total_output_tokens, COALESCE(SUM(a.cost), 0)::DOUBLE PRECISION AS total_cost, MAX(a.last_active_at) AS last_active_at FROM agent_sessions a JOIN chats c ON c.id = a.chat_id JOIN sessions s ON s.id = c.session_id GROUP BY c.session_id, s.name ORDER BY MAX(a.last_active_at) DESC NULLS LAST `; return { sessions: rows }; }); // GET /api/analytics/token-breakdown — aggregate token_breakdown categories // across all tasks that carry the JSONB field. app.get('/api/analytics/token-breakdown', async () => { const rows = await sql<{ category: string; total_tokens: number }[]>` SELECT key AS category, SUM((value->>0)::BIGINT)::BIGINT AS total_tokens FROM tasks, LATERAL jsonb_each(token_breakdown) WHERE token_breakdown IS NOT NULL AND jsonb_typeof(token_breakdown) = 'object' GROUP BY key ORDER BY total_tokens DESC `; return { categories: rows }; }); }