import { Journal, KeyCipher, KeyResolver, MediaStorage, ModelCatalog, ProviderRegistry, ProvidersStore, createNordRouterProviders, loadConfig, LocalWhisperProvider, createLogger, type NexusConfig, } from '@nexusai/core'; const log = createLogger('context'); export interface AppContext { config: NexusConfig; registry: ProviderRegistry; storage: MediaStorage; journal: Journal; providersStore: ProvidersStore; /** Model catalogs keyed by provider name (for sync + fuzzy resolution). */ catalogs: Map; /** Effective default provider name after resolving DB/env. */ defaultProvider: string; } /** Built-in providers always considered, even without a DB row. */ const BUILTINS = [{ name: 'nordrouter', defaultBaseUrl: 'https://nordrouter.com' }]; /** * Wire up providers, storage, journal and the encrypted providers store. * API keys resolve with ENV > DB precedence (see KeyResolver). */ export function buildContext(): AppContext { const config = loadConfig(); const storage = new MediaStorage(config.mediaDir); const journal = new Journal(config.dbPath); const registry = new ProviderRegistry(); const cipher = new KeyCipher(config.masterKey); const providersStore = new ProvidersStore(config.providersDbPath, cipher); const resolver = new KeyResolver(config, providersStore); if (!cipher.enabled) { log.warn('NEXUS_MASTER_KEY not set — DB-stored API keys are unavailable; using env keys only'); } const resolved = resolver.resolveAll(BUILTINS); const defaultProvider = resolver.resolveDefault(resolved); const catalogs = new Map(); for (const p of resolved) { if (!p.enabled) { log.info(`provider "${p.name}" disabled — skipped`); continue; } if (p.type === 'nordrouter') { if (!p.apiKey) { log.warn(`provider "${p.name}" has no API key (env or DB) — tools will error when used`); } try { const nr = createNordRouterProviders({ config: { apiKey: p.apiKey, baseUrl: p.baseUrl }, storage, save: true, }); // Register under the provider's own name so multiple nordrouter-type // providers with different keys can coexist. Object.defineProperty(nr.media, 'name', { value: p.name }); Object.defineProperty(nr.search, 'name', { value: p.name }); registry.registerMedia(nr.media); registry.registerSearch(nr.search); // Model catalog with periodic background sync + fuzzy resolution. if (p.apiKey) { const catalog = new ModelCatalog(nr.media); catalog.startAutoSync(); catalogs.set(p.name, catalog); } log.info(`registered provider "${p.name}" (media + search, key: ${p.keySource})`); } catch (err) { log.warn(`failed to register "${p.name}"`, err instanceof Error ? err.message : String(err)); } } else { log.warn(`provider type "${p.type}" (${p.name}) not implemented yet — skipped`); } } registry.registerStt(new LocalWhisperProvider(config.stt)); log.info('registered local STT provider'); log.info(`default provider: ${defaultProvider}`); return { config, registry, storage, journal, providersStore, catalogs, defaultProvider }; }