Agent-JAE/default-extensions/cost-tracker.ts
jae a1b6e22c01 feat: add 16 default extensions for Agent JAE
New extensions shipping with Agent JAE:
- jae-rules: Per-project .jae/rules.md injection
- cost-tracker: Token/cost tracking with budget limits
- project-dna: Auto-analyze project fingerprint
- teach-mode: Pedagogical explanation mode
- bookmarks: File bookmark management
- replay: Session recording and asciicast export
- checkpoints: Git-based undo/time-travel
- pr-review: Autonomous PR diff review
- auto-docs: Auto-generate README and docs
- screenshot-context: Vision analysis for screenshots
- deploy: One-command deploy (Docker/Vercel/rsync/static)
- pair-programming: Real-time file watching with feedback
- dashboard: Terminal HUD with live stats
- swarm: Multi-agent parallel task execution
- skill-marketplace: Search/install/publish skills
- widget-api-demo: TUI widget API demonstration
2026-03-23 20:14:41 +01:00

104 lines
3.4 KiB
TypeScript

import type { ExtensionAPI, ExtensionContext } from "@jaeswift/jae-coding-agent";
interface CostState {
inputTokens: number;
outputTokens: number;
cacheReadTokens: number;
cacheWriteTokens: number;
totalCost: number;
budgetLimit: number | null;
turns: number;
}
const COST_PER_1K_INPUT = 0.003;
const COST_PER_1K_OUTPUT = 0.015;
const COST_PER_1K_CACHE_READ = 0.0003;
const COST_PER_1K_CACHE_WRITE = 0.00375;
export default function (pi: ExtensionAPI) {
const state: CostState = {
inputTokens: 0,
outputTokens: 0,
cacheReadTokens: 0,
cacheWriteTokens: 0,
totalCost: 0,
budgetLimit: null,
turns: 0,
};
function recalcCost() {
state.totalCost =
(state.inputTokens / 1000) * COST_PER_1K_INPUT +
(state.outputTokens / 1000) * COST_PER_1K_OUTPUT +
(state.cacheReadTokens / 1000) * COST_PER_1K_CACHE_READ +
(state.cacheWriteTokens / 1000) * COST_PER_1K_CACHE_WRITE;
}
function updateStatus(ctx: ExtensionContext) {
const cost = state.totalCost.toFixed(4);
const budget = state.budgetLimit ? ` / $${state.budgetLimit.toFixed(2)}` : "";
const tokens = ((state.inputTokens + state.outputTokens) / 1000).toFixed(1);
ctx.ui.setStatus("cost-tracker", `\u{1F4B0} $${cost}${budget} | ${tokens}k tok | ${state.turns} turns`);
}
pi.on("turn_start", async (_event, ctx) => {
state.turns++;
updateStatus(ctx);
});
pi.on("agent_end", async (event, ctx) => {
for (const message of event.messages) {
if (message && typeof message === "object" && "usage" in message) {
const u = (message as any).usage;
state.inputTokens += u.input || 0;
state.outputTokens += u.output || 0;
state.cacheReadTokens += u.cacheRead || 0;
state.cacheWriteTokens += u.cacheWrite || 0;
}
}
recalcCost();
updateStatus(ctx);
if (state.budgetLimit && state.totalCost >= state.budgetLimit) {
ctx.ui.notify(
`\u26A0\uFE0F Budget limit of $${state.budgetLimit} reached! Total: $${state.totalCost.toFixed(4)}`,
"warning",
);
}
});
pi.registerCommand("cost", {
description: "Show session cost breakdown",
handler: async (_args, ctx) => {
ctx.ui.notify(
`\u{1F4CA} Cost Report:\n` +
` Input: ${state.inputTokens.toLocaleString()} tokens ($${((state.inputTokens / 1000) * COST_PER_1K_INPUT).toFixed(4)})\n` +
` Output: ${state.outputTokens.toLocaleString()} tokens ($${((state.outputTokens / 1000) * COST_PER_1K_OUTPUT).toFixed(4)})\n` +
` Cache Read: ${state.cacheReadTokens.toLocaleString()} tokens\n` +
` Cache Write: ${state.cacheWriteTokens.toLocaleString()} tokens\n` +
` Total Cost: $${state.totalCost.toFixed(4)}\n` +
` Turns: ${state.turns}`,
"info",
);
},
});
pi.registerCommand("budget", {
description: "Set session budget limit: /budget <amount>",
handler: async (args, ctx) => {
const amount = parseFloat(args || "");
if (isNaN(amount) || amount <= 0) {
ctx.ui.notify(
state.budgetLimit
? `Current budget: $${state.budgetLimit.toFixed(2)} | Spent: $${state.totalCost.toFixed(4)}`
: "No budget set. Usage: /budget 5.00",
"info",
);
} else {
state.budgetLimit = amount;
ctx.ui.notify(`\u{1F4B0} Budget set to $${amount.toFixed(2)}`, "info");
updateStatus(ctx);
}
},
});
}