UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, and professional CLI. Built-in tools operational, 58+ external MCP servers discoverable. Connect to filesystem, GitHub, database operations, and more. Build, test, and

840 lines (839 loc) 32.5 kB
/** * Model Configuration System * * Replaces hardcoded model-specific logic with configurable, runtime-updateable configurations. * This addresses GitHub Copilot review comment about making model-specific logic configuration-based. */ import { logger } from "../utils/logger.js"; import fs from "fs"; import path from "path"; /** * Model name constants - extracted from hardcoded values for better maintainability * These constants can be overridden by environment variables */ export const MODEL_NAMES = { // Google AI Models GOOGLE_AI: { FAST: "gemini-2.5-flash", BALANCED: "gemini-2.5-pro", QUALITY: "gemini-2.5-pro", }, // Google Vertex Models GOOGLE_VERTEX: { FAST: "gemini-2.5-flash", BALANCED: "gemini-2.5-pro", QUALITY: "gemini-2.5-pro", }, // OpenAI Models OPENAI: { FAST: "gpt-4o-mini", BALANCED: "gpt-4o", QUALITY: "gpt-4o", }, // Anthropic Models ANTHROPIC: { FAST: "claude-3-haiku-20240307", BALANCED: "claude-3-sonnet-20240229", QUALITY: "claude-3-5-sonnet-20241022", }, // Vertex AI Models (legacy alias) VERTEX: { FAST: "gemini-2.5-flash", BALANCED: "gemini-2.5-pro", QUALITY: "gemini-2.5-pro", }, // AWS Bedrock Models BEDROCK: { FAST: "anthropic.claude-3-haiku-20240307-v1:0", BALANCED: "anthropic.claude-3-sonnet-20240229-v1:0", QUALITY: "anthropic.claude-3-opus-20240229-v1:0", }, // Azure OpenAI Models AZURE: { FAST: "gpt-4o-mini", BALANCED: "gpt-4o", QUALITY: "gpt-4o", }, // Ollama Models OLLAMA: { FAST: "llama3.2:latest", BALANCED: "llama3.1:8b", QUALITY: "llama3.1:70b", }, // HuggingFace Models HUGGINGFACE: { FAST: "microsoft/DialoGPT-medium", BALANCED: "microsoft/DialoGPT-large", QUALITY: "meta-llama/Llama-2-7b-chat-hf", }, // Mistral Models MISTRAL: { FAST: "mistral-small-latest", BALANCED: "mistral-medium-latest", QUALITY: "mistral-large-latest", }, }; /** * Model configuration manager */ export class ModelConfigurationManager { static instance; configurations = new Map(); configSource = "default"; lastUpdated = Date.now(); constructor() { this.loadDefaultConfigurations(); } static getInstance() { if (!ModelConfigurationManager.instance) { ModelConfigurationManager.instance = new ModelConfigurationManager(); } return ModelConfigurationManager.instance; } /** * Create Google AI provider configuration */ createGoogleAIConfig() { return { provider: "google-ai", models: { fast: this.getConfigValue("GOOGLE_AI_FAST_MODEL", MODEL_NAMES.GOOGLE_AI.FAST), balanced: this.getConfigValue("GOOGLE_AI_BALANCED_MODEL", MODEL_NAMES.GOOGLE_AI.BALANCED), quality: this.getConfigValue("GOOGLE_AI_QUALITY_MODEL", MODEL_NAMES.GOOGLE_AI.QUALITY), }, defaultCost: { input: this.parseFloat(process.env.GOOGLE_AI_DEFAULT_INPUT_COST, 0.000075), output: this.parseFloat(process.env.GOOGLE_AI_DEFAULT_OUTPUT_COST, 0.0003), }, requiredEnvVars: ["GOOGLE_AI_API_KEY"], performance: { speed: this.parseInt(process.env.GOOGLE_AI_SPEED_RATING, 3), quality: this.parseInt(process.env.GOOGLE_AI_QUALITY_RATING, 3), cost: this.parseInt(process.env.GOOGLE_AI_COST_RATING, 3), }, modelBehavior: { maxTokensIssues: this.getConfigArray("GOOGLE_AI_MAX_TOKENS_ISSUES", [ MODEL_NAMES.GOOGLE_AI.FAST, MODEL_NAMES.GOOGLE_AI.BALANCED, ]), }, }; } /** * Create Google Vertex AI provider configuration */ createVertexConfig() { return { provider: "google-vertex", models: { fast: this.getConfigValue("GOOGLE_VERTEX_FAST_MODEL", MODEL_NAMES.GOOGLE_VERTEX.FAST), balanced: this.getConfigValue("GOOGLE_VERTEX_BALANCED_MODEL", MODEL_NAMES.GOOGLE_VERTEX.BALANCED), quality: this.getConfigValue("GOOGLE_VERTEX_QUALITY_MODEL", MODEL_NAMES.GOOGLE_VERTEX.QUALITY), }, defaultCost: { input: this.parseFloat(process.env.GOOGLE_VERTEX_DEFAULT_INPUT_COST, 0.000075), output: this.parseFloat(process.env.GOOGLE_VERTEX_DEFAULT_OUTPUT_COST, 0.0003), }, requiredEnvVars: ["GOOGLE_VERTEX_PROJECT_ID", "GOOGLE_VERTEX_LOCATION"], performance: { speed: this.parseInt(process.env.GOOGLE_VERTEX_SPEED_RATING, 3), quality: this.parseInt(process.env.GOOGLE_VERTEX_QUALITY_RATING, 3), cost: this.parseInt(process.env.GOOGLE_VERTEX_COST_RATING, 3), }, modelBehavior: { maxTokensIssues: this.getConfigArray("GOOGLE_VERTEX_MAX_TOKENS_ISSUES", [MODEL_NAMES.GOOGLE_VERTEX.FAST, MODEL_NAMES.GOOGLE_VERTEX.BALANCED]), }, }; } /** * Create OpenAI provider configuration */ createOpenAIConfig() { return { provider: "openai", models: { fast: this.getConfigValue("OPENAI_FAST_MODEL", MODEL_NAMES.OPENAI.FAST), balanced: this.getConfigValue("OPENAI_BALANCED_MODEL", MODEL_NAMES.OPENAI.BALANCED), quality: this.getConfigValue("OPENAI_QUALITY_MODEL", MODEL_NAMES.OPENAI.QUALITY), }, defaultCost: { input: this.parseFloat(process.env.OPENAI_DEFAULT_INPUT_COST, 0.00015), output: this.parseFloat(process.env.OPENAI_DEFAULT_OUTPUT_COST, 0.0006), }, requiredEnvVars: ["OPENAI_API_KEY"], performance: { speed: this.parseInt(process.env.OPENAI_SPEED_RATING, 2), quality: this.parseInt(process.env.OPENAI_QUALITY_RATING, 3), cost: this.parseInt(process.env.OPENAI_COST_RATING, 2), }, modelBehavior: { maxTokensIssues: this.getConfigArray("OPENAI_MAX_TOKENS_ISSUES", []), specialHandling: this.getConfigObject("OPENAI_SPECIAL_HANDLING", {}), }, }; } /** * Create all provider configurations - centralized approach */ createAllProviderConfigs() { return { "google-ai": this.createGoogleAIConfig(), "google-vertex": this.createVertexConfig(), openai: this.createOpenAIConfig(), anthropic: this.createAnthropicConfig(), vertex: this.createVertexAlternativeConfig(), bedrock: this.createBedrockConfig(), azure: this.createAzureConfig(), ollama: this.createOllamaConfig(), huggingface: this.createHuggingFaceConfig(), mistral: this.createMistralConfig(), }; } /** * Create Anthropic provider configuration */ createAnthropicConfig() { return { provider: "anthropic", models: { fast: this.getConfigValue("ANTHROPIC_FAST_MODEL", MODEL_NAMES.ANTHROPIC.FAST), balanced: this.getConfigValue("ANTHROPIC_BALANCED_MODEL", MODEL_NAMES.ANTHROPIC.BALANCED), quality: this.getConfigValue("ANTHROPIC_QUALITY_MODEL", MODEL_NAMES.ANTHROPIC.QUALITY), }, defaultCost: { input: this.parseFloat(process.env.ANTHROPIC_DEFAULT_INPUT_COST, 0.0015), output: this.parseFloat(process.env.ANTHROPIC_DEFAULT_OUTPUT_COST, 0.0075), }, requiredEnvVars: ["ANTHROPIC_API_KEY"], performance: { speed: this.parseInt(process.env.ANTHROPIC_SPEED_RATING, 3), quality: this.parseInt(process.env.ANTHROPIC_QUALITY_RATING, 4), cost: this.parseInt(process.env.ANTHROPIC_COST_RATING, 2), }, modelBehavior: { maxTokensIssues: this.getConfigArray("ANTHROPIC_MAX_TOKENS_ISSUES", []), }, }; } /** * Create Vertex alternative provider configuration */ createVertexAlternativeConfig() { return { provider: "vertex", models: { fast: this.getConfigValue("VERTEX_FAST_MODEL", MODEL_NAMES.VERTEX.FAST), balanced: this.getConfigValue("VERTEX_BALANCED_MODEL", MODEL_NAMES.VERTEX.BALANCED), quality: this.getConfigValue("VERTEX_QUALITY_MODEL", MODEL_NAMES.VERTEX.QUALITY), }, defaultCost: { input: this.parseFloat(process.env.VERTEX_DEFAULT_INPUT_COST, 0.000075), output: this.parseFloat(process.env.VERTEX_DEFAULT_OUTPUT_COST, 0.0003), }, requiredEnvVars: ["GOOGLE_VERTEX_PROJECT_ID", "GOOGLE_VERTEX_LOCATION"], performance: { speed: this.parseInt(process.env.VERTEX_SPEED_RATING, 3), quality: this.parseInt(process.env.VERTEX_QUALITY_RATING, 4), cost: this.parseInt(process.env.VERTEX_COST_RATING, 3), }, modelBehavior: { maxTokensIssues: this.getConfigArray("VERTEX_MAX_TOKENS_ISSUES", []), }, }; } /** * Create Bedrock provider configuration */ createBedrockConfig() { return { provider: "bedrock", models: { fast: this.getConfigValue("BEDROCK_FAST_MODEL", MODEL_NAMES.BEDROCK.FAST), balanced: this.getConfigValue("BEDROCK_BALANCED_MODEL", MODEL_NAMES.BEDROCK.BALANCED), quality: this.getConfigValue("BEDROCK_QUALITY_MODEL", MODEL_NAMES.BEDROCK.QUALITY), }, defaultCost: { input: this.parseFloat(process.env.BEDROCK_DEFAULT_INPUT_COST, 0.0015), output: this.parseFloat(process.env.BEDROCK_DEFAULT_OUTPUT_COST, 0.0075), }, requiredEnvVars: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"], performance: { speed: this.parseInt(process.env.BEDROCK_SPEED_RATING, 3), quality: this.parseInt(process.env.BEDROCK_QUALITY_RATING, 4), cost: this.parseInt(process.env.BEDROCK_COST_RATING, 3), }, modelBehavior: { maxTokensIssues: this.getConfigArray("BEDROCK_MAX_TOKENS_ISSUES", []), }, }; } /** * Create Azure provider configuration */ createAzureConfig() { return { provider: "azure", models: { fast: this.getConfigValue("AZURE_FAST_MODEL", MODEL_NAMES.AZURE.FAST), balanced: this.getConfigValue("AZURE_BALANCED_MODEL", MODEL_NAMES.AZURE.BALANCED), quality: this.getConfigValue("AZURE_QUALITY_MODEL", MODEL_NAMES.AZURE.QUALITY), }, defaultCost: { input: this.parseFloat(process.env.AZURE_DEFAULT_INPUT_COST, 0.00015), output: this.parseFloat(process.env.AZURE_DEFAULT_OUTPUT_COST, 0.0006), }, requiredEnvVars: ["AZURE_API_KEY", "AZURE_ENDPOINT"], performance: { speed: this.parseInt(process.env.AZURE_SPEED_RATING, 2), quality: this.parseInt(process.env.AZURE_QUALITY_RATING, 3), cost: this.parseInt(process.env.AZURE_COST_RATING, 2), }, modelBehavior: { maxTokensIssues: this.getConfigArray("AZURE_MAX_TOKENS_ISSUES", []), }, }; } /** * Create Ollama provider configuration */ createOllamaConfig() { return { provider: "ollama", models: { fast: this.getConfigValue("OLLAMA_FAST_MODEL", MODEL_NAMES.OLLAMA.FAST), balanced: this.getConfigValue("OLLAMA_BALANCED_MODEL", MODEL_NAMES.OLLAMA.BALANCED), quality: this.getConfigValue("OLLAMA_QUALITY_MODEL", MODEL_NAMES.OLLAMA.QUALITY), }, defaultCost: { input: this.parseFloat(process.env.OLLAMA_DEFAULT_INPUT_COST, 0), output: this.parseFloat(process.env.OLLAMA_DEFAULT_OUTPUT_COST, 0), }, requiredEnvVars: [], performance: { speed: this.parseInt(process.env.OLLAMA_SPEED_RATING, 4), quality: this.parseInt(process.env.OLLAMA_QUALITY_RATING, 2), cost: this.parseInt(process.env.OLLAMA_COST_RATING, 5), }, modelBehavior: { maxTokensIssues: this.getConfigArray("OLLAMA_MAX_TOKENS_ISSUES", [ MODEL_NAMES.OLLAMA.FAST, MODEL_NAMES.OLLAMA.BALANCED, MODEL_NAMES.OLLAMA.QUALITY, ]), specialHandling: this.getConfigObject("OLLAMA_SPECIAL_HANDLING", { baseUrl: this.getConfigValue("OLLAMA_BASE_URL", "http://localhost:11434"), }), }, }; } /** * Create HuggingFace provider configuration */ createHuggingFaceConfig() { return { provider: "huggingface", models: { fast: this.getConfigValue("HUGGINGFACE_FAST_MODEL", MODEL_NAMES.HUGGINGFACE.FAST), balanced: this.getConfigValue("HUGGINGFACE_BALANCED_MODEL", MODEL_NAMES.HUGGINGFACE.BALANCED), quality: this.getConfigValue("HUGGINGFACE_QUALITY_MODEL", MODEL_NAMES.HUGGINGFACE.QUALITY), }, defaultCost: { input: this.parseFloat(process.env.HUGGINGFACE_DEFAULT_INPUT_COST, 0.0002), output: this.parseFloat(process.env.HUGGINGFACE_DEFAULT_OUTPUT_COST, 0.0008), }, requiredEnvVars: ["HUGGINGFACE_API_KEY"], performance: { speed: this.parseInt(process.env.HUGGINGFACE_SPEED_RATING, 3), quality: this.parseInt(process.env.HUGGINGFACE_QUALITY_RATING, 3), cost: this.parseInt(process.env.HUGGINGFACE_COST_RATING, 4), }, modelBehavior: { maxTokensIssues: this.getConfigArray("HUGGINGFACE_MAX_TOKENS_ISSUES", []), }, }; } /** * Create Mistral provider configuration */ createMistralConfig() { return { provider: "mistral", models: { fast: this.getConfigValue("MISTRAL_FAST_MODEL", MODEL_NAMES.MISTRAL.FAST), balanced: this.getConfigValue("MISTRAL_BALANCED_MODEL", MODEL_NAMES.MISTRAL.BALANCED), quality: this.getConfigValue("MISTRAL_QUALITY_MODEL", MODEL_NAMES.MISTRAL.QUALITY), }, defaultCost: { input: this.parseFloat(process.env.MISTRAL_DEFAULT_INPUT_COST, 0.0001), output: this.parseFloat(process.env.MISTRAL_DEFAULT_OUTPUT_COST, 0.0003), }, requiredEnvVars: ["MISTRAL_API_KEY"], performance: { speed: this.parseInt(process.env.MISTRAL_SPEED_RATING, 3), quality: this.parseInt(process.env.MISTRAL_QUALITY_RATING, 3), cost: this.parseInt(process.env.MISTRAL_COST_RATING, 4), }, modelBehavior: { maxTokensIssues: this.getConfigArray("MISTRAL_MAX_TOKENS_ISSUES", []), }, }; } /** * Load default configurations (replaces hardcoded values) */ loadDefaultConfigurations() { // Load all provider configurations using centralized method const defaultConfigs = this.createAllProviderConfigs(); // Load configurations for (const [provider, config] of Object.entries(defaultConfigs)) { this.configurations.set(provider, config); } logger.debug(`Loaded ${this.configurations.size} provider configurations from ${this.configSource}`); } /** * Helper method to get configuration value with fallback and validation */ getConfigValue(envVar, defaultValue) { const value = process.env[envVar]; if (value && !this.isValidConfigValue(value)) { logger.warn(`Environment variable ${envVar} has an invalid value: "${value}". Falling back to default value.`); return defaultValue; } return value || defaultValue; } /** * Validate configuration values for security and correctness */ isValidConfigValue(value) { // Basic validation rules for security-sensitive configuration values // Check for potentially dangerous characters (script injection prevention) const dangerousChars = /[<>;"'`${}]/; if (dangerousChars.test(value)) { return false; } // Check for excessively long values (DoS prevention) if (value.length > 500) { return false; } // Check for null bytes (security) if (value.includes("\0")) { return false; } // Check for control characters except newlines and tabs // Using String.fromCharCode to avoid ESLint control character error const controlChars = new RegExp(`[${String.fromCharCode(0x00)}-${String.fromCharCode(0x08)}${String.fromCharCode(0x0b)}${String.fromCharCode(0x0c)}${String.fromCharCode(0x0e)}-${String.fromCharCode(0x1f)}${String.fromCharCode(0x7f)}]`); if (controlChars.test(value)) { return false; } return true; } /** * Helper method to get configuration array with fallback * Parses comma-separated environment variable values */ getConfigArray(envVar, defaultValue) { const envValue = process.env[envVar]; if (!envValue) { return defaultValue; } return envValue .split(",") .map((item) => item.trim()) .filter(Boolean); } /** * Helper method to parse float with fallback */ parseFloat(value, defaultValue) { if (!value) { return defaultValue; } const parsed = Number.parseFloat(value); return Number.isNaN(parsed) ? defaultValue : parsed; } /** * Helper method to parse int with fallback */ parseInt(value, defaultValue) { if (!value) { return defaultValue; } const parsed = Number.parseInt(value, 10); return Number.isNaN(parsed) ? defaultValue : parsed; } /** * Helper method to get configuration object with fallback * Parses JSON environment variable values */ getConfigObject(envVar, defaultValue) { const envValue = process.env[envVar]; if (!envValue) { return defaultValue; } try { const parsed = JSON.parse(envValue); return typeof parsed === "object" && parsed !== null ? parsed : defaultValue; } catch { logger.warn(`Invalid JSON in environment variable ${envVar}, using default`); return defaultValue; } } /** * Get provider configuration */ getProviderConfig(provider) { return this.configurations.get(provider) || null; } /** * Get all provider configurations */ getAllConfigurations() { return new Map(this.configurations); } /** * Update provider configuration (runtime updates) */ updateProviderConfig(provider, config) { this.configurations.set(provider, config); this.lastUpdated = Date.now(); this.configSource = "dynamic"; logger.debug(`Updated configuration for provider: ${provider}`); } /** * Parse YAML content with basic support * For full YAML support, would need a proper YAML library */ parseYAMLContent(configContent) { // Basic YAML parsing for simple configurations try { return JSON.parse(configContent); } catch { return this.convertSimpleYAMLToJSON(configContent); } } /** * Convert simple YAML to JSON object * Handles basic key-value pairs only */ convertSimpleYAMLToJSON(configContent) { const yamlLines = configContent.split("\n"); const jsonObj = {}; for (const line of yamlLines) { const processedLine = this.processYAMLLine(line); if (processedLine) { jsonObj[processedLine.key] = processedLine.value; } } return jsonObj; } /** * Process a single YAML line * Returns key-value pair if valid, null otherwise */ processYAMLLine(line) { const trimmedLine = line.trim(); if (!trimmedLine || trimmedLine.startsWith("#")) { return null; } const colonIndex = trimmedLine.indexOf(":"); if (colonIndex <= 0) { return null; } const key = trimmedLine.substring(0, colonIndex).trim(); const value = trimmedLine.substring(colonIndex + 1).trim(); return { key, value: this.convertYAMLValue(value), }; } /** * Convert YAML value to appropriate JavaScript type */ convertYAMLValue(value) { if (value === "true") { return true; } if (value === "false") { return false; } if (!isNaN(Number(value))) { return Number(value); } return value.replace(/['"]/g, ""); } /** * Load configurations from external source */ loadConfigurationsFromFile(configPath) { try { // Check if file exists if (!fs.existsSync(configPath)) { throw new Error(`Configuration file not found: ${configPath}`); } // Read and parse configuration file const configContent = fs.readFileSync(configPath, "utf8"); const fileExtension = path.extname(configPath).toLowerCase(); let configData; if (fileExtension === ".json") { configData = JSON.parse(configContent); } else if (fileExtension === ".yaml" || fileExtension === ".yml") { configData = this.parseYAMLContent(configContent); } else { throw new Error(`Unsupported configuration file format: ${fileExtension}. Supported formats: .json, .yaml, .yml`); } // Validate configuration structure if (!configData || typeof configData !== "object") { throw new Error("Invalid configuration format: must be an object"); } const config = configData; // Load provider configurations from file if (config.providers && typeof config.providers === "object") { const providers = config.providers; for (const [providerName, providerConfig] of Object.entries(providers)) { if (this.isValidProviderConfig(providerConfig)) { this.configurations.set(providerName, providerConfig); logger.debug(`Loaded configuration for provider: ${providerName}`); } else { logger.warn(`Invalid configuration for provider: ${providerName}, skipping`); } } } // Load global model name overrides if (config.modelNames && typeof config.modelNames === "object") { const modelNames = config.modelNames; this.applyModelNameOverrides(modelNames); } // Load global defaults if (config.defaults && typeof config.defaults === "object") { const defaults = config.defaults; this.applyGlobalDefaults(defaults); } this.configSource = "file"; this.lastUpdated = Date.now(); logger.info(`Successfully loaded configurations from file: ${configPath}`); logger.debug(`Loaded ${this.configurations.size} provider configurations`); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error(`Failed to load configurations from file: ${errorMessage}`); throw new Error(`Configuration loading failed: ${errorMessage}`); } } /** * Validate provider configuration structure */ isValidProviderConfig(config) { if (!config || typeof config !== "object") { return false; } const providerConfig = config; // Check required fields const requiredFields = [ "provider", "models", "defaultCost", "requiredEnvVars", "performance", ]; for (const field of requiredFields) { if (!(field in providerConfig)) { return false; } } // Validate models structure if (typeof providerConfig.models !== "object" || !providerConfig.models) { return false; } const models = providerConfig.models; const requiredTiers = ["fast", "balanced", "quality"]; for (const tier of requiredTiers) { if (typeof models[tier] !== "string") { return false; } } // Validate defaultCost structure if (typeof providerConfig.defaultCost !== "object" || !providerConfig.defaultCost) { return false; } const defaultCost = providerConfig.defaultCost; if (typeof defaultCost.input !== "number" || typeof defaultCost.output !== "number") { return false; } // Validate requiredEnvVars if (!Array.isArray(providerConfig.requiredEnvVars)) { return false; } // Validate performance structure if (typeof providerConfig.performance !== "object" || !providerConfig.performance) { return false; } const performance = providerConfig.performance; const perfFields = ["speed", "quality", "cost"]; for (const field of perfFields) { if (typeof performance[field] !== "number") { return false; } } return true; } /** * Apply model name overrides from configuration file */ applyModelNameOverrides(modelNames) { try { // Apply overrides to existing configurations for (const [providerKey, providerModels] of Object.entries(modelNames)) { if (typeof providerModels === "object" && providerModels) { const models = providerModels; const existingConfig = this.configurations.get(providerKey); if (existingConfig) { // Update existing configuration for (const [tier, model] of Object.entries(models)) { if (typeof model === "string" && ["fast", "balanced", "quality"].includes(tier)) { existingConfig.models[tier] = model; } } this.configurations.set(providerKey, existingConfig); logger.debug(`Applied model name overrides for provider: ${providerKey}`); } } } } catch (error) { logger.warn(`Failed to apply model name overrides: ${error instanceof Error ? error.message : String(error)}`); } } /** * Apply global default configurations */ applyGlobalDefaults(defaults) { try { // Apply global defaults to all existing configurations for (const [providerName, config] of this.configurations.entries()) { let updated = false; // Apply default cost overrides if (defaults.defaultCost && typeof defaults.defaultCost === "object") { const defaultCost = defaults.defaultCost; if (typeof defaultCost.input === "number") { config.defaultCost.input = defaultCost.input; updated = true; } if (typeof defaultCost.output === "number") { config.defaultCost.output = defaultCost.output; updated = true; } } // Apply performance overrides if (defaults.performance && typeof defaults.performance === "object") { const performance = defaults.performance; ["speed", "quality", "cost"].forEach((field) => { if (typeof performance[field] === "number") { config.performance[field] = performance[field]; updated = true; } }); } if (updated) { this.configurations.set(providerName, config); logger.debug(`Applied global defaults to provider: ${providerName}`); } } } catch (error) { logger.warn(`Failed to apply global defaults: ${error instanceof Error ? error.message : String(error)}`); } } /** * Get configuration metadata */ getConfigurationMeta() { return { source: this.configSource, lastUpdated: this.lastUpdated, providerCount: this.configurations.size, }; } /** * Get model for specific tier and provider */ getModelForTier(provider, tier) { const config = this.getProviderConfig(provider); return config?.models[tier] || null; } /** * Get cost information for provider and model */ getCostInfo(provider, model) { const config = this.getProviderConfig(provider); if (!config) { return null; } // If specific model config exists, use it; otherwise use default if (model && config.modelConfigs?.[model]) { return config.modelConfigs[model].cost; } return config.defaultCost; } /** * Check if provider is available (has required environment variables) */ isProviderAvailable(provider) { const config = this.getProviderConfig(provider); if (!config) { return false; } if (config.requiredEnvVars.length === 0) { return true; // No requirements (e.g., Ollama) } return config.requiredEnvVars.some((envVar) => Boolean(process.env[envVar])); } /** * Get available providers */ getAvailableProviders() { return Array.from(this.configurations.values()).filter((config) => this.isProviderAvailable(config.provider)); } } /** * Global instance accessor */ export const modelConfig = ModelConfigurationManager.getInstance(); /** * Convenience functions for common operations */ /** * Get provider configuration (backwards compatible) */ export function getProviderConfig(provider) { return modelConfig.getProviderConfig(provider); } /** * Get model for tier (backwards compatible) */ export function getModelForTier(provider, tier) { return modelConfig.getModelForTier(provider, tier); } /** * Get cost information (backwards compatible) */ export function getCostInfo(provider, model) { return modelConfig.getCostInfo(provider, model); } /** * Check provider availability (backwards compatible) */ export function isProviderAvailable(provider) { return modelConfig.isProviderAvailable(provider); }