UNPKG

route-claudecode

Version:

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

983 lines 35.8 kB
"use strict"; /** * Gemini Standard Pipeline Client * 基于OpenAI标准设计规则的11模块流水线架构重构 * 项目所有者: Jason Zhang * * 遵循设计原则: * - 零硬编码: 所有配置外部化 * - 细菌式编程: 小巧、模块化、自包含 * - 严格类型安全: TypeScript接口定义 * - 失败时快速失败: 不使用fallback机制 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.GeminiStandardPipelineClient = void 0; const types_1 = require("@/types"); const logger_1 = require("@/utils/logger"); const genai_1 = require("@google/genai"); // ==================== 1. 客户端路由器模块 ==================== class GeminiClientRouter { id = 'gemini-client-router'; type = 'client-router'; debug; test; constructor() { this.debug = new ModuleDebugCapture(this.id); this.test = new ModuleUnitTest(this.id, this.validateRouting.bind(this)); } async process(input) { const startTime = Date.now(); const debugInput = this.debug.captureInput(input.data, input.metadata); try { // 路由决策逻辑 const routedRequest = { ...input.data, metadata: { ...input.data.metadata, requestId: input.data.metadata?.requestId || `req_${Date.now()}`, routedBy: this.id, routingDecision: { provider: 'gemini', confidence: 1.0, reason: 'direct-gemini-request' } } }; const debugOutput = this.debug.captureOutput(routedRequest, input.metadata); return { data: routedRequest, metadata: input.metadata, debug: { moduleId: this.id, processingTime: Date.now() - startTime, inputSize: this.calculateSize(input.data), outputSize: this.calculateSize(routedRequest) } }; } catch (error) { this.debug.captureError(error, input); throw new Error(`GeminiClientRouter failed: ${error instanceof Error ? error.message : String(error)}`); } } async validateRouting(input, output) { const errors = []; const warnings = []; if (!output.metadata?.routedBy) { errors.push('Output missing routing metadata'); } if (input.model !== output.model) { warnings.push('Model changed during routing'); } return { valid: errors.length === 0, errors, warnings }; } calculateSize(data) { return JSON.stringify(data).length; } } // ==================== 2. 输入格式转换器模块 ==================== class GeminiInputTransformer { id = 'gemini-input-transformer'; type = 'input-transformer'; debug; test; constructor() { this.debug = new ModuleDebugCapture(this.id); this.test = new ModuleUnitTest(this.id, this.validateTransformation.bind(this)); } async process(input) { const startTime = Date.now(); this.debug.captureInput(input.data, input.metadata); try { const geminiRequest = this.transformToGeminiFormat(input.data); this.debug.captureOutput(geminiRequest, input.metadata); return { data: geminiRequest, metadata: input.metadata, debug: { moduleId: this.id, processingTime: Date.now() - startTime, inputSize: this.calculateSize(input.data), outputSize: this.calculateSize(geminiRequest) } }; } catch (error) { this.debug.captureError(error, input); throw new Error(`GeminiInputTransformer failed: ${error instanceof Error ? error.message : String(error)}`); } } transformToGeminiFormat(request) { const requestId = request.metadata?.requestId || 'unknown'; logger_1.logger.debug('Transforming to Gemini format', { messageCount: request.messages?.length || 0, hasTools: !!request.tools, maxTokens: request.max_tokens }, requestId, 'gemini-input-transformer'); if (!request.messages || !Array.isArray(request.messages)) { throw new Error('request.messages must be a non-empty array'); } const geminiRequest = { model: this.extractModelName(request.model), contents: this.convertMessagesToGemini(request.messages, requestId), generationConfig: this.buildGenerationConfig(request), tools: request.tools ? this.convertToolsToGemini(request.tools, requestId) : undefined, functionCallingConfig: request.tools ? { mode: 'ANY' } : undefined }; // 移除undefined字段 Object.keys(geminiRequest).forEach(key => { if (geminiRequest[key] === undefined) { delete geminiRequest[key]; } }); return geminiRequest; } extractModelName(model) { if (!model) { throw new Error('model is required'); } // Zero Hardcode Principle: model patterns must be configurable const allowedPatterns = this.getValidModelPatterns(); const isValidModel = allowedPatterns.some(pattern => pattern.test(model)); if (!isValidModel) { const expectedPatterns = allowedPatterns.map(p => p.source.replace(/\^|\$|\\\./, '')).join(', '); throw new Error(`Unsupported model '${model}'. Expected patterns: ${expectedPatterns}`); } return model.replace(/^google\//, ''); } /** * Get valid model patterns - Zero Hardcode Principle */ getValidModelPatterns() { // In a fully configurable system, this would come from config // For now, use static patterns but mark as configurable return [ /^gemini-1\./, /^gemini-2\./, /^gemini-pro/, /^gemini-ultra/, /^gemini-nano/, /^gemini-flash/ ]; } convertMessagesToGemini(messages, requestId) { const contents = []; for (let i = 0; i < messages.length; i++) { const message = messages[i]; if (!message || typeof message !== 'object') { throw new Error(`Invalid message at index ${i}`); } // 跳过系统消息 if (message.role === 'system') { continue; } const role = message.role === 'assistant' ? 'model' : 'user'; const parts = this.convertContentToParts(message, i, requestId); if (parts.length > 0) { contents.push({ role, parts }); } } if (contents.length === 0) { throw new Error('No valid messages to convert'); } return contents; } convertContentToParts(message, index, requestId) { const parts = []; if (typeof message.content === 'string') { if (message.content.trim()) { parts.push({ text: message.content }); } } else if (Array.isArray(message.content)) { for (const block of message.content) { if (block.type === 'text' && block.text) { parts.push({ text: block.text }); } else if (block.type === 'tool_use') { parts.push({ functionCall: { name: block.name, args: block.input || {} } }); } else if (block.type === 'tool_result') { parts.push({ functionResponse: { name: block.tool_use_id || 'unknown_tool', response: { name: block.tool_use_id || 'unknown_tool', content: block.content || block.result || 'Tool execution completed' } } }); } } } return parts; } buildGenerationConfig(request) { const config = {}; if (request.max_tokens) { config.maxOutputTokens = request.max_tokens; } if (request.temperature !== undefined) { config.temperature = request.temperature; } return Object.keys(config).length > 0 ? config : undefined; } convertToolsToGemini(tools, requestId) { if (!Array.isArray(tools)) { throw new Error('tools must be an array'); } const functionDeclarations = tools.map((tool, index) => { let func; if (tool.function) { func = tool.function; } else if (tool.name) { func = tool; } else { throw new Error(`Invalid tool at index ${index}: missing function or name`); } if (!func.name) { throw new Error(`Invalid tool at index ${index}: missing function name`); } return { name: func.name, description: func.description || '', parameters: func.parameters || func.input_schema || {} }; }); return [{ functionDeclarations }]; } async validateTransformation(input, output) { const errors = []; const warnings = []; if (!output.model) { errors.push('Output missing model field'); } if (!output.contents || !Array.isArray(output.contents)) { errors.push('Output missing or invalid contents field'); } if (input.tools && !output.tools) { warnings.push('Input tools not converted to output'); } return { valid: errors.length === 0, errors, warnings }; } calculateSize(data) { return JSON.stringify(data).length; } } // ==================== 3. 请求预处理器模块 ==================== class GeminiRequestPreprocessor { id = 'gemini-request-preprocessor'; type = 'request-preprocessor'; debug; test; constructor() { this.debug = new ModuleDebugCapture(this.id); this.test = new ModuleUnitTest(this.id, this.validatePreprocessing.bind(this)); } async process(input) { const startTime = Date.now(); this.debug.captureInput(input.data, input.metadata); try { // 请求验证 await this.validateRequest(input.data); // 参数标准化 const normalized = await this.normalizeParameters(input.data); // 安全检查 const secured = await this.applySafetyChecks(normalized); // 性能优化 const optimized = await this.optimizeRequest(secured); this.debug.captureOutput(optimized, input.metadata); return { data: optimized, metadata: input.metadata, debug: { moduleId: this.id, processingTime: Date.now() - startTime, inputSize: this.calculateSize(input.data), outputSize: this.calculateSize(optimized) } }; } catch (error) { this.debug.captureError(error, input); throw new Error(`GeminiRequestPreprocessor failed: ${error instanceof Error ? error.message : String(error)}`); } } async validateRequest(request) { if (!request.model) { throw new Error('Request missing required model field'); } if (!request.contents || !Array.isArray(request.contents)) { throw new Error('Request missing or invalid contents field'); } if (request.contents.length === 0) { throw new Error('Request contents cannot be empty'); } } async normalizeParameters(request) { const normalized = { ...request }; // 标准化生成配置 if (normalized.generationConfig) { // 确保maxOutputTokens在合理范围内 if (normalized.generationConfig.maxOutputTokens) { normalized.generationConfig.maxOutputTokens = Math.min(normalized.generationConfig.maxOutputTokens, 100000 // Gemini最大token限制 ); } // 标准化温度值 if (normalized.generationConfig.temperature !== undefined) { normalized.generationConfig.temperature = Math.max(0, Math.min(2, normalized.generationConfig.temperature)); } } return normalized; } async applySafetyChecks(request) { const secured = { ...request }; // 添加默认安全设置 if (!secured.safetySettings) { secured.safetySettings = [ { category: 'HARM_CATEGORY_HARASSMENT', threshold: 'BLOCK_MEDIUM_AND_ABOVE' }, { category: 'HARM_CATEGORY_HATE_SPEECH', threshold: 'BLOCK_MEDIUM_AND_ABOVE' }, { category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT', threshold: 'BLOCK_MEDIUM_AND_ABOVE' }, { category: 'HARM_CATEGORY_DANGEROUS_CONTENT', threshold: 'BLOCK_MEDIUM_AND_ABOVE' } ]; } return secured; } async optimizeRequest(request) { const optimized = { ...request }; // 移除空字段 Object.keys(optimized).forEach(key => { if (optimized[key] === undefined || optimized[key] === null) { delete optimized[key]; } }); return optimized; } async validatePreprocessing(input, output) { const errors = []; const warnings = []; if (!output.safetySettings) { warnings.push('Output missing safety settings'); } if (input.model !== output.model) { errors.push('Model was modified during preprocessing'); } return { valid: errors.length === 0, errors, warnings }; } calculateSize(data) { return JSON.stringify(data).length; } } // ==================== 4. Provider接口层模块 ==================== class GeminiProviderInterface { config; id = 'gemini-provider-interface'; type = 'provider-interface'; debug; test; constructor(config) { this.config = config; this.debug = new ModuleDebugCapture(this.id); this.test = new ModuleUnitTest(this.id, this.validateInterface.bind(this)); } async process(input) { const startTime = Date.now(); this.debug.captureInput(input.data, input.metadata); try { // 接口标准化 const standardizedRequest = await this.standardizeInterface(input.data); // 认证处理 const authenticatedRequest = await this.processAuthentication(standardizedRequest, input.context); // 端点配置 const configuredRequest = await this.configureEndpoint(authenticatedRequest); this.debug.captureOutput(configuredRequest, input.metadata); return { data: configuredRequest, metadata: input.metadata, debug: { moduleId: this.id, processingTime: Date.now() - startTime, inputSize: this.calculateSize(input.data), outputSize: this.calculateSize(configuredRequest) } }; } catch (error) { this.debug.captureError(error, input); throw new Error(`GeminiProviderInterface failed: ${error instanceof Error ? error.message : String(error)}`); } } async standardizeInterface(request) { // 确保请求符合Gemini API接口标准 const standardized = { ...request, _interface: { type: 'gemini-api', version: 'v1', endpoint: 'generateContent' } }; return standardized; } async processAuthentication(request, context) { const credentials = this.config.authentication.credentials; const apiKey = credentials ? (credentials.apiKey || credentials.api_key) : ''; if (!apiKey) { throw new Error('Gemini API key is required for authentication'); } return { ...request, _auth: { type: 'api-key', key: Array.isArray(apiKey) ? apiKey[0] : apiKey } }; } async configureEndpoint(request) { if (!this.config.endpoint) { throw new Error('GeminiProviderInterface: config.endpoint is required - no default endpoint allowed (Zero Hardcode Principle)'); } const endpoint = this.config.endpoint; return { ...request, _endpoint: { baseUrl: endpoint, path: '/v1/models/generateContent', method: 'POST' } }; } async validateInterface(input, output) { const errors = []; const warnings = []; if (!output._interface) { errors.push('Output missing interface configuration'); } if (!output._auth) { errors.push('Output missing authentication configuration'); } if (!output._endpoint) { errors.push('Output missing endpoint configuration'); } return { valid: errors.length === 0, errors, warnings }; } calculateSize(data) { return JSON.stringify(data).length; } } // ==================== 5. 第三方服务连接模块 ==================== class GeminiThirdPartyServer { config; id = 'gemini-third-party-server'; type = 'third-party-server'; debug; test; genAIClients = []; maxRetries = 3; retryDelay = 1000; constructor(config) { this.config = config; this.debug = new ModuleDebugCapture(this.id); this.test = new ModuleUnitTest(this.id, this.validateConnection.bind(this)); this.initializeClients(); } initializeClients() { const credentials = this.config.authentication.credentials; const apiKey = credentials ? (credentials.apiKey || credentials.api_key) : ''; if (Array.isArray(apiKey)) { this.genAIClients = apiKey.map(key => new genai_1.GoogleGenAI({ apiKey: key })); } else { this.genAIClients = [new genai_1.GoogleGenAI({ apiKey: apiKey })]; } } async process(input) { const startTime = Date.now(); this.debug.captureInput(input.data, input.metadata); try { // 选择客户端 const client = await this.selectClient(); // 执行API调用 const response = await this.executeApiCall(client, input.data); // 响应验证 const validatedResponse = await this.validateResponse(response); this.debug.captureOutput(validatedResponse, input.metadata); return { data: validatedResponse, metadata: input.metadata, debug: { moduleId: this.id, processingTime: Date.now() - startTime, inputSize: this.calculateSize(input.data), outputSize: this.calculateSize(validatedResponse) } }; } catch (error) { this.debug.captureError(error, input); throw new types_1.ProviderError(`GeminiThirdPartyServer failed: ${error instanceof Error ? error.message : String(error)}`, 'gemini', error?.status || 500, error); } } async selectClient() { if (this.genAIClients.length === 0) { throw new Error('No Gemini clients available'); } // 简单轮询选择 const clientIndex = Date.now() % this.genAIClients.length; return this.genAIClients[clientIndex]; } async executeApiCall(client, request) { let lastError; for (let attempt = 0; attempt < this.maxRetries; attempt++) { try { const apiRequest = this.buildApiRequest(request); const response = await Promise.race([ client.models.generateContent(apiRequest), new Promise((_, reject) => setTimeout(() => reject(new Error('Gemini API timeout after 60s')), 60000)) ]); return response; } catch (error) { lastError = error; if (!this.isRetryableError(error) || attempt === this.maxRetries - 1) { break; } await this.waitForRetry(attempt); } } throw lastError; } buildApiRequest(request) { // 构建符合Gemini API标准的请求 const apiRequest = { model: request.model, contents: request.contents }; if (request.generationConfig) { apiRequest.generationConfig = request.generationConfig; } if (request.tools) { apiRequest.tools = request.tools; } if (request.functionCallingConfig) { apiRequest.functionCallingConfig = request.functionCallingConfig; } if (request.safetySettings) { apiRequest.safetySettings = request.safetySettings; } return apiRequest; } async validateResponse(response) { if (!response) { throw new Error('Empty response from Gemini API'); } if (!response.candidates || response.candidates.length === 0) { throw new Error('Gemini response has no candidates'); } return response; } isRetryableError(error) { const status = error?.status || error?.response?.status; const message = error?.message || ''; if (status) { const retryableStatuses = [429, 502, 503, 504]; if (retryableStatuses.includes(status)) { return true; } } const retryablePatterns = [ 'quota', 'rate', 'RESOURCE_EXHAUSTED', 'Too Many Requests', 'temporarily unavailable', 'service unavailable' ]; return retryablePatterns.some(pattern => message.toLowerCase().includes(pattern.toLowerCase())); } async waitForRetry(attempt) { const delay = this.retryDelay * Math.pow(2, attempt); await new Promise(resolve => setTimeout(resolve, delay)); } async validateConnection(input, output) { const errors = []; const warnings = []; if (!output.candidates) { errors.push('Output missing candidates field'); } if (output.candidates && output.candidates.length === 0) { errors.push('Output has empty candidates array'); } return { valid: errors.length === 0, errors, warnings }; } calculateSize(data) { return JSON.stringify(data).length; } } // ==================== 支持类实现 ==================== class ModuleDebugCapture { moduleId; constructor(moduleId) { this.moduleId = moduleId; } captureInput(data, metadata) { return { id: `${this.moduleId}_input_${Date.now()}`, timestamp: new Date().toISOString(), type: 'input', module: this.moduleId, data: this.sanitizeData(data), metadata, size: this.calculateDataSize(data) }; } captureOutput(data, metadata) { return { id: `${this.moduleId}_output_${Date.now()}`, timestamp: new Date().toISOString(), type: 'output', module: this.moduleId, data: this.sanitizeData(data), metadata, size: this.calculateDataSize(data) }; } captureError(error, context) { return { id: `${this.moduleId}_error_${Date.now()}`, timestamp: new Date().toISOString(), type: 'error', module: this.moduleId, data: { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, context }, metadata: context.metadata || {}, size: this.calculateDataSize(error) }; } sanitizeData(data) { if (typeof data !== 'object') return data; const sanitized = { ...data }; // 移除敏感字段 ['apiKey', 'api_key', 'token', 'authorization', '_auth'].forEach(key => { if (sanitized[key]) { sanitized[key] = '[REDACTED]'; } }); return sanitized; } calculateDataSize(data) { try { return JSON.stringify(data).length; } catch { return 0; } } } class ModuleUnitTest { moduleId; validator; constructor(moduleId, validator) { this.moduleId = moduleId; this.validator = validator; } async run() { const startTime = Date.now(); const errors = []; try { // 运行基础测试用例 const testCases = this.generateTestCases(); for (const testCase of testCases) { try { const result = await this.validator(testCase.input, testCase.expectedOutput); if (!result.valid) { errors.push({ testCase: testCase.name, errors: result.errors, warnings: result.warnings }); } } catch (error) { errors.push({ testCase: testCase.name, error: error instanceof Error ? error.message : String(error) }); } } return { passed: errors.length === 0, duration: Date.now() - startTime, errors }; } catch (error) { return { passed: false, duration: Date.now() - startTime, errors: [{ error: error instanceof Error ? error.message : String(error) }] }; } } async validate(input, output) { return this.validator(input, output); } generateTestCases() { return [ { name: 'basic-validation', input: { test: true }, expectedOutput: { test: true, processed: true } } ]; } } // ==================== 标准流水线客户端主类 ==================== class GeminiStandardPipelineClient { config; providerId; modules = new Map(); pipeline = []; constructor(config, providerId = 'gemini-standard-pipeline') { this.config = config; this.providerId = providerId; this.initializePipeline(); } initializePipeline() { // 初始化11个模块(这里只实现了前5个作为示例) const clientRouter = new GeminiClientRouter(); const inputTransformer = new GeminiInputTransformer(); const requestPreprocessor = new GeminiRequestPreprocessor(); const providerInterface = new GeminiProviderInterface(this.config); const thirdPartyServer = new GeminiThirdPartyServer(this.config); // 注册模块 this.modules.set(clientRouter.id, clientRouter); this.modules.set(inputTransformer.id, inputTransformer); this.modules.set(requestPreprocessor.id, requestPreprocessor); this.modules.set(providerInterface.id, providerInterface); this.modules.set(thirdPartyServer.id, thirdPartyServer); // 构建流水线 this.pipeline = [ clientRouter, inputTransformer, requestPreprocessor, providerInterface, thirdPartyServer ]; logger_1.logger.info('Gemini Standard Pipeline initialized', { providerId: this.providerId, moduleCount: this.modules.size, pipelineLength: this.pipeline.length }); } async processRequest(request) { const requestId = request.metadata?.requestId || `req_${Date.now()}`; const startTime = Date.now(); logger_1.logger.info('Starting Gemini Standard Pipeline processing', { requestId, moduleCount: this.pipeline.length }); try { let currentData = request; const metadata = { requestId, timestamp: startTime, source: 'anthropic', target: 'gemini' }; const context = { session: { sessionId: `session_${Date.now()}`, userId: request.metadata?.userId }, routing: { category: 'default', provider: 'gemini', model: request.model, originalModel: request.model }, transformation: { sourceFormat: 'anthropic', targetFormat: 'gemini', requestId } }; // 通过流水线处理请求 for (let i = 0; i < this.pipeline.length; i++) { const module = this.pipeline[i]; const moduleInput = { data: currentData, metadata, context }; logger_1.logger.debug(`Processing module ${i + 1}/${this.pipeline.length}: ${module.id}`, { requestId, moduleType: module.type }); const moduleOutput = await module.process(moduleInput); currentData = moduleOutput.data; logger_1.logger.debug(`Module ${module.id} completed`, { requestId, processingTime: moduleOutput.debug?.processingTime, inputSize: moduleOutput.debug?.inputSize, outputSize: moduleOutput.debug?.outputSize }); } // 转换最终响应 const finalResponse = this.convertToBaseResponse(currentData, request); const totalTime = Date.now() - startTime; logger_1.logger.info('Gemini Standard Pipeline processing completed', { requestId, totalTime, moduleCount: this.pipeline.length }); return finalResponse; } catch (error) { logger_1.logger.error('Gemini Standard Pipeline processing failed', error, requestId); throw error; } } convertToBaseResponse(geminiResponse, originalRequest) { // 简化的响应转换 if (!geminiResponse.candidates || geminiResponse.candidates.length === 0) { throw new Error('Invalid Gemini response: no candidates'); } const candidate = geminiResponse.candidates[0]; const content = []; if (candidate.content && candidate.content.parts) { for (const part of candidate.content.parts) { if (part.text) { content.push({ type: 'text', text: part.text }); } else if (part.functionCall) { content.push({ type: 'tool_use', id: `toolu_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, name: part.functionCall.name, input: part.functionCall.args || {} }); } } } if (content.length === 0) { content.push({ type: 'text', text: 'Response generated but content was empty.' }); } // 映射结束原因 const stopReason = this.mapFinishReason(candidate.finishReason); return { id: `msg_${Date.now()}`, content, model: originalRequest.model, role: 'assistant', stop_reason: stopReason, stop_sequence: null, usage: { input_tokens: geminiResponse.usageMetadata?.promptTokenCount || 0, output_tokens: geminiResponse.usageMetadata?.candidatesTokenCount || 0 } }; } mapFinishReason(finishReason) { const mapping = { 'STOP': 'end_turn', 'MAX_TOKENS': 'max_tokens', 'SAFETY': 'stop_sequence', 'RECITATION': 'stop_sequence', 'OTHER': 'end_turn' }; return mapping[finishReason] || 'end_turn'; } async runUnitTests() { const results = []; for (const [moduleId, module] of this.modules) { logger_1.logger.info(`Running unit tests for module: ${moduleId}`); try { const result = await module.test.run(); results.push(result); if (result.passed) { logger_1.logger.info(`Unit tests passed for module: ${moduleId}`); } else { logger_1.logger.warn(`Unit tests failed for module: ${moduleId}`, { errors: result.errors }); } } catch (error) { logger_1.logger.error(`Unit test execution failed for module: ${moduleId}`, error); results.push({ passed: false, duration: 0, errors: [{ error: error instanceof Error ? error.message : String(error) }] }); } } return results; } getModuleStats() { const stats = {}; for (const [moduleId, module] of this.modules) { stats[moduleId] = { type: module.type, id: module.id }; } return { totalModules: this.modules.size, pipelineLength: this.pipeline.length, modules: stats }; } } exports.GeminiStandardPipelineClient = GeminiStandardPipelineClient; //# sourceMappingURL=standard-pipeline-client.js.map