import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent"; import { collectBreakdown } from "./breakdown"; import { bar, fmt, percent, themeColorFor } from "./format"; import type { Section } from "./types"; /** pi's default compaction reserveTokens (settings.json `compaction.reserveTokens`). */ const RESERVE_TOKENS = 16_384; /** Renders the context overview + breakdown as a plain-text report. */ export const buildReport = (pi: ExtensionAPI, ctx: ExtensionCommandContext): string => { const usage = ctx.getContextUsage(); const model = ctx.model; const theme = ctx.ui.theme; const lines: string[] = []; lines.push(theme.fg("accent", theme.bold("Context Usage"))); lines.push(""); if (!usage) { lines.push(theme.fg("dim", "No active model or session yet.")); return lines.join("\n"); } const { tokens, contextWindow, percent: rawPercent } = usage; const threshold = contextWindow - RESERVE_TOKENS; const color = themeColorFor(tokens ?? 0, contextWindow, threshold); lines.push( `${theme.fg("muted", "Model:")} ${theme.bold(`${model?.provider ?? "?"}/${model?.id ?? "?"}`)}`, ); lines.push(`${theme.fg("muted", "Context window:")} ${theme.bold(fmt(contextWindow))} tokens`); lines.push( `${theme.fg("muted", "Used:")} ${theme.fg(color, `${fmt(tokens ?? 0)} tokens (${rawPercent?.toFixed(1) ?? "?"}%)`)}`, ); if (tokens !== null) { lines.push( `${theme.fg("muted", "Remaining:")} ${theme.bold(fmt(Math.max(0, contextWindow - tokens)))} tokens`, ); } lines.push(""); lines.push(` ${theme.fg(color, bar(tokens ?? 0, contextWindow, threshold))}`); lines.push( theme.fg( "dim", ` ┃ = auto-compaction threshold (window − reserve ${fmt(RESERVE_TOKENS)} = ${fmt(threshold)})`, ), ); if (tokens === null || rawPercent === null) { lines.push(theme.fg("dim", "Breakdown unavailable (tokens unknown).")); return lines.join("\n"); } const hasReportedUsage = tokens > 0; /* --- section breakdown (estimates) --- */ lines.push(""); lines.push(theme.fg("accent", theme.bold("Section breakdown (estimated)"))); if (!hasReportedUsage) { lines.push( theme.fg( "dim", "No provider usage yet — sizes below are chars/4 estimates from your session.", ), ); } const breakdown = collectBreakdown(pi, ctx); const sections: Section[] = [breakdown.system, breakdown.messages, breakdown.tools]; for (const section of sections) { const sColor = themeColorFor(section.total, tokens, threshold); lines.push(""); const pctLabel = hasReportedUsage ? ` ${theme.fg("dim", `(${percent(section.total, tokens).toFixed(1)}%)`)}` : ""; lines.push( `${theme.bold(section.title)}${" ".repeat(Math.max(1, 16 - section.title.length))}` + `${theme.fg("muted", fmt(section.total))} ` + `${theme.fg(sColor, bar(section.total, tokens, threshold, 12))} ` + pctLabel, ); for (const item of section.lines) { const detail = item.detail ? ` ${theme.fg("dim", item.detail)}` : ""; lines.push( ` ${theme.fg("dim", "├")} ${item.label}${" ".repeat(Math.max(1, 22 - item.label.length))}${theme.fg("muted", fmt(item.tokens))}${detail}`, ); } } if (hasReportedUsage) { /* --- reconciliation vs provider-reported total --- */ const accounted = sections.reduce((a, s) => a + s.total, 0); const ratio = (accounted / tokens) * 100; const diff = tokens - accounted; lines.push(""); lines.push( ratio >= 90 ? theme.fg( "success", `✓ Breakdown accounts for ${fmt(accounted)} of ${fmt(tokens)} tokens (${ratio.toFixed(0)}%)`, ) : theme.fg( "warning", `Breakdown accounts for ${fmt(accounted)} of ${fmt(tokens)} tokens (${ratio.toFixed(0)}%); ` + `${fmt(diff)} unaccounted (cache markers, encodings, overhead)`, ), ); } lines.push(""); lines.push( theme.fg( "dim", "Sizes are chars/4 estimates — the provider's own tokenizer may differ. Subcommands: /context skills · tools · files", ), ); return lines.join("\n"); };