import path from 'node:path'; import fs from 'fs'; import { Config, ConfigModelProvider, EnvMap, UIConfigSections } from './types'; import ModelRegistry from '../models/registry'; import { hashObj } from '../serverUtils'; class ConfigManager { configPath: string = path.join( process.env.DATA_DIR || process.cwd(), '/data/config.json', ); configVersion = 1; currentConfig: Config = { version: this.configVersion, general: {}, modelProviders: [], }; uiConfigSections: UIConfigSections = { general: [], modelProviders: [], }; modelRegistry = new ModelRegistry(); constructor() { this.initialize(); } private initialize() { this.initializeConfig(); this.initializeFromEnv(); } private saveConfig() { fs.writeFileSync( this.configPath, JSON.stringify(this.currentConfig, null, 2), ); } private initializeConfig() { const exists = fs.existsSync(this.configPath); if (!exists) { fs.writeFileSync( this.configPath, JSON.stringify(this.currentConfig, null, 2), ); } else { try { this.currentConfig = JSON.parse( fs.readFileSync(this.configPath, 'utf-8'), ); } catch (err) { if (err instanceof SyntaxError) { console.error( `Error parsing config file at ${this.configPath}:`, err, ); console.log( 'Loading default config and overwriting the existing file.', ); fs.writeFileSync( this.configPath, JSON.stringify(this.currentConfig, null, 2), ); return; } else { console.log('Unknown error reading config file:', err); } } this.currentConfig = this.migrateConfig(this.currentConfig); } } private migrateConfig(config: Config): Config { /* TODO: Add migrations */ return config; } private initializeFromEnv() { const providerConfigSections = this.modelRegistry.getUIConfigSection(); this.uiConfigSections.modelProviders = providerConfigSections; const newProviders: ConfigModelProvider[] = []; providerConfigSections.forEach((provider) => { const newProvider: ConfigModelProvider & { required?: string[] } = { id: crypto.randomUUID(), name: `${provider.name} ${Math.floor(Math.random() * 1000)}`, type: provider.key, chatModels: [], embeddingModels: [], config: {}, required: [], hash: '', }; provider.fields.forEach((field) => { newProvider.config[field.key] = process.env[field.env!] || field.default || ''; /* Env var must exist for providers */ if (field.required) newProvider.required?.push(field.key); }); let configured = true; newProvider.required?.forEach((r) => { if (!newProvider.config[r]) { configured = false; } }); if (configured) { const hash = hashObj(newProvider.config); newProvider.hash = hash; delete newProvider.required; const exists = this.currentConfig.modelProviders.find( (p) => p.hash === hash, ); if (!exists) { newProviders.push(newProvider); } } }); this.currentConfig.modelProviders.push(...newProviders); this.saveConfig(); } public getConfig(key: string, defaultValue?: any): any { const nested = key.split('.'); let obj: any = this.currentConfig; for (let i = 0; i < nested.length; i++) { const part = nested[i]; if (obj == null) return defaultValue; obj = obj[part]; } return obj === undefined ? defaultValue : obj; } } const configManager = new ConfigManager(); export default configManager