UNPKG

@hivetechs/hive-ai

Version:

Real-time streaming AI consensus platform with HTTP+SSE MCP integration for Claude Code, VS Code, Cursor, and Windsurf - powered by OpenRouter's unified API

342 lines (338 loc) 13.7 kB
/** * Real-time Usage Monitoring - Unified Database Implementation * * Uses the unified SQLite database instead of JSON files for data persistence. * Provides real-time monitoring of spending with budget thresholds and alerts. */ import { z } from 'zod'; import { getDatabase } from '../../storage/unified-database.js'; // Usage monitoring schema export const UsageMonitorConfigSchema = z.object({ enabled: z.boolean().default(true).describe('Enable real-time monitoring'), interval: z.number().default(60).describe('Check interval in seconds'), warningThreshold: z.number().default(0.8).describe('Warning threshold (0.0-1.0)'), criticalThreshold: z.number().default(0.95).describe('Critical threshold (0.0-1.0)'), spikeDetection: z.boolean().default(true).describe('Enable usage spike detection'), spikeThreshold: z.number().default(0.5).describe('Spike threshold as multiplier of average'), autoBreak: z.boolean().default(false).describe('Automatically pause when budget exceeded'), gracePeriod: z.number().default(300).describe('Grace period in seconds before auto-break') }); export const MonitoringSessionSchema = z.object({ sessionId: z.string().describe('Unique session identifier'), startTime: z.string().describe('Session start timestamp'), endTime: z.string().optional().describe('Session end timestamp'), status: z.enum(['active', 'paused', 'stopped']).describe('Session status'), checksPerformed: z.number().default(0).describe('Number of checks performed'), alertsSent: z.number().default(0).describe('Number of alerts sent'), lastCheck: z.string().optional().describe('Last check timestamp'), config: UsageMonitorConfigSchema.describe('Monitoring configuration') }); // Initialize monitoring tables in unified database async function initializeMonitoringTables() { const db = await getDatabase(); await db.exec(` -- Monitoring sessions table CREATE TABLE IF NOT EXISTS monitoring_sessions ( id TEXT PRIMARY KEY, session_id TEXT NOT NULL UNIQUE, start_time TEXT NOT NULL, end_time TEXT, status TEXT NOT NULL CHECK (status IN ('active', 'paused', 'stopped')), checks_performed INTEGER DEFAULT 0, alerts_sent INTEGER DEFAULT 0, last_check TEXT, config TEXT NOT NULL, -- JSON config created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP ); -- Monitor configuration table CREATE TABLE IF NOT EXISTS monitor_config ( key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at TEXT DEFAULT CURRENT_TIMESTAMP ); -- Monitoring statistics CREATE TABLE IF NOT EXISTS monitor_statistics ( id TEXT PRIMARY KEY DEFAULT 'global', total_sessions INTEGER DEFAULT 0, total_checks INTEGER DEFAULT 0, total_alerts INTEGER DEFAULT 0, avg_check_interval REAL DEFAULT 60, last_updated TEXT DEFAULT CURRENT_TIMESTAMP ); -- Initialize default statistics if not exists INSERT OR IGNORE INTO monitor_statistics (id) VALUES ('global'); -- Indexes for performance CREATE INDEX IF NOT EXISTS idx_monitoring_sessions_status ON monitoring_sessions(status); CREATE INDEX IF NOT EXISTS idx_monitoring_sessions_start_time ON monitoring_sessions(start_time); `); } // Get monitoring configuration async function getMonitoringConfig() { await initializeMonitoringTables(); const db = await getDatabase(); const configRows = await db.all('SELECT key, value FROM monitor_config'); const config = configRows.reduce((acc, row) => { try { acc[row.key] = JSON.parse(row.value); } catch { // Handle non-JSON values acc[row.key] = row.value; } return acc; }, {}); // Return default config with overrides return { enabled: true, interval: 60, warningThreshold: 0.8, criticalThreshold: 0.95, spikeDetection: true, spikeThreshold: 0.5, autoBreak: false, gracePeriod: 300, ...config }; } // Save monitoring configuration async function saveMonitoringConfig(config) { const db = await getDatabase(); for (const [key, value] of Object.entries(config)) { await db.run('INSERT OR REPLACE INTO monitor_config (key, value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP)', key, JSON.stringify(value)); } } // Get current active session async function getCurrentSession() { await initializeMonitoringTables(); const db = await getDatabase(); const row = await db.get('SELECT * FROM monitoring_sessions WHERE status = ? ORDER BY start_time DESC LIMIT 1', 'active'); if (!row) return null; return { sessionId: row.session_id, startTime: row.start_time, endTime: row.end_time, status: row.status, checksPerformed: row.checks_performed, alertsSent: row.alerts_sent, lastCheck: row.last_check, config: JSON.parse(row.config) }; } // Save monitoring session async function saveSession(session) { const db = await getDatabase(); await db.run(` INSERT OR REPLACE INTO monitoring_sessions (id, session_id, start_time, end_time, status, checks_performed, alerts_sent, last_check, config, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) `, session.sessionId, session.sessionId, session.startTime, session.endTime || null, session.status, session.checksPerformed, session.alertsSent, session.lastCheck || null, JSON.stringify(session.config)); } // Calculate current spending for budget period function calculateCurrentSpending(usageRecords, budgetType, budgetStart) { const now = new Date(); let startDate; if (budgetStart) { startDate = budgetStart; } else { switch (budgetType) { case 'daily': startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()); break; case 'weekly': const dayOfWeek = now.getDay(); startDate = new Date(now.getTime() - dayOfWeek * 24 * 60 * 60 * 1000); startDate.setHours(0, 0, 0, 0); break; case 'monthly': startDate = new Date(now.getFullYear(), now.getMonth(), 1); break; case 'total': startDate = new Date(0); // Beginning of time break; default: startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()); } } return usageRecords .filter(u => new Date(u.timestamp) >= startDate && new Date(u.timestamp) <= now) .reduce((total, u) => total + (u.cost || 0), 0); } // Detect usage spikes function detectUsageSpike(usageRecords, spikeThreshold) { if (usageRecords.length < 10) return false; // Need enough data const recent = usageRecords.slice(-5); // Last 5 entries const historical = usageRecords.slice(-50, -5); // Previous 45 entries const recentAvg = recent.reduce((sum, u) => sum + (u.cost || 0), 0) / recent.length; const historicalAvg = historical.reduce((sum, u) => sum + (u.cost || 0), 0) / historical.length; return recentAvg > historicalAvg * (1 + spikeThreshold); } // Check single budget async function checkBudget(budget) { const db = await getDatabase(); // Get usage records from unified database const usageRecords = await db.all('SELECT cost, timestamp FROM usage_records WHERE action_type = ? ORDER BY timestamp DESC', 'conversation'); const currentSpend = calculateCurrentSpending(usageRecords, budget.period_type); const percentage = (currentSpend / budget.limit_amount) * 100; let alertType = null; if (percentage >= 100) { alertType = 'exceeded'; } else if (percentage >= 95) { alertType = 'critical'; } else if (percentage >= 80) { alertType = 'warning'; } if (alertType) { console.warn(`Budget ${alertType}: ${percentage.toFixed(1)}% of ${budget.period_type} budget used`); console.warn(`Current spend: $${currentSpend.toFixed(4)}, Limit: $${budget.limit_amount}`); } } // Check for usage spikes async function checkUsageSpikes(config) { if (!config.spikeDetection) return; const db = await getDatabase(); const usageRecords = await db.all('SELECT cost, timestamp FROM usage_records ORDER BY timestamp DESC LIMIT 50'); const isSpike = detectUsageSpike(usageRecords, config.spikeThreshold); if (isSpike) { const recentUsage = usageRecords.slice(0, 5); const totalRecent = recentUsage.reduce((sum, u) => sum + (u.cost || 0), 0); console.warn('Usage spike detected: Recent spending significantly higher than average'); console.warn(`Recent spending: $${totalRecent.toFixed(4)}, Entries analyzed: ${recentUsage.length}`); } } // Perform monitoring check async function performMonitoringCheck(session) { try { const db = await getDatabase(); // Check active budgets const budgets = await db.all('SELECT * FROM budget_limits WHERE is_active = 1'); for (const budget of budgets) { await checkBudget(budget); } // Check for usage spikes await checkUsageSpikes(session.config); // Update session session.checksPerformed++; session.lastCheck = new Date().toISOString(); await saveSession(session); // Update statistics await db.run('UPDATE monitor_statistics SET total_checks = total_checks + 1, last_updated = CURRENT_TIMESTAMP WHERE id = ?', 'global'); } catch (error) { console.error('Error during monitoring check:', error); } } // Start monitoring session export async function startMonitoringSession(config) { try { await initializeMonitoringTables(); // Stop existing session if any const currentSession = await getCurrentSession(); if (currentSession) { await stopMonitoringSession(); } // Get merged configuration const baseConfig = await getMonitoringConfig(); const sessionConfig = { ...baseConfig, ...config }; // Create new session const sessionId = `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const newSession = { sessionId, startTime: new Date().toISOString(), status: 'active', checksPerformed: 0, alertsSent: 0, config: sessionConfig }; await saveSession(newSession); // Update statistics const db = await getDatabase(); await db.run('UPDATE monitor_statistics SET total_sessions = total_sessions + 1, last_updated = CURRENT_TIMESTAMP WHERE id = ?', 'global'); return { success: true, sessionId, message: `Real-time monitoring started (checking every ${sessionConfig.interval}s)` }; } catch (error) { return { success: false, sessionId: '', message: `Failed to start monitoring: ${error instanceof Error ? error.message : 'Unknown error'}` }; } } // Stop monitoring session export async function stopMonitoringSession() { try { const currentSession = await getCurrentSession(); if (!currentSession) { return { success: false, message: 'No active monitoring session found' }; } currentSession.status = 'stopped'; currentSession.endTime = new Date().toISOString(); const sessionDuration = new Date(currentSession.endTime).getTime() - new Date(currentSession.startTime).getTime(); const sessionSummary = { sessionId: currentSession.sessionId, duration: Math.round(sessionDuration / 1000), // seconds checksPerformed: currentSession.checksPerformed, alertsSent: currentSession.alertsSent, avgInterval: sessionDuration / currentSession.checksPerformed / 1000 // actual average interval }; await saveSession(currentSession); return { success: true, message: 'Monitoring session stopped', sessionSummary }; } catch (error) { return { success: false, message: `Failed to stop monitoring: ${error instanceof Error ? error.message : 'Unknown error'}` }; } } // Get monitoring status export async function getMonitoringStatus() { const currentSession = await getCurrentSession(); const db = await getDatabase(); const statistics = await db.get('SELECT * FROM monitor_statistics WHERE id = ?', 'global'); return { isActive: currentSession?.status === 'active', currentSession, statistics: statistics || { total_sessions: 0, total_checks: 0, total_alerts: 0, avg_check_interval: 60, last_updated: new Date().toISOString() } }; } // Configure monitoring settings export async function configureMonitoring(config) { try { await saveMonitoringConfig(config); return { success: true, message: 'Monitoring configuration updated' }; } catch (error) { return { success: false, message: `Failed to configure monitoring: ${error instanceof Error ? error.message : 'Unknown error'}` }; } } //# sourceMappingURL=usage-monitor.js.map