codecrucible-synth
Version:
Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability
362 lines (318 loc) • 11.1 kB
text/typescript
/**
* Configuration Manager - Centralizes configuration management, validation, and defaults
* Extracted from UnifiedModelClient to provide focused configuration handling with validation
*/
import { logger } from '../logger.js';
import { UnifiedClientConfig } from '../client.js';
import { ProviderType, ProviderConfig } from '../providers/provider-repository.js';
import { StreamConfig } from '../streaming/streaming-manager.js';
export interface ConfigurationDefaults {
providers: ProviderConfig[];
executionMode: 'fast' | 'auto' | 'quality';
fallbackChain: ProviderType[];
performanceThresholds: {
fastModeMaxTokens: number;
timeoutMs: number;
maxConcurrentRequests: number;
};
security: {
enableSandbox: boolean;
maxInputLength: number;
allowedCommands: string[];
};
streaming: StreamConfig;
}
export interface ConfigurationValidation {
isValid: boolean;
errors: string[];
warnings: string[];
sanitized?: UnifiedClientConfig;
}
export interface IConfigurationManager {
/**
* Get default configuration with all necessary defaults
*/
getDefaultConfig(): UnifiedClientConfig;
/**
* Create default unified client configuration with overrides
*/
createDefaultUnifiedClientConfig(overrides?: Partial<UnifiedClientConfig>): UnifiedClientConfig;
/**
* Validate configuration for security and consistency
*/
validateConfiguration(config: Partial<UnifiedClientConfig>): ConfigurationValidation;
/**
* Merge configuration with defaults and validate
*/
mergeConfigurationWithDefaults(
config: Partial<UnifiedClientConfig>,
baseConfig?: UnifiedClientConfig
): UnifiedClientConfig;
/**
* Get environment-specific configuration overrides
*/
getEnvironmentOverrides(): Partial<UnifiedClientConfig>;
/**
* Sanitize configuration for security (remove sensitive data from logs)
*/
sanitizeConfigForLogging(config: UnifiedClientConfig): any;
}
export class ConfigurationManager implements IConfigurationManager {
private readonly defaults: ConfigurationDefaults;
constructor() {
this.defaults = {
providers: [
{ type: 'ollama', endpoint: 'http://localhost:11434' },
{ type: 'lm-studio', endpoint: 'http://localhost:1234' },
],
executionMode: 'auto',
fallbackChain: ['ollama', 'lm-studio', 'huggingface'],
performanceThresholds: {
fastModeMaxTokens: 1000,
timeoutMs: 180000, // 3 minutes default timeout
maxConcurrentRequests: 3, // Increased back to 3 for faster parallel processing
},
security: {
enableSandbox: true,
maxInputLength: 50000,
allowedCommands: ['npm', 'node', 'git'],
},
streaming: {
chunkSize: 50,
bufferSize: 1024,
enableBackpressure: true,
timeout: 30000,
encoding: 'utf8',
},
};
logger.debug('ConfigurationManager initialized with defaults');
}
/**
* Get default configuration with all necessary defaults
*/
getDefaultConfig(): UnifiedClientConfig {
const defaultConfig: UnifiedClientConfig = {
endpoint: 'http://localhost:11434',
providers: [...this.defaults.providers], // Clone array
executionMode: this.defaults.executionMode,
fallbackChain: [...this.defaults.fallbackChain], // Clone array
performanceThresholds: { ...this.defaults.performanceThresholds }, // Clone object
security: {
...this.defaults.security,
allowedCommands: [...this.defaults.security.allowedCommands], // Clone array
},
streaming: { ...this.defaults.streaming }, // Clone object
};
logger.debug('Generated default configuration', {
providersCount: defaultConfig.providers.length,
executionMode: defaultConfig.executionMode,
timeoutMs: defaultConfig.performanceThresholds.timeoutMs,
});
return defaultConfig;
}
/**
* Create default unified client configuration with overrides
*/
createDefaultUnifiedClientConfig(
overrides: Partial<UnifiedClientConfig> = {}
): UnifiedClientConfig {
const baseConfig = this.getDefaultConfig();
const mergedConfig = this.mergeConfigurationWithDefaults(overrides, baseConfig);
logger.debug('Created unified client configuration', {
hasOverrides: Object.keys(overrides).length > 0,
overrideKeys: Object.keys(overrides),
});
return mergedConfig;
}
/**
* Validate configuration for security and consistency
*/
validateConfiguration(config: Partial<UnifiedClientConfig>): ConfigurationValidation {
const errors: string[] = [];
const warnings: string[] = [];
// Validate providers
if (config.providers) {
if (!Array.isArray(config.providers) || config.providers.length === 0) {
errors.push('Providers must be a non-empty array');
} else {
config.providers.forEach((provider, index) => {
if (!provider.type) {
errors.push(`Provider at index ${index} missing type`);
}
if (!provider.endpoint) {
errors.push(`Provider at index ${index} missing endpoint`);
}
if (provider.endpoint && !this.isValidEndpoint(provider.endpoint)) {
errors.push(`Provider at index ${index} has invalid endpoint format`);
}
});
}
}
// Validate execution mode
if (config.executionMode && !['fast', 'auto', 'quality'].includes(config.executionMode)) {
errors.push('ExecutionMode must be one of: fast, auto, quality');
}
// Validate performance thresholds
if (config.performanceThresholds) {
const perf = config.performanceThresholds;
if (perf.timeoutMs && (perf.timeoutMs < 5000 || perf.timeoutMs > 600000)) {
warnings.push('TimeoutMs should be between 5 seconds and 10 minutes');
}
if (
perf.maxConcurrentRequests &&
(perf.maxConcurrentRequests < 1 || perf.maxConcurrentRequests > 10)
) {
warnings.push('MaxConcurrentRequests should be between 1 and 10');
}
}
// Validate security settings
if (config.security) {
const sec = config.security;
if (sec.maxInputLength && sec.maxInputLength > 100000) {
warnings.push('MaxInputLength over 100KB may cause performance issues');
}
if (sec.allowedCommands && sec.allowedCommands.includes('rm')) {
errors.push('Command "rm" is not allowed for security reasons');
}
}
// Validate streaming configuration
if (config.streaming) {
const stream = config.streaming;
if (stream.chunkSize && (stream.chunkSize < 1 || stream.chunkSize > 1000)) {
warnings.push('Streaming chunkSize should be between 1 and 1000');
}
if (stream.timeout && stream.timeout < 1000) {
warnings.push('Streaming timeout should be at least 1 second');
}
}
const isValid = errors.length === 0;
if (!isValid) {
logger.warn('Configuration validation failed', { errors, warnings });
} else if (warnings.length > 0) {
logger.info('Configuration validation passed with warnings', { warnings });
}
return {
isValid,
errors,
warnings,
sanitized: isValid ? (config as UnifiedClientConfig) : undefined,
};
}
/**
* Merge configuration with defaults and validate
*/
mergeConfigurationWithDefaults(
config: Partial<UnifiedClientConfig>,
baseConfig?: UnifiedClientConfig
): UnifiedClientConfig {
const base = baseConfig || this.getDefaultConfig();
// Deep merge configuration with defaults
const merged: UnifiedClientConfig = {
...base,
...config,
// Deep merge nested objects
performanceThresholds: {
...base.performanceThresholds,
...(config.performanceThresholds || {}),
},
security: {
...base.security,
...(config.security || {}),
// Preserve array references or merge them properly
allowedCommands: config.security?.allowedCommands || base.security.allowedCommands,
},
streaming: {
...base.streaming,
...(config.streaming || {}),
},
// Arrays should be replaced, not merged
providers: config.providers || base.providers,
fallbackChain: config.fallbackChain || base.fallbackChain,
};
// Validate merged configuration
const validation = this.validateConfiguration(merged);
if (!validation.isValid) {
logger.warn('Merged configuration is invalid, using base configuration', {
errors: validation.errors,
});
return base;
}
return merged;
}
/**
* Get environment-specific configuration overrides
*/
getEnvironmentOverrides(): Partial<UnifiedClientConfig> {
const overrides: Partial<UnifiedClientConfig> = {};
// Check for environment variables
if (process.env.AI_TIMEOUT_MS) {
const timeoutMs = parseInt(process.env.AI_TIMEOUT_MS, 10);
if (!isNaN(timeoutMs)) {
overrides.performanceThresholds = {
...this.defaults.performanceThresholds,
timeoutMs,
};
}
}
if (process.env.AI_MAX_CONCURRENT) {
const maxConcurrent = parseInt(process.env.AI_MAX_CONCURRENT, 10);
if (!isNaN(maxConcurrent)) {
overrides.performanceThresholds = {
...this.defaults.performanceThresholds,
...(overrides.performanceThresholds || {}),
maxConcurrentRequests: maxConcurrent,
};
}
}
if (process.env.AI_EXECUTION_MODE) {
const mode = process.env.AI_EXECUTION_MODE;
if (['fast', 'auto', 'quality'].includes(mode)) {
overrides.executionMode = mode as 'fast' | 'auto' | 'quality';
}
}
if (Object.keys(overrides).length > 0) {
logger.debug('Applied environment overrides', { overrides });
}
return overrides;
}
/**
* Sanitize configuration for security (remove sensitive data from logs)
*/
sanitizeConfigForLogging(config: UnifiedClientConfig): any {
const sanitized = { ...config };
// Remove or mask sensitive endpoint information
if (sanitized.providers) {
sanitized.providers = sanitized.providers.map(provider => ({
...provider,
endpoint: provider.endpoint ? this.maskEndpoint(provider.endpoint) : '[NO_ENDPOINT]',
}));
}
if (sanitized.endpoint) {
sanitized.endpoint = this.maskEndpoint(sanitized.endpoint);
}
return sanitized;
}
/**
* Validate endpoint format
*/
private isValidEndpoint(endpoint: string): boolean {
try {
const url = new URL(endpoint);
return ['http:', 'https:'].includes(url.protocol);
} catch {
return false;
}
}
/**
* Mask sensitive parts of endpoint for logging
*/
private maskEndpoint(endpoint: string): string {
try {
const url = new URL(endpoint);
const port = url.port || (url.protocol === 'https:' ? '443' : '80');
return `${url.protocol}//${url.hostname}:${port}${url.pathname}`;
} catch {
return '[INVALID_ENDPOINT]';
}
}
}