Repository Public
1/** Spend: today vs the daily cap, the last 7 days, and the last 7 days by model. Real numbers only. */2import type { FastifyInstance } from 'fastify';3import { SpendSummary, type Provider } from '@aos/shared';4import type { Queryable } from '../../db.js';5import { authOf, type Ctx } from '../../http.js';6import { getSettings } from '../settings/store.js';78/**9 * Spend recorded by the kernel for the current day. `spend_days.day` is assumed to be the10 * database's `CURRENT_DATE` (the kernel writes it in the same database session time zone).11 */12export async function todaySpendUsd(db: Queryable): Promise<number> {13 const row = await db.one<{ usd: number }>('SELECT usd::float8 AS usd FROM spend_days WHERE day = CURRENT_DATE');14 return row?.usd ?? 0;15}1617export default async function spendPlugin(app: FastifyInstance, ctx: Ctx): Promise<void> {18 const { db } = ctx;19 app.get('/spend', { config: { doc: { summary: 'Spend summary (today, 7 days, by model over 7 days)', response: SpendSummary, responseName: 'SpendSummary' } } }, async (req): Promise<SpendSummary> => {20 const a = authOf(req);21 const settings = await getSettings(db, a.userId);22 const days = await db.q<{ day: string; usd: number }>(23 `SELECT to_char(d::date, 'YYYY-MM-DD') AS day, coalesce(s.usd, 0)::float8 AS usd24 FROM generate_series(CURRENT_DATE - 6, CURRENT_DATE, interval '1 day') d25 LEFT JOIN spend_days s ON s.day = d::date ORDER BY d`,26 );27 const byModel = await db.q<{ provider: string; model: string; usd: number; input_tokens: number; output_tokens: number }>(28 `SELECT r.provider, r.model,29 coalesce(sum((s.usage->>'usd')::float8), 0)::float8 AS usd,30 coalesce(sum((s.usage->>'inputTokens')::float8), 0)::float8 AS input_tokens,31 coalesce(sum((s.usage->>'outputTokens')::float8), 0)::float8 AS output_tokens32 FROM steps s JOIN runs r ON r.id = s.run_id JOIN missions m ON m.id = r.mission_id33 WHERE m.owner_id = $1 AND s.kind = 'model_call' AND s.usage IS NOT NULL AND s.started_at >= CURRENT_DATE - 634 GROUP BY r.provider, r.model ORDER BY usd DESC, r.model`,35 [a.userId],36 );37 return {38 todayUsd: days[days.length - 1]?.usd ?? 0,39 dailyBudgetUsd: settings.dailyBudgetUsd,40 last7Days: days,41 byModel: byModel.map((r) => ({ provider: r.provider as Provider, model: r.model, usd: r.usd, inputTokens: r.input_tokens, outputTokens: r.output_tokens })),42 };43 });44}45