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

776 lines (775 loc) 33.2 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; } /** * Load default configurations (replaces hardcoded values) */ loadDefaultConfigurations() { // Default provider configurations - these can be overridden const defaultConfigs = { "google-ai": { 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, ]), }, }, "google-vertex": { 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, ]), }, }, openai: { 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", {}), }, }, anthropic: { 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.00025), output: this.parseFloat(process.env.ANTHROPIC_DEFAULT_OUTPUT_COST, 0.00125), }, requiredEnvVars: ["ANTHROPIC_API_KEY"], performance: { speed: this.parseInt(process.env.ANTHROPIC_SPEED_RATING, 2), quality: this.parseInt(process.env.ANTHROPIC_QUALITY_RATING, 3), cost: this.parseInt(process.env.ANTHROPIC_COST_RATING, 2), }, modelBehavior: { maxTokensIssues: this.getConfigArray("ANTHROPIC_MAX_TOKENS_ISSUES", []), specialHandling: this.getConfigObject("ANTHROPIC_SPECIAL_HANDLING", {}), }, }, vertex: { 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", "GOOGLE_APPLICATION_CREDENTIALS", ], performance: { speed: this.parseInt(process.env.VERTEX_SPEED_RATING, 2), quality: this.parseInt(process.env.VERTEX_QUALITY_RATING, 3), cost: this.parseInt(process.env.VERTEX_COST_RATING, 3), }, modelBehavior: { maxTokensIssues: this.getConfigArray("VERTEX_MAX_TOKENS_ISSUES", [ MODEL_NAMES.VERTEX.FAST, MODEL_NAMES.VERTEX.BALANCED, ]), specialHandling: this.getConfigObject("VERTEX_SPECIAL_HANDLING", {}), }, }, bedrock: { 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.00025), output: this.parseFloat(process.env.BEDROCK_DEFAULT_OUTPUT_COST, 0.00125), }, requiredEnvVars: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"], performance: { speed: this.parseInt(process.env.BEDROCK_SPEED_RATING, 2), quality: this.parseInt(process.env.BEDROCK_QUALITY_RATING, 3), cost: this.parseInt(process.env.BEDROCK_COST_RATING, 2), }, modelBehavior: { maxTokensIssues: this.getConfigArray("BEDROCK_MAX_TOKENS_ISSUES", []), specialHandling: this.getConfigObject("BEDROCK_SPECIAL_HANDLING", {}), }, }, azure: { 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_OPENAI_API_KEY", "AZURE_OPENAI_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", []), specialHandling: this.getConfigObject("AZURE_SPECIAL_HANDLING", {}), }, }, ollama: { 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: 0, // Local models are free output: 0, }, requiredEnvVars: [], // No API key needed performance: { speed: this.parseInt(process.env.OLLAMA_SPEED_RATING, 1), quality: this.parseInt(process.env.OLLAMA_QUALITY_RATING, 2), cost: this.parseInt(process.env.OLLAMA_COST_RATING, 3), }, modelBehavior: { maxTokensIssues: this.getConfigArray("OLLAMA_MAX_TOKENS_ISSUES", []), specialHandling: this.getConfigObject("OLLAMA_SPECIAL_HANDLING", {}), // Tool-capable models configuration (replaces hardcoded list in ollama.ts) toolCapableModels: this.getConfigArray("OLLAMA_TOOL_CAPABLE_MODELS", [ // Llama 3.1 series (excellent tool calling) "llama3.1:8b-instruct", "llama3.1:70b-instruct", "llama3.1", // Mistral series (reliable function calling) "mistral:7b-instruct", "mistral-nemo:12b", "mistral", // Hermes series (specialized for tools) "hermes3:8b", "hermes2-pro", // Function-calling specialized models "firefunction-v2", "firefunction", // Code Llama (code-focused tools) "codellama:34b-instruct", "codellama:13b-instruct", // Other capable models "qwen2.5:14b-instruct", "gemma2:27b-instruct", ]), }, }, huggingface: { 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.0006), }, requiredEnvVars: ["HUGGINGFACE_API_KEY"], performance: { speed: this.parseInt(process.env.HUGGINGFACE_SPEED_RATING, 1), quality: this.parseInt(process.env.HUGGINGFACE_QUALITY_RATING, 2), cost: this.parseInt(process.env.HUGGINGFACE_COST_RATING, 2), }, modelBehavior: { maxTokensIssues: this.getConfigArray("HUGGINGFACE_MAX_TOKENS_ISSUES", []), specialHandling: this.getConfigObject("HUGGINGFACE_SPECIAL_HANDLING", {}), }, }, mistral: { 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.0002), output: this.parseFloat(process.env.MISTRAL_DEFAULT_OUTPUT_COST, 0.0006), }, requiredEnvVars: ["MISTRAL_API_KEY"], performance: { speed: this.parseInt(process.env.MISTRAL_SPEED_RATING, 2), quality: this.parseInt(process.env.MISTRAL_QUALITY_RATING, 2), cost: this.parseInt(process.env.MISTRAL_COST_RATING, 2), }, modelBehavior: { maxTokensIssues: this.getConfigArray("MISTRAL_MAX_TOKENS_ISSUES", []), specialHandling: this.getConfigObject("MISTRAL_SPECIAL_HANDLING", {}), }, }, }; // 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}`); } /** * 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") { // Basic YAML parsing for simple configurations // For full YAML support, would need a proper YAML library try { configData = JSON.parse(configContent); } catch { // Simple YAML-to-JSON conversion for basic cases const yamlLines = configContent.split("\n"); const jsonObj = {}; for (const line of yamlLines) { const trimmedLine = line.trim(); if (trimmedLine && !trimmedLine.startsWith("#")) { const colonIndex = trimmedLine.indexOf(":"); if (colonIndex > 0) { const key = trimmedLine.substring(0, colonIndex).trim(); const value = trimmedLine.substring(colonIndex + 1).trim(); // Basic type conversion if (value === "true") { jsonObj[key] = true; } else if (value === "false") { jsonObj[key] = false; } else if (!isNaN(Number(value))) { jsonObj[key] = Number(value); } else { jsonObj[key] = value.replace(/['"]/g, ""); } } } } configData = jsonObj; } } 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); }