@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
188 lines • 8.05 kB
JavaScript
/**
* Auto-Update System for Model Registry
* Handles daily background updates with intelligent timing and user feedback
*/
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { openRouterClient } from './openrouter-client.js';
export class AutoUpdater {
configDir = path.join(os.homedir(), '.hive-ai');
statusFile = path.join(this.configDir, 'update-status.json');
registryFile = path.join(process.cwd(), 'src', 'data', 'openrouter-registry.json');
userRegistryFile = path.join(this.configDir, 'openrouter-registry.json');
constructor() {
this.ensureConfigDir();
}
ensureConfigDir() {
if (!fs.existsSync(this.configDir)) {
fs.mkdirSync(this.configDir, { recursive: true });
}
}
getUpdateStatus() {
try {
if (fs.existsSync(this.statusFile)) {
const data = fs.readFileSync(this.statusFile, 'utf8');
return JSON.parse(data);
}
}
catch (error) {
// If file is corrupted, we'll create a new one
}
// Default status for first run
const now = new Date();
return {
lastCheck: new Date(0).toISOString(), // Force first check
lastUpdate: new Date(0).toISOString(),
version: '1.0.0',
providerCount: 0,
modelCount: 0,
nextCheck: new Date(now.getTime() + 24 * 60 * 60 * 1000).toISOString()
};
}
saveUpdateStatus(status) {
try {
fs.writeFileSync(this.statusFile, JSON.stringify(status, null, 2));
}
catch (error) {
console.error('Warning: Could not save update status:', error instanceof Error ? error.message : 'Unknown error');
}
}
shouldCheckForUpdate() {
const status = this.getUpdateStatus();
const now = new Date();
const nextCheck = new Date(status.nextCheck);
// Check if it's time for the next scheduled check
return now >= nextCheck;
}
async getCurrentRegistryStats() {
try {
// Check user registry first, then source registry
const registryPath = fs.existsSync(this.userRegistryFile) ? this.userRegistryFile : this.registryFile;
if (!fs.existsSync(registryPath)) {
return null;
}
const data = fs.readFileSync(registryPath, 'utf8');
const registry = JSON.parse(data);
return {
providers: registry.totalProviders || 0,
models: registry.totalModels || 0
};
}
catch (error) {
return null;
}
}
async checkForUpdatesAsync() {
const startTime = Date.now();
try {
// Get current stats
const currentStats = await this.getCurrentRegistryStats();
// Fetch latest data from OpenRouter (with timeout)
const providers = await Promise.race([
openRouterClient.getProviderSummary(),
new Promise((_, reject) => setTimeout(() => reject(new Error('Update timeout')), 10000) // 10 second timeout
)
]);
const newStats = {
providers: providers.length,
models: providers.reduce((sum, p) => sum + p.modelCount, 0)
};
// Determine if we should update
const shouldUpdate = !currentStats ||
newStats.providers !== currentStats.providers ||
newStats.models !== currentStats.models;
if (shouldUpdate) {
// Update the user registry
const registry = {
providers,
lastUpdated: new Date().toISOString(),
source: 'openrouter',
totalProviders: newStats.providers,
totalModels: newStats.models,
autoUpdated: true
};
fs.writeFileSync(this.userRegistryFile, JSON.stringify(registry, null, 2));
// Update build/dist versions
try {
const buildPath = path.join(process.cwd(), 'build', 'data', 'openrouter-registry.json');
const distPath = path.join(process.cwd(), 'dist', 'data', 'openrouter-registry.json');
for (const targetPath of [buildPath, distPath]) {
const targetDir = path.dirname(targetPath);
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true });
}
fs.writeFileSync(targetPath, JSON.stringify(registry, null, 2));
}
}
catch (error) {
// Non-critical if build/dist update fails
}
}
// Update status
const now = new Date();
const currentStatus = this.getUpdateStatus();
const status = {
lastCheck: now.toISOString(),
lastUpdate: shouldUpdate ? now.toISOString() : (currentStats ? now.toISOString() : currentStatus.lastUpdate),
version: '1.0.0',
providerCount: newStats.providers,
modelCount: newStats.models,
nextCheck: new Date(now.getTime() + 24 * 60 * 60 * 1000).toISOString() // Next day
};
this.saveUpdateStatus(status);
const timeTaken = Date.now() - startTime;
return {
updated: shouldUpdate,
newModels: shouldUpdate ? (newStats.models - (currentStats?.models || 0)) : 0,
newProviders: shouldUpdate ? (newStats.providers - (currentStats?.providers || 0)) : 0,
timeTaken,
summary: shouldUpdate
? `Updated registry: ${newStats.providers} providers, ${newStats.models} models`
: `Registry current: ${newStats.providers} providers, ${newStats.models} models`
};
}
catch (error) {
// On error, schedule next check for 4 hours later instead of 24 hours
const now = new Date();
const status = this.getUpdateStatus();
status.lastCheck = now.toISOString();
status.nextCheck = new Date(now.getTime() + 4 * 60 * 60 * 1000).toISOString(); // 4 hours
this.saveUpdateStatus(status);
return {
updated: false,
newModels: 0,
newProviders: 0,
timeTaken: Date.now() - startTime,
summary: `Update check failed: ${error instanceof Error ? error.message : 'Unknown error'}`
};
}
}
async performAutoUpdateIfNeeded() {
if (!this.shouldCheckForUpdate()) {
return null; // No update needed
}
return await this.checkForUpdatesAsync();
}
getLastUpdateInfo() {
const status = this.getUpdateStatus();
const lastUpdate = new Date(status.lastUpdate);
const now = new Date();
if (lastUpdate.getTime() === 0) {
return 'Model registry: Never updated';
}
const hoursAgo = Math.floor((now.getTime() - lastUpdate.getTime()) / (1000 * 60 * 60));
if (hoursAgo < 1) {
return `Model registry: Updated recently (${status.providerCount} providers, ${status.modelCount} models)`;
}
else if (hoursAgo < 24) {
return `Model registry: Updated ${hoursAgo}h ago (${status.providerCount} providers, ${status.modelCount} models)`;
}
else {
const daysAgo = Math.floor(hoursAgo / 24);
return `Model registry: Updated ${daysAgo}d ago (${status.providerCount} providers, ${status.modelCount} models)`;
}
}
}
export const autoUpdater = new AutoUpdater();
//# sourceMappingURL=auto-updater.js.map