UNPKG

paqad-ai

Version:

Spec-driven development framework — AI agents that think before they type

168 lines (150 loc) • 6.1 kB
import type { PaqadCost, PaqadCostLine, PaqadCostTokens } from './types'; function scaled(value: number, unit: string): string { const text = value < 100 ? value.toFixed(1).replace(/\.0$/, '') : String(Math.round(value)); return `${text}${unit}`; } export function formatTokens(tokens: number): string { if (tokens >= 1_000_000) return scaled(tokens / 1_000_000, 'M'); if (tokens >= 1_000) return scaled(tokens / 1_000, 'K'); return String(Math.max(0, Math.round(tokens))); } export function formatStopwatch(ms: number): string { const total = Math.max(0, Math.floor(ms / 1000)); const hours = Math.floor(total / 3600); const minutes = Math.floor((total % 3600) / 60); const seconds = String(total % 60).padStart(2, '0'); return hours > 0 ? `${hours}:${String(minutes).padStart(2, '0')}:${seconds}` : `${minutes}:${seconds}`; } export function formatClock(ms: number): string { const total = Math.max(0, Math.floor(ms / 1000)); const hours = Math.floor(total / 3600); const minutes = Math.floor((total % 3600) / 60); const seconds = total % 60; if (hours > 0) return `${hours}h ${String(minutes).padStart(2, '0')}m`; if (minutes > 0) return `${minutes}m ${String(seconds).padStart(2, '0')}s`; return `${seconds}s`; } export function shortAge(ms: number): string { const minutes = Math.floor(Math.max(0, ms) / 60_000); if (minutes < 1) return '<1m'; if (minutes < 60) return `${minutes}m`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours}h`; return `${Math.floor(hours / 24)}d`; } export function fullTokens(tokens: number): string { return Math.max(0, Math.round(tokens)) .toString() .replace(/\B(?=(\d{3})+(?!\d))/g, ','); } export function formatUsd(usd: number): string { if (usd > 0 && usd < 0.005) return 'under $0.01'; return `$${usd.toFixed(2)}`; } export function moneyShort(usd: number, priceDate: string): string { return `~${formatUsd(usd)} estimate, ${priceDate} prices`; } export function costPill(cost: PaqadCost | null): string { if (!cost) return 'cost: not yet'; if (cost.tokens.total === 0 && !cost.sealed) return 'cost: counting'; const tokens = `${formatTokens(cost.tokens.total)} tokens`; if (cost.usd === null) return tokens; const partial = cost.unpriced_models.length > 0 ? ', part unpriced' : ''; return `${tokens}, ${moneyShort(cost.usd, cost.price_table_date)}${partial}`; } function lineText(line: PaqadCostLine, priceDate: string): string { const tokens = `${formatTokens(line.tokens.total)} tokens`; return line.usd === null ? tokens : `${tokens}, ${moneyShort(line.usd, priceDate)}`; } function tokenKinds(tokens: PaqadCostTokens): string { return [ `${fullTokens(tokens.input)} input`, `${fullTokens(tokens.output)} output`, `${fullTokens(tokens.cache_read)} cache read`, `${fullTokens(tokens.cache_write)} cache write`, ].join(', '); } const ROLE_ACRONYMS: Readonly<Record<string, string>> = { qa: 'QA', db: 'DB', ux: 'UX', ui: 'UI', api: 'API', devops: 'DevOps', }; export function roleWords(role: string): string { const words = role .replace(/[-_]+/g, ' ') .trim() .split(/\s+/) .map((word) => ROLE_ACRONYMS[word.toLowerCase()] ?? word) .join(' '); return words ? `${words[0]!.toUpperCase()}${words.slice(1)}` : role; } export const CHANGE_SCOPE_SENTENCE = "Counted from the change's first row to its seal, this change only."; export const WHOLE_SESSION_SENTENCE = "Whole session: paqad could not split this session's turns, so this is not the change's cost."; export interface CostLinesOptions { lane: string | null; stageLabel: (stage: string) => string; } function expertLines(cost: PaqadCost, options: CostLinesOptions): string[] { const date = cost.price_table_date; if (cost.by_expert.length === 0 && cost.chief === null) { return [ options.lane === 'fast' ? 'By expert: none. The fast lane seats no High Table.' : 'By expert: none. No High Table seat has reported tokens.', ]; } const lines = ['By expert:']; for (const seat of cost.by_expert) { lines.push( seat.exact ? ` ${roleWords(seat.role)}: Run: ${lineText(seat, date)}, mostly the code it read (exact)` : ` ${roleWords(seat.role)}: Note: ~${formatTokens(seat.tokens.total)} tokens, the seat's own estimate, already inside the total`, ); } if (cost.chief) { lines.push( cost.chief.exact ? `Chief architect: Run: ${lineText(cost.chief, date)}, mostly the code it read (exact)` : `Chief architect: Note: ~${formatTokens(cost.chief.tokens.total)} tokens, its own estimate, already inside the total`, ); } else { lines.push('Chief architect: no tokens reported yet.'); } return lines; } export function costLines(cost: PaqadCost, options: CostLinesOptions): string[] { const date = cost.price_table_date; const money = cost.usd === null ? 'no money figure, since no model it used is in the price table' : `~${formatUsd(cost.usd)}, ${cost.label}, prices of ${date}`; const whole = cost.scope === 'whole-session'; const lines = [ `${whole ? 'Cost of the whole session' : 'Cost of this change'}: ${fullTokens(cost.tokens.total)} tokens, ${money}.`, `Tokens: ${tokenKinds(cost.tokens)}.`, whole ? WHOLE_SESSION_SENTENCE : CHANGE_SCOPE_SENTENCE, ...(cost.sealed ? [] : ['Counted so far: the change is not sealed yet.']), 'By stage:', ...(cost.by_stage.length === 0 ? [' none yet'] : cost.by_stage.map( (line) => ` ${options.stageLabel(line.stage)}: ${lineText(line, date)}`, )), ...expertLines(cost, options), `Basis: ${cost.basis}. Tokens come from the session's own transcripts; money is tokens times the published API price on ${date}. ${cost.plan_note}`, ]; if (cost.unpriced_models.length > 0) { lines.push( `No price for ${cost.unpriced_models.join(', ')}: its tokens are in the total, its money is not.`, ); } lines.push(`Not counted: ${cost.not_counted.join('; ')}.`); return lines; }