cost-claude
Version:
Claude Code cost monitoring, analytics, and optimization toolkit
124 lines • 5.03 kB
JavaScript
export class SessionAnalyzer {
parser;
calculator;
constructor(parser, calculator) {
this.parser = parser;
this.calculator = calculator;
}
async analyzeSession(messages, sessionId) {
const sessionMessages = messages.filter((m) => m.sessionId === sessionId);
if (sessionMessages.length === 0)
return null;
const sorted = this.parser.sortByTimestamp(sessionMessages);
const userMessages = this.parser.filterByType(sessionMessages, 'user');
const assistantMessages = this.parser.filterByType(sessionMessages, 'assistant');
const duration = this.parser.calculateSessionDuration(sessionMessages);
const totalCost = this.calculateTotalCost(assistantMessages);
const costBreakdown = this.calculateCostBreakdown(assistantMessages);
const efficiency = this.calculateEfficiency(assistantMessages, duration);
const timeline = this.buildTimeline(sorted);
return {
sessionId,
duration,
totalCost,
messageCount: sessionMessages.length,
userMessageCount: userMessages.length,
assistantMessageCount: assistantMessages.length,
costBreakdown,
efficiency,
timeline,
};
}
calculateTotalCost(messages) {
return messages.reduce((sum, msg) => {
if (msg.costUSD !== null && msg.costUSD !== undefined) {
return sum + msg.costUSD;
}
const content = this.parser.parseMessageContent(msg);
if (content?.usage) {
const cost = this.calculator.calculate(content.usage);
return sum + cost;
}
return sum;
}, 0);
}
calculateCostBreakdown(messages) {
const breakdown = {
inputTokensCost: 0,
outputTokensCost: 0,
cacheCreationCost: 0,
cacheReadCost: 0,
};
messages.forEach((msg) => {
const content = this.parser.parseMessageContent(msg);
if (content?.usage) {
const costs = this.calculator.calculateBreakdown(content.usage);
breakdown.inputTokensCost += costs.inputTokensCost;
breakdown.outputTokensCost += costs.outputTokensCost;
breakdown.cacheCreationCost += costs.cacheCreationCost;
breakdown.cacheReadCost += costs.cacheReadCost;
}
});
return breakdown;
}
calculateEfficiency(messages, duration) {
let totalInputTokens = 0;
let totalOutputTokens = 0;
let cacheHits = 0;
let totalResponseTime = 0;
let responseCount = 0;
messages.forEach((msg) => {
if (msg.type === 'assistant') {
const content = this.parser.parseMessageContent(msg);
if (content?.usage) {
totalInputTokens += content.usage.input_tokens || 0;
totalOutputTokens += content.usage.output_tokens || 0;
cacheHits += content.usage.cache_read_input_tokens || 0;
}
if (msg.durationMs) {
totalResponseTime += msg.durationMs;
responseCount++;
}
}
});
const totalCost = this.calculateTotalCost(messages);
const totalTokens = totalInputTokens + totalOutputTokens;
return {
cacheHitRate: totalInputTokens > 0 ? (cacheHits / (totalInputTokens + cacheHits)) * 100 : 0,
avgResponseTime: responseCount > 0 ? totalResponseTime / responseCount : 0,
costPerMinute: duration > 0 ? (totalCost / duration) * 60000 : 0,
tokensPerMessage: messages.length > 0 ? totalTokens / messages.length : 0,
};
}
buildTimeline(messages) {
let cumulativeCost = 0;
return messages.map((msg) => {
if (msg.costUSD) {
cumulativeCost += msg.costUSD;
}
return {
timestamp: msg.timestamp,
type: msg.type,
cost: msg.costUSD || 0,
cumulativeCost,
summary: this.extractSummary(msg),
};
});
}
extractSummary(message) {
if (message.type === 'summary') {
return message.summary || 'Session summary';
}
const content = this.parser.parseMessageContent(message);
if (!content)
return `${message.type} message`;
if (message.type === 'user' && typeof content.content === 'string') {
return content.content.substring(0, 100) + (content.content.length > 100 ? '...' : '');
}
else if (message.type === 'assistant') {
return `Response (${message.durationMs || 0}ms, $${(message.costUSD || 0).toFixed(4)})`;
}
return `${message.type} message`;
}
}
//# sourceMappingURL=session-analyzer.js.map