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

309 lines (308 loc) 12.2 kB
/** * Configuration management for Amazon SageMaker Provider * * This module handles loading, validation, and management of SageMaker * configuration from environment variables, files, and defaults. */ import { z } from "zod"; import { logger } from "../../utils/logger.js"; /** * Zod schema for SageMaker configuration validation */ const SageMakerConfigSchema = z.object({ region: z.string().min(1, "AWS region is required"), accessKeyId: z.string().min(1, "AWS access key ID is required"), secretAccessKey: z.string().min(1, "AWS secret access key is required"), sessionToken: z.string().optional(), timeout: z.number().min(1000).max(300000).optional(), maxRetries: z.number().min(0).max(10).optional(), endpoint: z.string().url().optional(), }); /** * Zod schema for SageMaker model configuration validation */ const SageMakerModelConfigSchema = z.object({ endpointName: z.string().min(1, "Endpoint name is required"), modelType: z .enum(["llama", "mistral", "claude", "huggingface", "jumpstart", "custom"]) .optional(), contentType: z.string().optional(), accept: z.string().optional(), customAttributes: z.string().optional(), inputFormat: z.enum(["huggingface", "jumpstart", "custom"]).optional(), outputFormat: z.enum(["huggingface", "jumpstart", "custom"]).optional(), maxTokens: z.number().min(1).max(100000).optional(), temperature: z.number().min(0).max(2).optional(), topP: z.number().min(0).max(1).optional(), stopSequences: z.array(z.string()).optional(), maxConcurrentDetectionTests: z.number().min(1).max(10).optional(), }); /** * Configuration cache to avoid repeated environment variable reads */ let configCache = null; const modelConfigCache = new Map(); /** * Load and validate SageMaker configuration from environment variables * * Environment variable priority: * 1. SAGEMAKER_* variables (highest priority) * 2. AWS_* variables (standard AWS SDK variables) * 3. Default values (lowest priority) * * @returns Validated SageMaker configuration * @throws {Error} When required configuration is missing or invalid */ export function getSageMakerConfig() { // Return cached config if available if (configCache) { return configCache; } const config = { region: process.env.SAGEMAKER_REGION || process.env.AWS_REGION || "us-east-1", accessKeyId: process.env.AWS_ACCESS_KEY_ID || "", secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || "", sessionToken: process.env.AWS_SESSION_TOKEN, timeout: parseInt(process.env.SAGEMAKER_TIMEOUT || "30000"), maxRetries: parseInt(process.env.SAGEMAKER_MAX_RETRIES || "3"), endpoint: process.env.SAGEMAKER_ENDPOINT, }; // Validate configuration using Zod schema try { const validatedConfig = SageMakerConfigSchema.parse(config); // Cache the validated configuration configCache = validatedConfig; return validatedConfig; } catch (error) { if (error instanceof z.ZodError) { const errorMessages = error.errors.map((err) => `${err.path.join(".")}: ${err.message}`); throw new Error(`SageMaker configuration validation failed:\n${errorMessages.join("\n")}\n\n` + `Please set the required environment variables:\n` + `- AWS_ACCESS_KEY_ID: Your AWS access key\n` + `- AWS_SECRET_ACCESS_KEY: Your AWS secret key\n` + `- AWS_REGION: AWS region (default: us-east-1)\n` + `- AWS_SESSION_TOKEN: Session token (optional, for temporary credentials)\n` + `- SAGEMAKER_TIMEOUT: Request timeout in ms (optional, default: 30000)\n` + `- SAGEMAKER_MAX_RETRIES: Max retry attempts (optional, default: 3)`); } throw error; } } /** * Load and validate SageMaker model configuration * * @param endpointName - Name of the SageMaker endpoint * @returns Validated model configuration */ export function getSageMakerModelConfig(endpointName) { const endpoint = endpointName || getDefaultSageMakerEndpoint(); // Check cache first if (modelConfigCache.has(endpoint)) { return modelConfigCache.get(endpoint); } const config = { endpointName: endpoint, modelType: process.env.SAGEMAKER_MODEL_TYPE || "custom", contentType: process.env.SAGEMAKER_CONTENT_TYPE || "application/json", accept: process.env.SAGEMAKER_ACCEPT || "application/json", customAttributes: process.env.SAGEMAKER_CUSTOM_ATTRIBUTES, inputFormat: process.env.SAGEMAKER_INPUT_FORMAT || "custom", outputFormat: process.env.SAGEMAKER_OUTPUT_FORMAT || "custom", maxTokens: process.env.SAGEMAKER_MAX_TOKENS ? parseInt(process.env.SAGEMAKER_MAX_TOKENS) : undefined, temperature: process.env.SAGEMAKER_TEMPERATURE ? parseFloat(process.env.SAGEMAKER_TEMPERATURE) : undefined, topP: process.env.SAGEMAKER_TOP_P ? parseFloat(process.env.SAGEMAKER_TOP_P) : undefined, stopSequences: process.env.SAGEMAKER_STOP_SEQUENCES ? process.env.SAGEMAKER_STOP_SEQUENCES.split(",").map((s) => s.trim()) : undefined, }; // Validate configuration try { const validatedConfig = SageMakerModelConfigSchema.parse(config); // Cache the validated configuration modelConfigCache.set(endpoint, validatedConfig); return validatedConfig; } catch (error) { if (error instanceof z.ZodError) { const errorMessages = error.errors.map((err) => `${err.path.join(".")}: ${err.message}`); throw new Error(`SageMaker model configuration validation failed for endpoint '${endpoint}':\n${errorMessages.join("\n")}`); } throw error; } } /** * Get the default SageMaker endpoint name from environment variables * * @returns Default endpoint name */ export function getDefaultSageMakerEndpoint() { return (process.env.SAGEMAKER_DEFAULT_ENDPOINT || process.env.SAGEMAKER_ENDPOINT_NAME || "default-endpoint"); } /** * Get SageMaker model name from environment variables * * @returns Model name */ export function getSageMakerModel() { return (process.env.SAGEMAKER_MODEL || process.env.SAGEMAKER_MODEL_NAME || "sagemaker-model"); } /** * Check AWS access key presence (minimal validation to prevent credential enumeration) * @param accessKeyId - AWS access key to check * @returns Validation result */ function checkAccessKeyPresence(accessKeyId) { // Only check for obviously invalid keys (empty/whitespace) // Delegate all other validation to AWS SDK to prevent credential enumeration if (!accessKeyId || accessKeyId.trim() === "") { return { isValid: false }; } // Accept non-empty string - let AWS handle validation // This prevents attackers from learning about valid formats return { isValid: true }; } /** * Validate AWS credentials are properly configured * * @param config - SageMaker configuration to validate * @returns true if credentials are valid * @throws {Error} When credentials are missing or invalid */ export function validateAWSCredentials(config) { if (!config.accessKeyId || config.accessKeyId.trim() === "") { throw new Error("AWS Access Key ID is missing. Please set AWS_ACCESS_KEY_ID environment variable."); } if (!config.secretAccessKey || config.secretAccessKey.trim() === "") { throw new Error("AWS Secret Access Key is missing. Please set AWS_SECRET_ACCESS_KEY environment variable."); } // Basic AWS access key validation (let AWS SDK handle detailed validation) const accessKeyValidation = checkAccessKeyPresence(config.accessKeyId); if (!accessKeyValidation.isValid) { // Minimal logging for security - let AWS SDK handle detailed validation logger.warn("AWS Access Key ID format check failed. " + "Please verify your AWS credentials are correct."); } // Validate region format if (!/^[a-z0-9-]+$/.test(config.region)) { throw new Error(`Invalid AWS region format: ${config.region}. Expected format: us-east-1, eu-west-1, etc.`); } return true; } /** * Create a comprehensive configuration summary for debugging * * @returns Configuration summary (sensitive data masked) */ export function getConfigurationSummary() { try { const config = getSageMakerConfig(); const defaultEndpoint = getDefaultSageMakerEndpoint(); const modelConfig = getSageMakerModelConfig(); return { aws: { region: config.region, accessKeyId: config.accessKeyId ? `${config.accessKeyId.substring(0, 4)}***` : "NOT_SET", secretAccessKey: config.secretAccessKey ? "***SET***" : "NOT_SET", sessionToken: config.sessionToken ? "***SET***" : "NOT_SET", timeout: config.timeout, maxRetries: config.maxRetries, endpoint: config.endpoint || "DEFAULT", }, sagemaker: { defaultEndpoint, model: getSageMakerModel(), modelConfig: { endpointName: modelConfig.endpointName, modelType: modelConfig.modelType, contentType: modelConfig.contentType, accept: modelConfig.accept, inputFormat: modelConfig.inputFormat, outputFormat: modelConfig.outputFormat, maxTokens: modelConfig.maxTokens, temperature: modelConfig.temperature, topP: modelConfig.topP, }, }, environment: { nodeEnv: process.env.NODE_ENV || "development", sagemakerConfigured: !!process.env.SAGEMAKER_DEFAULT_ENDPOINT, awsConfigured: !!(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY), }, }; } catch (error) { return { error: error instanceof Error ? error.message : "Unknown configuration error", configured: false, }; } } /** * Clear configuration cache (useful for testing or credential rotation) */ export function clearConfigurationCache() { configCache = null; modelConfigCache.clear(); } /** * Load configuration from a JSON file (alternative to environment variables) * * @param filePath - Path to configuration JSON file * @returns Loaded configuration */ export async function loadConfigurationFromFile(filePath) { try { const fs = await import("fs/promises"); const configData = await fs.readFile(filePath, "utf-8"); const parsedConfig = JSON.parse(configData); // Validate the loaded configuration const validatedConfig = SageMakerConfigSchema.parse(parsedConfig); // Update cache with file-based configuration configCache = validatedConfig; return validatedConfig; } catch (error) { throw new Error(`Failed to load SageMaker configuration from file '${filePath}': ${error instanceof Error ? error.message : "Unknown error"}`); } } /** * Check if SageMaker provider is properly configured * * @returns Configuration check result */ export function checkSageMakerConfiguration() { const issues = []; let configured = false; try { // Try to load configuration const config = getSageMakerConfig(); validateAWSCredentials(config); // Check endpoint configuration const endpoint = getDefaultSageMakerEndpoint(); if (endpoint === "default-endpoint") { issues.push("Default endpoint name detected. Consider setting SAGEMAKER_DEFAULT_ENDPOINT."); } configured = true; } catch (error) { issues.push(error instanceof Error ? error.message : "Unknown configuration error"); } return { configured, issues, summary: getConfigurationSummary(), }; }