import type { FastifyInstance } from 'fastify'; import { query } from '../db/client.js'; import { logger } from '../observability/logger.js'; interface RtkStatsBody { host: string; commands: Array<{ rtk_id: number; timestamp: string; original_cmd: string; rtk_cmd: string; input_tokens: number; output_tokens: number; saved_tokens: number; savings_pct: number; exec_time_ms: number; project_path: string; }>; } export async function rtkRoutes(app: FastifyInstance): Promise { // ─── Ingest RTK stats from client machines ─────────────────────────── app.post<{ Body: RtkStatsBody }>('/v1/rtk/stats', async (req, reply) => { const { host, commands } = req.body; if (!host || !Array.isArray(commands)) { reply.code(400); return { error: 'host and commands array required' }; } let inserted = 0; for (const cmd of commands) { try { const result = await query( `INSERT INTO rtk_commands ( rtk_id, host, timestamp, original_cmd, rtk_cmd, input_tokens, output_tokens, saved_tokens, savings_pct, exec_time_ms, project_path ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) ON CONFLICT (host, rtk_id) DO NOTHING`, [ cmd.rtk_id, host, cmd.timestamp, cmd.original_cmd, cmd.rtk_cmd, cmd.input_tokens, cmd.output_tokens, cmd.saved_tokens, cmd.savings_pct, cmd.exec_time_ms, cmd.project_path, ], ); if (result.rowCount && result.rowCount > 0) inserted += 1; } catch (err) { logger.warn({ err, rtk_id: cmd.rtk_id }, 'Failed to insert RTK command'); } } logger.info({ host, received: commands.length, inserted }, 'RTK stats ingested'); return { received: commands.length, inserted }; }); // ─── Aggregate stats (all hosts) ────────────────────────────────────── app.get<{ Querystring: { period?: string } }>('/v1/rtk/stats', async (req) => { const intervals: Record = { today: "timestamp >= CURRENT_DATE", week: "timestamp >= CURRENT_DATE - INTERVAL '7 days'", month: "timestamp >= CURRENT_DATE - INTERVAL '30 days'", all: '1=1', }; const where = intervals[req.query.period ?? 'all'] ?? intervals['all']!; const result = await query<{ total_commands: string; total_input: string; total_output: string; total_saved: string; avg_savings: string; hosts: string; projects: string; }>(`SELECT COUNT(*) as total_commands, COALESCE(SUM(input_tokens), 0) as total_input, COALESCE(SUM(output_tokens), 0) as total_output, COALESCE(SUM(saved_tokens), 0) as total_saved, COALESCE(AVG(savings_pct), 0) as avg_savings, COUNT(DISTINCT host) as hosts, COUNT(DISTINCT project_path) as projects FROM rtk_commands WHERE ${where}`); const row = result.rows[0]!; return { period: req.query.period ?? 'all', total_commands: parseInt(row.total_commands, 10), total_input_tokens: parseInt(row.total_input, 10), total_output_tokens: parseInt(row.total_output, 10), total_saved_tokens: parseInt(row.total_saved, 10), avg_savings_pct: parseFloat(row.avg_savings), unique_hosts: parseInt(row.hosts, 10), unique_projects: parseInt(row.projects, 10), }; }); // ─── Top commands by savings ────────────────────────────────────────── app.get('/v1/rtk/commands', async () => { const result = await query<{ cmd: string; count: string; saved: string; avg_pct: string; }>(`SELECT split_part(rtk_cmd, ' ', 1) || ' ' || split_part(rtk_cmd, ' ', 2) as cmd, COUNT(*) as count, COALESCE(SUM(saved_tokens), 0) as saved, COALESCE(AVG(savings_pct), 0) as avg_pct FROM rtk_commands WHERE timestamp >= CURRENT_DATE - INTERVAL '30 days' GROUP BY cmd ORDER BY saved DESC LIMIT 20`); return { commands: result.rows.map(r => ({ cmd: r.cmd, count: parseInt(r.count, 10), saved_tokens: parseInt(r.saved, 10), avg_savings_pct: parseFloat(r.avg_pct), })), }; }); // ─── Per-host breakdown ─────────────────────────────────────────────── app.get('/v1/rtk/hosts', async () => { const result = await query<{ host: string; count: string; saved: string; last_seen: string; }>(`SELECT host, COUNT(*) as count, COALESCE(SUM(saved_tokens), 0) as saved, MAX(timestamp) as last_seen FROM rtk_commands GROUP BY host ORDER BY saved DESC`); return { hosts: result.rows.map(r => ({ host: r.host, commands: parseInt(r.count, 10), saved_tokens: parseInt(r.saved, 10), last_seen: r.last_seen, })), }; }); }