UNPKG

route-claudecode

Version:

Advanced routing and transformation system for Claude Code outputs to multiple AI providers

221 lines 9.7 kB
"use strict"; /** * 统一配置验证模块 * 项目所有者: Jason Zhang * * 提供零fallback的配置验证,确保所有配置都是明确的 * 遵循零硬编码、零Fallback、零静默失败原则 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ConfigValidationError = void 0; exports.validateApiKey = validateApiKey; exports.validateBaseURL = validateBaseURL; exports.validatePortConfiguration = validatePortConfiguration; exports.validateDefaultModel = validateDefaultModel; exports.validateSDKOptions = validateSDKOptions; exports.validateProviderConfig = validateProviderConfig; const logger_1 = require("@/utils/logger"); /** * 配置验证错误类型 */ class ConfigValidationError extends Error { providerId; configField; constructor(message, providerId, configField) { super(message); this.providerId = providerId; this.configField = configField; this.name = 'ConfigValidationError'; } } exports.ConfigValidationError = ConfigValidationError; /** * 🚨 严格验证API Key - 零fallback原则 */ function validateApiKey(config, providerId) { // 检查认证类型,如果是none则不需要credentials if (config.authentication?.type === 'none') { logger_1.logger.debug(`Provider ${providerId} uses no authentication`, { providerId }); return ''; // 返回空字符串表示不需要API key } const credentials = config.authentication?.credentials; if (!credentials) { throw new ConfigValidationError(`Provider ${providerId} requires authentication credentials - violates zero fallback principle`, providerId, 'authentication.credentials'); } const apiKey = credentials.apiKey || credentials.api_key; if (!apiKey) { throw new ConfigValidationError(`Provider ${providerId} requires valid API key - violates zero fallback principle`, providerId, 'apiKey'); } // 处理数组格式的API key const finalApiKey = Array.isArray(apiKey) ? apiKey[0] : apiKey; if (!finalApiKey || typeof finalApiKey !== 'string') { throw new ConfigValidationError(`Provider ${providerId} API key must be a valid string - violates zero fallback principle`, providerId, 'apiKey'); } // 验证API key格式(基本检查)- 允许本地服务的简单key if (finalApiKey === 'dummy-key' || finalApiKey === 'test-key') { throw new ConfigValidationError(`Provider ${providerId} has invalid or placeholder API key - violates zero fallback principle`, providerId, 'apiKey'); } // 允许本地服务使用简单的API key if (finalApiKey.length < 5) { throw new ConfigValidationError(`Provider ${providerId} API key too short - must be at least 5 characters`, providerId, 'apiKey'); } return finalApiKey; } /** * 🚨 严格验证Base URL - 零fallback原则 */ function validateBaseURL(config, providerId) { if (!config.endpoint) { throw new ConfigValidationError(`Provider ${providerId} requires endpoint configuration - violates zero fallback principle`, providerId, 'endpoint'); } let baseURL = config.endpoint; // 验证URL格式 try { new URL(baseURL); } catch (error) { throw new ConfigValidationError(`Provider ${providerId} endpoint is not a valid URL: ${baseURL} - violates zero fallback principle`, providerId, 'endpoint'); } // 标准化OpenAI兼容服务的URL if (baseURL.includes('/chat/completions')) { baseURL = baseURL.replace(/\/chat\/completions.*$/, ''); } // 确保以/v1结尾(仅对OpenAI兼容服务) if (baseURL.includes('openai') || baseURL.includes('api.openai.com') || baseURL.includes('v1') || baseURL.includes('completions')) { if (!baseURL.endsWith('/v1')) { // 移除可能的尾随斜杠 if (baseURL.endsWith('/')) { baseURL = baseURL.slice(0, -1); } // 添加/v1 baseURL += '/v1'; } } return baseURL; } /** * 🚨 严格验证端口配置 - 零fallback原则 */ function validatePortConfiguration(config, providerId) { // 尝试从endpoint URL提取端口 try { const url = new URL(config.endpoint); if (url.port) { const port = parseInt(url.port, 10); if (isNaN(port) || port < 1 || port > 65535) { throw new ConfigValidationError(`Provider ${providerId} has invalid port in endpoint: ${url.port} - must be 1-65535`, providerId, 'endpoint.port'); } return port; } } catch (error) { throw new ConfigValidationError(`Provider ${providerId} endpoint URL is invalid - cannot extract port`, providerId, 'endpoint'); } // 从环境变量获取 if (process.env.RCC_PORT) { const envPort = parseInt(process.env.RCC_PORT, 10); if (isNaN(envPort) || envPort < 1 || envPort > 65535) { throw new ConfigValidationError(`Environment variable RCC_PORT has invalid value: ${process.env.RCC_PORT} - must be 1-65535`, providerId, 'RCC_PORT'); } return envPort; } // 检查配置对象中的端口设置 if (config.port) { const configPort = typeof config.port === 'string' ? parseInt(config.port, 10) : config.port; if (isNaN(configPort) || configPort < 1 || configPort > 65535) { throw new ConfigValidationError(`Provider ${providerId} has invalid port configuration: ${config.port} - must be 1-65535`, providerId, 'port'); } return configPort; } // 零fallback - 必须明确配置端口 throw new ConfigValidationError(`Provider ${providerId} requires explicit port configuration - violates zero fallback principle. ` + `Set port in endpoint URL, RCC_PORT environment variable, or config.port`, providerId, 'port'); } /** * 🚨 严格验证默认模型 - 零fallback原则 */ function validateDefaultModel(config, providerId) { if (!config.defaultModel) { throw new ConfigValidationError(`Provider ${providerId} requires explicit defaultModel configuration - violates zero fallback principle`, providerId, 'defaultModel'); } const model = config.defaultModel; if (typeof model !== 'string' || model.length === 0) { throw new ConfigValidationError(`Provider ${providerId} defaultModel must be a non-empty string - violates zero fallback principle`, providerId, 'defaultModel'); } // 检查常见的fallback模型名 const fallbackModels = ['gpt-3.5-turbo', 'claude-3-haiku', 'default', 'fallback', 'unknown']; if (fallbackModels.includes(model.toLowerCase())) { logger_1.logger.warn(`Provider ${providerId} is using a common fallback model: ${model}`, { providerId, model, suggestion: 'Consider using a more specific model name' }); } return model; } /** * 验证SDK选项配置 */ function validateSDKOptions(options = {}) { const timeout = typeof options.timeout === 'number' ? options.timeout : 60000; const maxRetries = typeof options.maxRetries === 'number' ? options.maxRetries : 3; const defaultHeaders = options.defaultHeaders || {}; // 验证超时时间 if (timeout < 1000 || timeout > 600000) { // 1秒到10分钟 throw new ConfigValidationError(`SDK timeout must be between 1000ms and 600000ms, got: ${timeout}`, 'sdk', 'timeout'); } // 验证重试次数 if (maxRetries < 0 || maxRetries > 10) { throw new ConfigValidationError(`SDK maxRetries must be between 0 and 10, got: ${maxRetries}`, 'sdk', 'maxRetries'); } // 验证headers格式 if (typeof defaultHeaders !== 'object' || Array.isArray(defaultHeaders)) { throw new ConfigValidationError(`SDK defaultHeaders must be an object, got: ${typeof defaultHeaders}`, 'sdk', 'defaultHeaders'); } return { timeout, maxRetries, defaultHeaders: { 'User-Agent': `claude-code-router/2.8.0`, ...defaultHeaders } }; } function validateProviderConfig(config, providerId, sdkOptions) { logger_1.logger.info(`Validating configuration for provider: ${providerId}`, { providerId, hasEndpoint: !!config.endpoint, hasAuth: !!config.authentication, hasDefaultModel: !!config.defaultModel }); try { const validatedConfig = { apiKey: validateApiKey(config, providerId), baseURL: validateBaseURL(config, providerId), port: validatePortConfiguration(config, providerId), defaultModel: validateDefaultModel(config, providerId), sdkOptions: validateSDKOptions(sdkOptions), httpOptions: sdkOptions // Pass through httpOptions as-is }; logger_1.logger.info(`Configuration validation passed for provider: ${providerId}`, { providerId, baseURL: validatedConfig.baseURL, hasApiKey: !!validatedConfig.apiKey, port: validatedConfig.port, defaultModel: validatedConfig.defaultModel, timeout: validatedConfig.sdkOptions.timeout, maxRetries: validatedConfig.sdkOptions.maxRetries }); return validatedConfig; } catch (error) { logger_1.logger.error(`Configuration validation failed for provider: ${providerId}`, { providerId, error: error instanceof Error ? error.message : String(error), errorType: error instanceof ConfigValidationError ? error.name : 'Unknown' }); throw error; } } //# sourceMappingURL=config-validation.js.map