@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
JavaScript
/**
* 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