UNPKG

@tehreet/conduit

Version:

LLM API gateway with intelligent routing, robust process management, and health monitoring

317 lines 10.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.UsageTracker = void 0; const events_1 = require("events"); const UsageStorage_1 = require("./UsageStorage"); const MetricsCollector_1 = require("./MetricsCollector"); const log_1 = require("../utils/log"); /** * Usage tracking system for Conduit */ class UsageTracker extends events_1.EventEmitter { constructor(config = {}) { super(); this.isInitialized = false; this.config = { enabled: true, storage: { type: 'memory', retention: 30 }, metrics: { enabled: false }, ...config }; this.storage = new UsageStorage_1.UsageStorage(this.config.storage || {}); this.metrics = new MetricsCollector_1.MetricsCollector(this.config.metrics || {}); } /** * Initialize usage tracking */ async initialize() { if (this.isInitialized) { return; } if (!this.config.enabled) { (0, log_1.log)('Usage tracking is disabled'); return; } (0, log_1.log)('Initializing usage tracking...'); await this.storage.initialize(); await this.metrics.initialize(); this.isInitialized = true; this.emit('initialized'); (0, log_1.log)('Usage tracking initialized'); } /** * Track usage data */ async trackUsage(usage) { if (!this.isInitialized || !this.config.enabled) { return; } try { // Validate usage data if (!this.validateUsageData(usage)) { throw new Error('Invalid usage data'); } // Store usage data await this.storage.store(usage); // Collect metrics if (this.config.metrics?.enabled) { await this.metrics.record(usage); } this.emit('usage-tracked', usage); } catch (error) { (0, log_1.log)('Error tracking usage:', error); this.emit('error', error); throw error; } } /** * Get usage statistics */ async getUsageStats(query = {}) { if (!this.isInitialized) { throw new Error('Usage tracker not initialized'); } try { const usageData = await this.storage.query(query); return this.calculateStats(usageData, query); } catch (error) { (0, log_1.log)('Error getting usage stats:', error); this.emit('error', error); throw error; } } /** * Get cost breakdown */ async getCostBreakdown(query = {}) { if (!this.isInitialized) { throw new Error('Usage tracker not initialized'); } try { const usageData = await this.storage.query(query); return this.calculateCostBreakdown(usageData); } catch (error) { (0, log_1.log)('Error getting cost breakdown:', error); this.emit('error', error); throw error; } } /** * Get recent usage data */ async getRecentUsage(limit = 100) { if (!this.isInitialized) { throw new Error('Usage tracker not initialized'); } return this.storage.query({ limit }); } /** * Clear old usage data */ async cleanupOldData(olderThanDays = 30) { if (!this.isInitialized) { return; } const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - olderThanDays); await this.storage.cleanup(cutoffDate); this.emit('cleanup', { cutoffDate, olderThanDays }); } /** * Export usage data */ async exportData(query = {}, format = 'json') { if (!this.isInitialized) { throw new Error('Usage tracker not initialized'); } const data = await this.storage.query(query); if (format === 'csv') { return this.formatAsCSV(data); } return JSON.stringify(data, null, 2); } /** * Get usage tracking status */ getStatus() { return { initialized: this.isInitialized, enabled: this.config.enabled, storageType: this.config.storage?.type, metricsEnabled: this.config.metrics?.enabled }; } /** * Update configuration */ async updateConfig(config) { this.config = { ...this.config, ...config }; if (this.isInitialized) { await this.storage.updateConfig(this.config.storage || {}); await this.metrics.updateConfig(this.config.metrics || {}); } this.emit('config-updated', this.config); } /** * Validate usage data */ validateUsageData(usage) { return !!(usage.id && usage.timestamp && usage.model && typeof usage.tokenCount === 'number' && usage.routingReason); } /** * Calculate statistics from usage data */ calculateStats(usageData, query) { const stats = { totalRequests: usageData.length, totalTokens: 0, totalCost: 0, averageTokensPerRequest: 0, modelBreakdown: {}, timeRange: { start: query.timeRange?.start || new Date(0), end: query.timeRange?.end || new Date() }, projectBreakdown: {}, agentBreakdown: {} }; // Calculate totals and breakdowns for (const usage of usageData) { stats.totalTokens += usage.tokenCount; stats.totalCost += usage.cost || 0; // Model breakdown if (!stats.modelBreakdown[usage.model]) { stats.modelBreakdown[usage.model] = { requests: 0, tokens: 0, cost: 0 }; } stats.modelBreakdown[usage.model].requests++; stats.modelBreakdown[usage.model].tokens += usage.tokenCount; stats.modelBreakdown[usage.model].cost += usage.cost || 0; // Project breakdown const projectId = usage.projectId; if (projectId) { if (!stats.projectBreakdown[projectId]) { stats.projectBreakdown[projectId] = { requests: 0, tokens: 0, cost: 0 }; } stats.projectBreakdown[projectId].requests++; stats.projectBreakdown[projectId].tokens += usage.tokenCount; stats.projectBreakdown[projectId].cost += usage.cost || 0; } // Agent breakdown const agentId = usage.agentId; if (agentId) { if (!stats.agentBreakdown[agentId]) { stats.agentBreakdown[agentId] = { requests: 0, tokens: 0, cost: 0 }; } stats.agentBreakdown[agentId].requests++; stats.agentBreakdown[agentId].tokens += usage.tokenCount; stats.agentBreakdown[agentId].cost += usage.cost || 0; } } // Calculate averages stats.averageTokensPerRequest = stats.totalRequests > 0 ? Math.round(stats.totalTokens / stats.totalRequests) : 0; return stats; } /** * Calculate cost breakdown */ calculateCostBreakdown(usageData) { const breakdown = { total: 0, byModel: {}, byProject: {}, byAgent: {}, byTimeRange: [] }; // Group by date for time range breakdown const dailyCosts = {}; for (const usage of usageData) { const cost = usage.cost || 0; breakdown.total += cost; // By model if (!breakdown.byModel[usage.model]) { breakdown.byModel[usage.model] = 0; } breakdown.byModel[usage.model] += cost; // By project const projectId = usage.projectId; if (projectId) { if (!breakdown.byProject[projectId]) { breakdown.byProject[projectId] = 0; } breakdown.byProject[projectId] += cost; } // By agent const agentId = usage.agentId; if (agentId) { if (!breakdown.byAgent[agentId]) { breakdown.byAgent[agentId] = 0; } breakdown.byAgent[agentId] += cost; } // By date const date = usage.timestamp.toISOString().split('T')[0]; if (!dailyCosts[date]) { dailyCosts[date] = 0; } dailyCosts[date] += cost; } // Convert daily costs to array breakdown.byTimeRange = Object.entries(dailyCosts) .map(([date, cost]) => ({ date, cost })) .sort((a, b) => a.date.localeCompare(b.date)); return breakdown; } /** * Format data as CSV */ formatAsCSV(data) { if (data.length === 0) { return ''; } const headers = Object.keys(data[0]); const rows = data.map(item => headers.map(header => JSON.stringify(item[header] || '')).join(',')); return [headers.join(','), ...rows].join('\n'); } /** * Cleanup resources */ async cleanup() { if (!this.isInitialized) { return; } (0, log_1.log)('Cleaning up usage tracker...'); await this.storage.cleanup(); await this.metrics.cleanup(); this.isInitialized = false; this.emit('cleanup'); (0, log_1.log)('Usage tracker cleaned up'); } } exports.UsageTracker = UsageTracker; //# sourceMappingURL=UsageTracker.js.map