/**
* Flynn Token Usage Page
*
* Shows per-session token usage breakdown including input/output tokens,
* API calls, estimated cost, and delegation details.
* Auto-refreshes every 30 seconds.
*/
let _timer = null;
function formatNumber(n) {
return (n ?? 0).toLocaleString();
}
function formatCost(n) {
if (!n || n === 0) {return '$0.00';}
if (n < 0.01) {return `$${n.toFixed(4)}`;}
return `$${n.toFixed(2)}`;
}
function truncateId(id) {
if (!id) {return '-';}
if (id.length <= 24) {return id;}
return id.slice(0, 24) + '\u2026';
}
async function loadUsage(el, client) {
let data;
try {
data = await client.call('system.tokenUsage');
} catch (err) {
el.innerHTML = `
Failed to load usage: ${err.message}
`;
return;
}
const sessions = data?.sessions ?? [];
// Compute totals across all sessions
let totalInput = 0;
let totalOutput = 0;
let totalCalls = 0;
let totalCost = 0;
for (const s of sessions) {
totalInput += s.total?.inputTokens ?? 0;
totalOutput += s.total?.outputTokens ?? 0;
totalCalls += s.total?.calls ?? 0;
totalCost += s.total?.estimatedCost ?? 0;
}
// Summary cards
const summaryHtml = `
Total Input Tokens
${formatNumber(totalInput)}
Total Output Tokens
${formatNumber(totalOutput)}
Total Tokens
${formatNumber(totalInput + totalOutput)}
API Calls
${formatNumber(totalCalls)}
Estimated Cost
${formatCost(totalCost)}
Active Sessions
${sessions.length}
`;
// Per-session table
let tableHtml = '';
if (sessions.length === 0) {
tableHtml = 'No active sessions with usage data
';
} else {
const rows = sessions.map(s => {
const inTok = s.total?.inputTokens ?? 0;
const outTok = s.total?.outputTokens ?? 0;
const calls = s.total?.calls ?? 0;
const cost = s.total?.estimatedCost ?? 0;
// Build delegation breakdown if present
const delegationEntries = Object.entries(s.delegation ?? {});
let delegationCell = '-';
if (delegationEntries.length > 0) {
delegationCell = delegationEntries.map(([tier, stats]) =>
`${tier} ${formatNumber(stats.inputTokens)}/${formatNumber(stats.outputTokens)}`,
).join('
');
}
return `
| ${truncateId(s.sessionId)} |
${formatNumber(inTok)} |
${formatNumber(outTok)} |
${formatNumber(inTok + outTok)} |
${formatNumber(calls)} |
${formatCost(cost)} |
${delegationCell} |
`;
}).join('');
tableHtml = `
| Session |
Input |
Output |
Total |
Calls |
Cost |
Delegation |
${rows}
`;
}
el.innerHTML = `
${summaryHtml}
Per-Session Breakdown
${tableHtml}
`;
// Wire up refresh button
const refreshBtn = el.querySelector('#usage-refresh-btn');
if (refreshBtn) {
refreshBtn.addEventListener('click', () => {
loadUsage(el, client).catch(() => {});
});
}
}
export const UsagePage = {
async render(el, client) {
await loadUsage(el, client);
// Auto-refresh every 30 seconds
_timer = setInterval(() => {
loadUsage(el, client).catch(() => {});
}, 30000);
},
teardown() {
if (_timer) {
clearInterval(_timer);
_timer = null;
}
},
};