@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
353 lines • 13.3 kB
JavaScript
/**
* Real-time usage monitoring service
* Continuously monitors spending and triggers alerts when thresholds are exceeded
*/
import { z } from 'zod';
import { readFile, writeFile } from 'fs/promises';
import { join } from 'path';
import { homedir } from 'os';
// Real-time alerts feature removed - simplified monitoring only
// 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')
});
const HIVE_DIR = join(homedir(), '.hive-ai');
const MONITOR_DB_PATH = join(HIVE_DIR, 'usage-monitor.json');
const BUDGET_DB_PATH = join(HIVE_DIR, 'budget.json');
const COST_DB_PATH = join(HIVE_DIR, 'cost-tracking.json');
// Load monitor database
async function loadMonitorDatabase() {
try {
const data = await readFile(MONITOR_DB_PATH, 'utf-8');
return JSON.parse(data);
}
catch {
return {
config: {
enabled: true,
interval: 60,
warningThreshold: 0.8,
criticalThreshold: 0.95,
spikeDetection: true,
spikeThreshold: 0.5,
autoBreak: false,
gracePeriod: 300
},
currentSession: null,
sessions: [],
statistics: {
totalSessions: 0,
totalChecks: 0,
totalAlerts: 0,
avgCheckInterval: 60,
lastUpdated: new Date().toISOString()
}
};
}
}
// Save monitor database
async function saveMonitorDatabase(db) {
db.statistics.lastUpdated = new Date().toISOString();
await writeFile(MONITOR_DB_PATH, JSON.stringify(db, null, 2));
}
// Load budget data
async function loadBudgetData() {
try {
const data = await readFile(BUDGET_DB_PATH, 'utf-8');
return JSON.parse(data);
}
catch {
return { budgets: [] };
}
}
// Load cost data
async function loadCostData() {
try {
const data = await readFile(COST_DB_PATH, 'utf-8');
return JSON.parse(data);
}
catch {
return { usage: [] };
}
}
// Calculate current spending for budget period
function calculateCurrentSpending(usage, 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 usage
.filter(u => new Date(u.timestamp) >= startDate && new Date(u.timestamp) <= now)
.reduce((total, u) => total + u.amount, 0);
}
// Detect usage spikes
function detectUsageSpike(usage, spikeThreshold) {
if (usage.length < 10)
return false; // Need enough data
const recent = usage.slice(-5); // Last 5 entries
const historical = usage.slice(-50, -5); // Previous 45 entries
const recentAvg = recent.reduce((sum, u) => sum + u.amount, 0) / recent.length;
const historicalAvg = historical.reduce((sum, u) => sum + u.amount, 0) / historical.length;
return recentAvg > historicalAvg * (1 + spikeThreshold);
}
// Check single budget
async function checkBudget(budget, costData) {
const currentSpend = calculateCurrentSpending(costData.usage, budget.type);
const percentage = (currentSpend / budget.limit) * 100;
let alertType = null;
if (percentage >= 100) {
alertType = 'exceeded';
}
else if (percentage >= budget.criticalThreshold * 100) {
alertType = 'critical';
}
else if (percentage >= budget.warningThreshold * 100) {
alertType = 'warning';
}
if (alertType) {
// Real-time alerts feature removed - using console logging
console.warn(`Budget ${alertType}: ${percentage.toFixed(1)}% of ${budget.type} budget used`);
console.warn(`Current spend: ${currentSpend}, Limit: ${budget.limit}`);
}
}
// Check for usage spikes
async function checkUsageSpikes(config, costData) {
if (!config.spikeDetection)
return;
const isSpike = detectUsageSpike(costData.usage, config.spikeThreshold);
if (isSpike) {
const recentUsage = costData.usage.slice(-5);
const totalRecent = recentUsage.reduce((sum, u) => sum + u.amount, 0);
// Real-time alerts feature removed - using console logging
console.warn('Usage spike detected: Recent spending significantly higher than average');
console.warn(`Recent spending: ${totalRecent}, Entries analyzed: ${recentUsage.length}`);
}
}
// Perform monitoring check
async function performMonitoringCheck(session) {
try {
const budgetData = await loadBudgetData();
const costData = await loadCostData();
// Check each enabled budget
for (const budget of budgetData.budgets.filter(b => b.enabled)) {
await checkBudget(budget, costData);
}
// Check for usage spikes
await checkUsageSpikes(session.config, costData);
// Update session
session.checksPerformed++;
session.lastCheck = new Date().toISOString();
}
catch (error) {
console.error('Error during monitoring check:', error);
}
}
// Start monitoring session
export async function startMonitoringSession(config) {
try {
const monitorDb = await loadMonitorDatabase();
// Stop existing session if any
if (monitorDb.currentSession && monitorDb.currentSession.status === 'active') {
await stopMonitoringSession();
}
// Create new session
const sessionId = `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const sessionConfig = { ...monitorDb.config, ...config };
const newSession = {
sessionId,
startTime: new Date().toISOString(),
status: 'active',
checksPerformed: 0,
alertsSent: 0,
config: sessionConfig
};
monitorDb.currentSession = newSession;
monitorDb.sessions.push(newSession);
monitorDb.statistics.totalSessions++;
await saveMonitorDatabase(monitorDb);
// Start monitoring loop
const monitoringInterval = setInterval(async () => {
try {
const currentDb = await loadMonitorDatabase();
if (!currentDb.currentSession || currentDb.currentSession.status !== 'active') {
clearInterval(monitoringInterval);
return;
}
await performMonitoringCheck(currentDb.currentSession);
currentDb.statistics.totalChecks++;
await saveMonitorDatabase(currentDb);
}
catch (error) {
console.error('Error in monitoring loop:', error);
}
}, sessionConfig.interval * 1000);
// Store interval reference (in a real implementation, you'd want better process management)
process.on('SIGINT', () => {
clearInterval(monitoringInterval);
stopMonitoringSession();
});
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 monitorDb = await loadMonitorDatabase();
if (!monitorDb.currentSession) {
return {
success: false,
message: 'No active monitoring session found'
};
}
const session = monitorDb.currentSession;
session.status = 'stopped';
session.endTime = new Date().toISOString();
const sessionDuration = new Date(session.endTime).getTime() - new Date(session.startTime).getTime();
const sessionSummary = {
sessionId: session.sessionId,
duration: Math.round(sessionDuration / 1000), // seconds
checksPerformed: session.checksPerformed,
alertsSent: session.alertsSent,
avgInterval: sessionDuration / session.checksPerformed / 1000 // actual average interval
};
monitorDb.currentSession = null;
await saveMonitorDatabase(monitorDb);
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'}`
};
}
}
// Pause monitoring session
export async function pauseMonitoringSession() {
try {
const monitorDb = await loadMonitorDatabase();
if (!monitorDb.currentSession || monitorDb.currentSession.status !== 'active') {
return {
success: false,
message: 'No active monitoring session to pause'
};
}
monitorDb.currentSession.status = 'paused';
await saveMonitorDatabase(monitorDb);
return {
success: true,
message: 'Monitoring session paused'
};
}
catch (error) {
return {
success: false,
message: `Failed to pause monitoring: ${error instanceof Error ? error.message : 'Unknown error'}`
};
}
}
// Resume monitoring session
export async function resumeMonitoringSession() {
try {
const monitorDb = await loadMonitorDatabase();
if (!monitorDb.currentSession || monitorDb.currentSession.status !== 'paused') {
return {
success: false,
message: 'No paused monitoring session to resume'
};
}
monitorDb.currentSession.status = 'active';
await saveMonitorDatabase(monitorDb);
// Restart monitoring would need to be handled by the calling code
return {
success: true,
message: 'Monitoring session resumed'
};
}
catch (error) {
return {
success: false,
message: `Failed to resume monitoring: ${error instanceof Error ? error.message : 'Unknown error'}`
};
}
}
// Get monitoring status
export async function getMonitoringStatus() {
const monitorDb = await loadMonitorDatabase();
return {
isActive: monitorDb.currentSession?.status === 'active',
currentSession: monitorDb.currentSession,
statistics: monitorDb.statistics
};
}
// Configure monitoring settings
export async function configureMonitoring(config) {
try {
const monitorDb = await loadMonitorDatabase();
monitorDb.config = { ...monitorDb.config, ...config };
await saveMonitorDatabase(monitorDb);
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-legacy.js.map