UNPKG

claude-monitor

Version:

Real-time terminal monitoring tool for Claude AI token usage

173 lines 6.04 kB
import { normalizeModelName, getTotalTokens } from '../types/index.js'; export class SessionAnalyzer { sessionDurationMs; constructor(sessionDurationHours = 5) { this.sessionDurationMs = sessionDurationHours * 60 * 60 * 1000; } transformToBlocks(entries) { if (entries.length === 0) { return []; } const blocks = []; let currentBlock = null; for (const entry of entries) { if (!currentBlock || this.shouldCreateNewBlock(currentBlock, entry)) { if (currentBlock) { this.finalizeBlock(currentBlock); blocks.push(currentBlock); const gap = this.checkForGap(currentBlock, entry); if (gap) { blocks.push(gap); } } currentBlock = this.createNewBlock(entry); } this.addEntryToBlock(currentBlock, entry); } if (currentBlock) { this.finalizeBlock(currentBlock); blocks.push(currentBlock); } this.markActiveBlocks(blocks); return blocks; } detectLimits(rawEntries) { const limits = []; for (const entry of rawEntries) { const limitInfo = this.detectSingleLimit(entry); if (limitInfo) { limits.push(limitInfo); } } return limits; } shouldCreateNewBlock(block, entry) { if (entry.timestamp >= block.endTime) { return true; } if (block.entries.length > 0) { const lastEntry = block.entries[block.entries.length - 1]; const timeDiff = entry.timestamp.getTime() - lastEntry.timestamp.getTime(); return timeDiff >= this.sessionDurationMs; } return false; } roundToHour(timestamp) { const rounded = new Date(timestamp); rounded.setMinutes(0, 0, 0); return rounded; } createNewBlock(entry) { const startTime = this.roundToHour(entry.timestamp); const endTime = new Date(startTime.getTime() + this.sessionDurationMs); const blockId = startTime.toISOString(); return { id: blockId, startTime, endTime, entries: [], tokenCounts: { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, }, isActive: false, isGap: false, burnRate: null, actualEndTime: null, perModelStats: {}, models: [], sentMessagesCount: 0, costUsd: 0, limitMessages: [], projectionData: null, burnRateSnapshot: null, }; } addEntryToBlock(block, entry) { block.entries.push(entry); const model = normalizeModelName(entry.model || 'unknown'); if (!block.perModelStats[model]) { block.perModelStats[model] = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, totalTokens: 0, cost: 0, messageCount: 0, }; } const modelStats = block.perModelStats[model]; modelStats.inputTokens += entry.inputTokens; modelStats.outputTokens += entry.outputTokens; modelStats.cacheCreationTokens += entry.cacheCreationTokens; modelStats.cacheReadTokens += entry.cacheReadTokens; modelStats.totalTokens = getTotalTokens(modelStats); modelStats.cost += entry.costUsd; modelStats.messageCount += 1; block.tokenCounts.inputTokens += entry.inputTokens; block.tokenCounts.outputTokens += entry.outputTokens; block.tokenCounts.cacheCreationTokens += entry.cacheCreationTokens; block.tokenCounts.cacheReadTokens += entry.cacheReadTokens; block.costUsd += entry.costUsd; if (!block.models.includes(model)) { block.models.push(model); } block.sentMessagesCount += 1; } finalizeBlock(block) { if (block.entries.length > 0) { const lastEntry = block.entries[block.entries.length - 1]; block.actualEndTime = lastEntry.timestamp; } block.sentMessagesCount = block.entries.length; } checkForGap(lastBlock, nextEntry) { if (!lastBlock.actualEndTime) { return null; } const gapDuration = nextEntry.timestamp.getTime() - lastBlock.actualEndTime.getTime(); if (gapDuration >= this.sessionDurationMs) { const gapId = `gap-${lastBlock.actualEndTime.toISOString()}`; return { id: gapId, startTime: lastBlock.actualEndTime, endTime: nextEntry.timestamp, actualEndTime: null, isGap: true, isActive: false, entries: [], tokenCounts: { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, }, burnRate: null, perModelStats: {}, models: [], sentMessagesCount: 0, costUsd: 0, limitMessages: [], projectionData: null, burnRateSnapshot: null, }; } return null; } markActiveBlocks(blocks) { const currentTime = new Date(); for (const block of blocks) { if (!block.isGap && block.endTime > currentTime) { block.isActive = true; } } } detectSingleLimit(_rawData) { return null; } } export const defaultAnalyzer = new SessionAnalyzer(); //# sourceMappingURL=analyzer.js.map