UNPKG

cost-claude

Version:

Claude Code cost monitoring, analytics, and optimization toolkit

178 lines 6.41 kB
import { readFile, readFileSync, readdir, readdirSync } from 'fs'; import { promisify } from 'util'; import { join } from 'path'; const readFileAsync = promisify(readFile); const readdirAsync = promisify(readdir); export class JSONLParser { async parseFile(filePath) { const content = await readFileAsync(filePath, 'utf-8'); return this.parseContent(content); } parseFileSync(filePath) { const content = readFileSync(filePath, 'utf-8'); return this.parseContent(content); } parseContent(content) { const lines = content.split('\n').filter((line) => line.trim()); const messages = []; for (const line of lines) { try { const parsed = JSON.parse(line); messages.push(parsed); } catch (error) { console.warn(`Failed to parse line: ${line.substring(0, 100)}...`, error); } } return messages; } parseLine(line) { try { return JSON.parse(line); } catch (error) { return null; } } parseMessageContent(message) { if (!message.message) return null; if (typeof message.message === 'object') { return message.message; } if (typeof message.message !== 'string') { return null; } try { return JSON.parse(message.message); } catch { try { const cleanedMessage = message.message .replace(/'/g, '"') .replace(/None/g, 'null') .replace(/True/g, 'true') .replace(/False/g, 'false') .replace(/(\w+):/g, '"$1":'); return JSON.parse(cleanedMessage); } catch (error) { try { const contentMatch = message.message.match(/'content':\s*'([^']*)'|"content":\s*"([^"]*)"/); const roleMatch = message.message.match(/'role':\s*['"](user|assistant)['"]/); const usageMatch = message.message.match(/'usage':\s*({[^}]+})/); if (roleMatch) { const content = { role: roleMatch[1], content: contentMatch ? (contentMatch[1] || contentMatch[2]) : '', }; if (usageMatch && usageMatch[1]) { try { const usageStr = usageMatch[1] .replace(/'/g, '"') .replace(/None/g, 'null'); content.usage = JSON.parse(usageStr); } catch { } } return content; } } catch { } } } console.warn('Failed to parse message content:', typeof message.message === 'string' ? message.message.substring(0, 100) : JSON.stringify(message.message).substring(0, 100)); return null; } filterByType(messages, type) { return messages.filter((msg) => msg.type === type); } filterBySession(messages, sessionId) { return messages.filter((msg) => msg.sessionId === sessionId); } filterByDateRange(messages, startDate, endDate) { return messages.filter((msg) => { const msgDate = new Date(msg.timestamp); return msgDate >= startDate && msgDate <= endDate; }); } getUniqueSessions(messages) { const sessions = new Set(); messages.forEach((msg) => { if (msg.sessionId) { sessions.add(msg.sessionId); } }); return Array.from(sessions); } sortByTimestamp(messages, ascending = true) { return [...messages].sort((a, b) => { const dateA = new Date(a.timestamp).getTime(); const dateB = new Date(b.timestamp).getTime(); return ascending ? dateA - dateB : dateB - dateA; }); } groupBySession(messages) { const grouped = new Map(); messages.forEach((msg) => { if (msg.sessionId) { if (!grouped.has(msg.sessionId)) { grouped.set(msg.sessionId, []); } grouped.get(msg.sessionId).push(msg); } }); return grouped; } calculateSessionDuration(messages) { if (messages.length === 0) return 0; const sorted = this.sortByTimestamp(messages); if (sorted.length === 0) return 0; const firstMsg = sorted[0]; const lastMsg = sorted[sorted.length - 1]; if (!firstMsg || !lastMsg) return 0; const first = new Date(firstMsg.timestamp).getTime(); const last = new Date(lastMsg.timestamp).getTime(); return last - first; } async parseDirectory(directoryPath) { const files = await readdirAsync(directoryPath); const jsonlFiles = files.filter(file => file.endsWith('.jsonl')); const allMessages = []; for (const file of jsonlFiles) { const filePath = join(directoryPath, file); try { const messages = await this.parseFile(filePath); allMessages.push(...messages); } catch (error) { console.warn(`Failed to parse file ${file}:`, error); } } return allMessages; } parseDirectorySync(directoryPath) { const files = readdirSync(directoryPath); const jsonlFiles = files.filter(file => file.endsWith('.jsonl')); const allMessages = []; for (const file of jsonlFiles) { const filePath = join(directoryPath, file); try { const messages = this.parseFileSync(filePath); allMessages.push(...messages); } catch (error) { console.warn(`Failed to parse file ${file}:`, error); } } return allMessages; } } //# sourceMappingURL=jsonl-parser.js.map