UNPKG

@codai/memorai-core

Version:

Simplified advanced memory engine - no tiers, just powerful semantic search with persistence

243 lines (242 loc) 9.57 kB
import { z } from 'zod'; import { logger } from '../utils/logger.js'; /** * Create default configuration with current environment variables * This is called at instantiation time, not module load time */ function createDefaultConfig() { return { vector_db: { url: process.env.QDRANT_URL ?? 'http://localhost:6333', api_key: process.env.QDRANT_API_KEY, collection: 'memories', dimension: 1536, }, redis: { url: process.env.REDIS_URL ?? 'redis://localhost:6379', password: process.env.REDIS_PASSWORD, db: 0, }, openai: { provider: process.env.MEMORAI_OPENAI_PROVIDER || 'openai', api_key: process.env.MEMORAI_OPENAI_API_KEY || process.env.OPENAI_API_KEY || process.env.AZURE_OPENAI_API_KEY, model: process.env.MEMORAI_MODEL || 'gpt-4', ...(process.env.OPENAI_BASE_URL && { base_url: process.env.OPENAI_BASE_URL, }), ...(process.env.AZURE_OPENAI_ENDPOINT && { azure_endpoint: process.env.AZURE_OPENAI_ENDPOINT, }), ...(process.env.AZURE_OPENAI_DEPLOYMENT_NAME && { azure_deployment: process.env.AZURE_OPENAI_DEPLOYMENT_NAME, }), ...(process.env.AZURE_OPENAI_API_VERSION && { azure_api_version: process.env.AZURE_OPENAI_API_VERSION, }), }, embedding: { provider: process.env.MEMORAI_EMBEDDING_PROVIDER || 'openai', model: process.env.MEMORAI_EMBEDDING_MODEL || 'text-embedding-3-small', api_key: process.env.OPENAI_API_KEY || process.env.AZURE_OPENAI_API_KEY, ...(process.env.OPENAI_ENDPOINT && { endpoint: process.env.OPENAI_ENDPOINT, }), ...(process.env.AZURE_OPENAI_ENDPOINT && { azure_endpoint: process.env.AZURE_OPENAI_ENDPOINT, }), ...(process.env.AZURE_OPENAI_DEPLOYMENT_NAME && { azure_deployment: process.env.AZURE_OPENAI_DEPLOYMENT_NAME, }), ...(process.env.AZURE_OPENAI_API_VERSION && { azure_api_version: process.env.AZURE_OPENAI_API_VERSION, }), ...(process.env.MEMORAI_EMBEDDING_DIMENSIONS && { dimensions: parseInt(process.env.MEMORAI_EMBEDDING_DIMENSIONS), }), }, performance: { max_query_time_ms: 100, cache_ttl_seconds: 300, batch_size: 100, }, security: { encryption_key: process.env.MEMORY_ENCRYPTION_KEY ?? '', tenant_isolation: true, audit_logs: true, }, }; } export class MemoryConfigManager { constructor(overrides = {}) { // Get fresh default config with current environment variables const defaultConfig = createDefaultConfig(); // Handle null overrides gracefully const safeOverrides = overrides || {}; this.config = this.mergeConfig(defaultConfig, safeOverrides); this.validate(); } get() { // Deep clone to ensure immutability return { vector_db: { ...this.config.vector_db }, redis: { ...this.config.redis }, embedding: { ...this.config.embedding }, performance: { ...this.config.performance }, security: { ...this.config.security }, }; } getVectorDB() { return { ...this.config.vector_db }; } getRedis() { return { ...this.config.redis }; } getEmbedding() { return { ...this.config.embedding }; } getPerformance() { return { ...this.config.performance }; } getSecurity() { return { ...this.config.security }; } mergeConfig(defaultConfig, overrides) { // Handle undefined or null overrides gracefully const safeOverrides = overrides || {}; return { vector_db: { ...defaultConfig.vector_db, ...(safeOverrides.vector_db || {}), }, redis: { ...defaultConfig.redis, ...(safeOverrides.redis || {}) }, embedding: { ...defaultConfig.embedding, ...(safeOverrides.embedding || {}), }, performance: { ...defaultConfig.performance, ...(safeOverrides.performance || {}), }, security: { ...defaultConfig.security, ...(safeOverrides.security || {}), }, }; } validate() { // Validate encryption key if (!this.config.security.encryption_key || this.config.security.encryption_key.length < 32) { throw new Error('Invalid memory configuration: Encryption key must be at least 32 characters long'); } // Validate URLs if (this.config.vector_db.url) { try { const url = new URL(this.config.vector_db.url); if (!['http:', 'https:'].includes(url.protocol)) { throw new Error('Invalid memory configuration: Vector database URL must use http or https protocol'); } if (!url.hostname) { throw new Error('Invalid memory configuration: Vector database URL must have a valid hostname'); } } catch (error) { if (error instanceof TypeError) { throw new Error('Invalid memory configuration: Vector database URL format is invalid'); } throw error; } } if (this.config.redis.url) { try { const url = new URL(this.config.redis.url); if (!['redis:', 'rediss:', 'http:', 'https:'].includes(url.protocol)) { throw new Error('Invalid memory configuration: Redis URL must use redis, rediss, http, or https protocol'); } if (!url.hostname) { throw new Error('Invalid memory configuration: Redis URL must have a valid hostname'); } } catch (error) { if (error instanceof TypeError) { throw new Error('Invalid memory configuration: Redis URL format is invalid'); } throw error; } } // Validate performance constraints if (this.config.performance.max_query_time_ms <= 0) { throw new Error('Invalid memory configuration: max_query_time_ms must be greater than 0'); } if (this.config.performance.cache_ttl_seconds < 0) { throw new Error('Invalid memory configuration: cache_ttl_seconds must be non-negative'); } if (this.config.performance.batch_size <= 0) { throw new Error('Invalid memory configuration: batch_size must be greater than 0'); } // Validate embedding configuration const validProviders = ['openai', 'azure', 'local']; if (!validProviders.includes(this.config.embedding.provider)) { throw new Error(`Invalid memory configuration: Embedding provider must be one of: ${validProviders.join(', ')}`); } if (!this.config.embedding.model || this.config.embedding.model.trim().length === 0) { throw new Error('Invalid memory configuration: Embedding model cannot be empty'); } if (this.config.embedding.api_key && this.config.embedding.api_key.length < 10) { throw new Error('Invalid memory configuration: API key is too short'); } // Additional Zod validation for any other edge cases try { // Import the schema from types and validate asynchronously for warnings only import('../types/index.js') .then(({ MemoryConfigSchema }) => { MemoryConfigSchema.parse(this.config); }) .catch((error) => { if (error instanceof z.ZodError) { // Don't throw here as this is async - just log warnings for additional validation logger.warn(`Memory configuration validation warning: ${JSON.stringify(error.errors, null, 2)}`); } }); } catch (error) { if (error instanceof z.ZodError) { logger.warn(`Memory configuration validation warning: ${JSON.stringify(error.errors, null, 2)}`); } } } /** * Get feature capabilities based on current configuration */ getFeatures() { return { embedding: !!this.config.embedding.api_key, similarity: true, // Always supported persistence: !!this.config.vector_db.url, scalability: !!this.config.redis.url, }; } } /** * Static factory class for MemoryConfig - provides convenience methods */ export class MemoryConfigFactory { /** * Create config from environment variables */ static fromEnv() { return new MemoryConfigManager(); } /** * Create config with custom overrides */ static create(overrides = {}) { return new MemoryConfigManager(overrides); } } // Alias for backward compatibility with tests export const MemoryConfig = MemoryConfigFactory;