naisys
Version:
NAISYS - Autonomous AI agent runner with built-in context management and cost tracking
133 lines • 6.7 kB
JavaScript
import stringArgv from "string-argv";
import { costCmd } from "../command/commandDefs.js";
import { calculateModelCacheSavings } from "./cacheSavings.js";
export function createCostDisplayService({ globalConfig }, { agentConfig }, costTracker, modelService, output) {
function formatCostDetail(label, cost, tokens, rate) {
return ` ${label}: $${cost.toFixed(4)} for ${tokens.toLocaleString()} tokens at $${rate}/MTokens`;
}
function printCosts() {
const modelCosts = costTracker.getModelCosts();
const totalStoredCost = costTracker.getTotalCost();
const periodInfo = costTracker.getPeriodInfo();
// Build model breakdowns from in-memory data
const modelBreakdowns = Array.from(modelCosts.entries())
.map(([model, data]) => ({
model,
...data,
}))
.sort((a, b) => b.cost - a.cost);
// Calculate aggregate cache token counts
let totalCacheWriteTokens = 0;
let totalCacheReadTokens = 0;
for (const data of modelCosts.values()) {
totalCacheWriteTokens += data.cacheWriteTokens;
totalCacheReadTokens += data.cacheReadTokens;
}
const totalCacheTokens = totalCacheWriteTokens + totalCacheReadTokens;
// Calculate cache savings for display
let totalCacheSavingsAmount = 0;
let totalCostWithoutCaching = 0;
for (const modelData of modelBreakdowns) {
try {
const model = modelService.getLlmModel(modelData.model);
const cacheSavings = calculateModelCacheSavings(modelData, model);
if (cacheSavings) {
totalCacheSavingsAmount += cacheSavings.savingsAmount;
totalCostWithoutCaching += cacheSavings.costWithoutCaching;
}
else {
totalCostWithoutCaching += modelData.cost || 0;
}
}
catch {
totalCostWithoutCaching += modelData.cost || 0;
}
}
const spendLimit = agentConfig().spendLimitDollars || globalConfig().spendLimitDollars;
const spendLimitHours = agentConfig().spendLimitHours || globalConfig().spendLimitHours;
// Show period information if time-based limits are enabled
if (periodInfo && spendLimitHours !== undefined) {
const formatTime = (date) => {
return date.toLocaleTimeString("en-US", {
hour: "2-digit",
minute: "2-digit",
hour12: true,
});
};
output.comment(`Current period: ${formatTime(periodInfo.periodStart)} - ${formatTime(periodInfo.periodEnd)} (${spendLimitHours} hour${spendLimitHours !== 1 ? "s" : ""})`);
output.comment(`Period cost: $${periodInfo.periodCost.toFixed(2)} of $${spendLimit} limit`);
}
output.comment(`Total cost: $${totalStoredCost.toFixed(2)}${spendLimitHours === undefined ? ` of $${spendLimit} limit` : ""}`);
// Calculate and display cache savings if caching was used
if (totalCacheTokens > 0) {
const savingsPercent = totalCostWithoutCaching > 0
? (totalCacheSavingsAmount / totalCostWithoutCaching) * 100
: 0;
output.comment(`Cache savings: $${totalCacheSavingsAmount.toFixed(4)} (${savingsPercent.toFixed(1)}% saved vs $${totalCostWithoutCaching.toFixed(4)} without caching)`);
output.comment(`Cache usage: ${totalCacheTokens.toLocaleString()} tokens (${totalCacheWriteTokens.toLocaleString()} write, ${totalCacheReadTokens.toLocaleString()} read)`);
}
// Show detailed breakdown by model
for (const modelData of modelBreakdowns) {
let model;
try {
model = modelService.getLlmModel(modelData.model);
}
catch {
output.comment(` Non-model: ${modelData.model}`);
output.comment(` Total cost: $${(modelData.cost || 0).toFixed(4)}`);
continue;
}
output.comment(` ${model.label}: $${(modelData.cost || 0).toFixed(4)} total`);
// Show token breakdown
const inputTokens = modelData.inputTokens || 0;
const outputTokens = modelData.outputTokens || 0;
const cacheWriteTokens = modelData.cacheWriteTokens || 0;
const cacheReadTokens = modelData.cacheReadTokens || 0;
if (inputTokens > 0) {
const inputCost = (inputTokens * model.inputCost) / 1_000_000;
output.comment(formatCostDetail("Input", inputCost, inputTokens, model.inputCost));
}
if (outputTokens > 0) {
const outputCost = (outputTokens * model.outputCost) / 1_000_000;
output.comment(formatCostDetail("Output", outputCost, outputTokens, model.outputCost));
}
if (model.cacheWriteCost && cacheWriteTokens > 0) {
const cacheWriteCost = (cacheWriteTokens * model.cacheWriteCost) / 1_000_000;
output.comment(formatCostDetail("Cache write", cacheWriteCost, cacheWriteTokens, model.cacheWriteCost));
}
if (model.cacheReadCost && cacheReadTokens > 0) {
const cacheReadCost = (cacheReadTokens * model.cacheReadCost) / 1_000_000;
output.comment(formatCostDetail("Cache read", cacheReadCost, cacheReadTokens, model.cacheReadCost));
}
// Show cache savings for this model
const cacheSavings = calculateModelCacheSavings(modelData, model);
if (cacheSavings) {
output.comment(` Cache savings: $${cacheSavings.savingsAmount.toFixed(4)} (${cacheSavings.savingsPercent.toFixed(1)}% saved vs $${cacheSavings.costWithoutCaching.toFixed(4)} without caching)`);
}
}
}
/** ns-cost [reset]: Show cost breakdown or reset cost tracking data */
function handleCommand(cmdArgs) {
const argv = stringArgv(cmdArgs);
const subcommand = argv[0];
if (subcommand === "reset") {
costTracker.resetCosts();
return "Cost tracking data cleared.";
}
else if (subcommand) {
return "The 'ns-cost' command only supports the 'reset' parameter.";
}
else {
printCosts();
return "";
}
}
const registrableCommand = {
command: costCmd,
handleCommand,
};
return {
...registrableCommand,
};
}
//# sourceMappingURL=costDisplayService.js.map