gemini-cost-tracker
Version:
CLI tool to display token usage and costs for Gemini and Vertex AI
122 lines • 5.13 kB
JavaScript
import { AppError, ErrorCode } from '../../types/index.js';
import { getPriceModel, convertPrice } from './priceTable.js';
import { logger } from '../../utils/logger.js';
export class CostCalculator {
calculateCost(usage, targetCurrency = 'USD') {
try {
const priceModel = getPriceModel(usage.model);
// Calculate costs in original currency (USD)
const inputCostUSD = (usage.inputTokens / 1000) * priceModel.inputTokenPrice;
const outputCostUSD = (usage.outputTokens / 1000) * priceModel.outputTokenPrice;
const totalCostUSD = inputCostUSD + outputCostUSD;
// Convert to target currency if needed
const inputCost = convertPrice(inputCostUSD, 'USD', targetCurrency);
const outputCost = convertPrice(outputCostUSD, 'USD', targetCurrency);
const totalCost = convertPrice(totalCostUSD, 'USD', targetCurrency);
return {
usageId: usage.id,
inputCost,
outputCost,
totalCost,
currency: targetCurrency,
calculatedAt: new Date(),
};
}
catch (error) {
throw new AppError(ErrorCode.COST_CALCULATION_ERROR, `Failed to calculate cost for usage ${usage.id}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
async generateReport(usageData, period, currency = 'USD') {
try {
logger.info(`Generating cost report for ${usageData.length} usage records`);
const details = [];
let totalInputTokens = 0;
let totalOutputTokens = 0;
let totalCost = 0;
for (const usage of usageData) {
const cost = this.calculateCost(usage, currency);
details.push({
date: usage.timestamp,
service: usage.service,
model: usage.model,
usage,
cost,
});
totalInputTokens += usage.inputTokens;
totalOutputTokens += usage.outputTokens;
totalCost += cost.totalCost;
}
// Sort details by date (newest first)
details.sort((a, b) => b.date.getTime() - a.date.getTime());
const report = {
period: {
start: period.start,
end: period.end,
},
summary: {
totalInputTokens,
totalOutputTokens,
totalCost,
currency,
},
details,
};
logger.info(`Generated report with total cost: ${totalCost.toFixed(4)} ${currency}`);
return report;
}
catch (error) {
if (error instanceof AppError) {
throw error;
}
throw new AppError(ErrorCode.REPORT_GENERATION_ERROR, `Failed to generate cost report: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
calculateDailyBreakdown(usageData, currency = 'USD') {
const breakdown = {};
for (const usage of usageData) {
const dateKey = usage.timestamp.toISOString().split('T')[0];
const cost = this.calculateCost(usage, currency);
if (!breakdown[dateKey]) {
breakdown[dateKey] = {
inputTokens: 0,
outputTokens: 0,
totalCost: 0,
services: {},
};
}
breakdown[dateKey].inputTokens += usage.inputTokens;
breakdown[dateKey].outputTokens += usage.outputTokens;
breakdown[dateKey].totalCost += cost.totalCost;
breakdown[dateKey].services[usage.service] =
(breakdown[dateKey].services[usage.service] || 0) + cost.totalCost;
}
return breakdown;
}
calculateModelBreakdown(usageData, currency = 'USD') {
const breakdown = {};
for (const usage of usageData) {
const cost = this.calculateCost(usage, currency);
if (!breakdown[usage.model]) {
breakdown[usage.model] = {
inputTokens: 0,
outputTokens: 0,
totalCost: 0,
usageCount: 0,
};
}
breakdown[usage.model].inputTokens += usage.inputTokens;
breakdown[usage.model].outputTokens += usage.outputTokens;
breakdown[usage.model].totalCost += cost.totalCost;
breakdown[usage.model].usageCount += 1;
}
return breakdown;
}
getTopExpensiveUsage(usageData, limit = 10, currency = 'USD') {
const withCosts = usageData.map((usage) => ({
usage,
cost: this.calculateCost(usage, currency),
}));
return withCosts.sort((a, b) => b.cost.totalCost - a.cost.totalCost).slice(0, limit);
}
}
//# sourceMappingURL=costCalculator.js.map