UNPKG

route-claudecode

Version:

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

452 lines 18.7 kB
"use strict"; /** * Universal Pipeline Debug System for Claude Code Router * Specialized for Gemini Tool Calling Pipeline Debug & Analysis * * Project owner: Jason Zhang * * This system provides comprehensive debugging infrastructure for ANY pipeline type: * - API routing pipelines * - Data processing pipelines * - CI/CD workflow pipelines * - Message queue pipelines * - Transformation pipelines * * Core Features: * - Universal debug hook integration * - Hierarchical data capture & storage * - Pipeline replay capabilities * - Comprehensive testing matrix generation * - Generic problem isolation framework */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.UniversalPipelineDebugger = void 0; const promises_1 = __importDefault(require("fs/promises")); const path_1 = __importDefault(require("path")); const logger_1 = require("../utils/logger"); class UniversalPipelineDebugger { debugDataDir; currentSession; debugEnabled = false; constructor(debugDataDir = './debug-data') { this.debugDataDir = debugDataDir; } /** * Initialize debug session with --debug flag support * Non-intrusive: only activates when explicitly enabled */ async initializeDebugSession(pipelineType, testScriptName, enableDebug = process.argv.includes('--debug')) { this.debugEnabled = enableDebug; if (!this.debugEnabled) { logger_1.logger.debug('Pipeline debug disabled, running normal operation'); return ''; } const sessionId = `${pipelineType}-${testScriptName}-${Date.now()}`; this.currentSession = { sessionId, pipelineType, testScriptName, startTime: Date.now(), steps: [] }; // Create hierarchical storage directory const sessionDir = await this.createSessionDirectory(sessionId); logger_1.logger.info('Universal Pipeline Debug Session Initialized', { sessionId, pipelineType, testScriptName, storageDir: sessionDir, debugEnabled: this.debugEnabled }); return sessionId; } /** * Capture pipeline step data with complete input/output preservation * Works for ANY pipeline step type */ async captureStepData(stepId, stepName, description, inputData, outputData, errorData, metadata) { if (!this.debugEnabled || !this.currentSession) { return; } const stepStartTime = Date.now(); const step = { stepId, stepName, description, inputData: this.sanitizeData(inputData), outputData: this.sanitizeData(outputData), errorData: this.sanitizeData(errorData), timestamp: stepStartTime, metadata: metadata || {} }; // Calculate duration if this is completion of a step const existingStepIndex = this.currentSession.steps.findIndex(s => s.stepId === stepId); if (existingStepIndex >= 0) { step.duration = stepStartTime - this.currentSession.steps[existingStepIndex].timestamp; this.currentSession.steps[existingStepIndex] = step; } else { this.currentSession.steps.push(step); } // Store step data to disk for replay capability await this.persistStepData(step); logger_1.logger.debug('Pipeline step data captured', { sessionId: this.currentSession.sessionId, stepId, stepName, hasInputData: !!inputData, hasOutputData: !!outputData, hasErrorData: !!errorData, duration: step.duration }); } /** * Generate comprehensive testing matrix for pipeline validation * Adapts to different pipeline types automatically */ async generateTestingMatrix(pipelineType, currentRules) { const testMatrix = []; switch (pipelineType) { case 'gemini-tool-calling': testMatrix.push(...this.generateGeminiToolCallTestMatrix()); break; case 'openai-tool-calling': testMatrix.push(...this.generateOpenAIToolCallTestMatrix()); break; case 'anthropic-processing': testMatrix.push(...this.generateAnthropicProcessingTestMatrix()); break; default: testMatrix.push(...this.generateGenericPipelineTestMatrix(pipelineType, currentRules)); } // Save test matrix to disk await this.saveTestMatrix(pipelineType, testMatrix); logger_1.logger.info('Testing matrix generated', { pipelineType, totalTests: testMatrix.length, criticalTests: testMatrix.filter(t => t.priority === 'critical').length, highPriorityTests: testMatrix.filter(t => t.priority === 'high').length }); return testMatrix; } /** * Create pipeline replay mechanism for any captured data * Enables precise problem reproduction */ async replayPipelineStep(stepId, sessionId, modifiedInput) { const targetSessionId = sessionId || this.currentSession?.sessionId; if (!targetSessionId) { throw new Error('No session available for replay'); } const stepData = await this.loadStepData(targetSessionId, stepId); if (!stepData) { throw new Error(`Step data not found: ${stepId}`); } logger_1.logger.info('Replaying pipeline step', { sessionId: targetSessionId, stepId: stepData.stepId, stepName: stepData.stepName, usingModifiedInput: !!modifiedInput }); // Use modified input if provided, otherwise use original const replayInput = modifiedInput || stepData.inputData; // Return the input data for replay by the calling system return { stepData, replayInput, originalOutput: stepData.outputData, metadata: stepData.metadata }; } /** * Analyze pipeline for problem isolation * Generic framework that works across pipeline types */ async analyzePipelineProblems(sessionId) { const targetSessionId = sessionId || this.currentSession?.sessionId; if (!targetSessionId) { throw new Error('No session available for analysis'); } const session = await this.loadSession(targetSessionId); if (!session) { throw new Error(`Session not found: ${targetSessionId}`); } const problemNodes = []; const recommendedTests = []; const nextSteps = []; // Identify failing nodes for (const step of session.steps) { if (step.errorData || !step.outputData) { problemNodes.push(step.stepId); } } // Determine isolation strategy based on pipeline type and failures let isolationStrategy = 'sequential-step-isolation'; if (problemNodes.length === 0) { isolationStrategy = 'output-validation-focused'; nextSteps.push('verify-output-format-compliance'); nextSteps.push('check-downstream-processing'); } else if (problemNodes.length === 1) { isolationStrategy = 'single-node-deep-analysis'; recommendedTests.push(`test-step-${problemNodes[0]}-isolated`); nextSteps.push(`debug-${problemNodes[0]}-input-validation`); nextSteps.push(`debug-${problemNodes[0]}-processing-logic`); } else { isolationStrategy = 'multi-node-dependency-analysis'; recommendedTests.push('test-pipeline-dependencies'); nextSteps.push('map-inter-step-dependencies'); nextSteps.push('test-individual-steps-isolation'); } logger_1.logger.info('Pipeline problem analysis completed', { sessionId: targetSessionId, problemNodesCount: problemNodes.length, isolationStrategy, recommendedTestsCount: recommendedTests.length }); return { problemNodes, recommendedTests, isolationStrategy, nextSteps }; } /** * Complete debug session and generate comprehensive summary */ async completeSession() { if (!this.currentSession || !this.debugEnabled) { return null; } this.currentSession.endTime = Date.now(); const summary = { totalSteps: this.currentSession.steps.length, successfulSteps: this.currentSession.steps.filter(s => !s.errorData && s.outputData).length, failedSteps: this.currentSession.steps.filter(s => s.errorData || !s.outputData).length, totalDuration: this.currentSession.endTime - this.currentSession.startTime, dataCaptureSummary: { inputDataSize: this.calculateTotalDataSize(this.currentSession.steps, 'inputData'), outputDataSize: this.calculateTotalDataSize(this.currentSession.steps, 'outputData'), intermediateDataCount: this.currentSession.steps.length, replayDataAvailable: true, storageLocation: await this.getSessionDirectory(this.currentSession.sessionId) } }; // Identify failure point const failedStep = this.currentSession.steps.find(s => s.errorData); if (failedStep) { summary.failurePoint = failedStep.stepId; summary.errorDetails = failedStep.errorData; } this.currentSession.summary = summary; // Persist complete session await this.saveSession(this.currentSession); logger_1.logger.info('Pipeline debug session completed', { sessionId: this.currentSession.sessionId, ...summary }); return summary; } // Private helper methods generateGeminiToolCallTestMatrix() { return [ { testId: 'gemini-anthropic-format-tools', testName: 'Gemini工具调用-Anthropic格式工具定义', pipelineType: 'gemini-tool-calling', inputVariations: [ { toolFormat: 'anthropic', toolCount: 1, toolType: 'simple' }, { toolFormat: 'anthropic', toolCount: 2, toolType: 'complex' }, { toolFormat: 'anthropic', toolCount: 1, toolType: 'nested-schema' } ], expectedOutputs: [ { hasToolConfig: true, toolConfigMode: 'AUTO', hasAllowedFunctionNames: true }, { stopReason: 'tool_use', contentType: 'tool_use' } ], testConditions: { model: 'gemini-2.5-flash', temperature: 0.1 }, priority: 'critical' }, { testId: 'gemini-openai-format-tools', testName: 'Gemini工具调用-OpenAI格式工具定义', pipelineType: 'gemini-tool-calling', inputVariations: [ { toolFormat: 'openai', toolCount: 1, toolType: 'function' }, { toolFormat: 'openai', toolCount: 2, toolType: 'mixed' } ], expectedOutputs: [ { hasToolConfig: true, toolConfigMode: 'AUTO', hasAllowedFunctionNames: true }, { stopReason: 'tool_use', contentType: 'tool_use' } ], testConditions: { model: 'gemini-2.5-pro', temperature: 0 }, priority: 'critical' }, { testId: 'gemini-tool-config-modes', testName: 'Gemini工具配置模式测试', pipelineType: 'gemini-tool-calling', inputVariations: [ { toolConfigMode: 'AUTO' }, { toolConfigMode: 'ANY' }, { toolConfigMode: 'NONE' } ], expectedOutputs: [ { toolBehavior: 'conditional' }, { toolBehavior: 'forced' }, { toolBehavior: 'disabled' } ], testConditions: { model: 'gemini-2.5-flash' }, priority: 'high' } ]; } generateOpenAIToolCallTestMatrix() { return [ { testId: 'openai-tool-choice-auto', testName: 'OpenAI工具调用-自动选择模式', pipelineType: 'openai-tool-calling', inputVariations: [ { toolChoice: 'auto', toolCount: 1 }, { toolChoice: 'auto', toolCount: 3 } ], expectedOutputs: [ { finishReason: 'tool_calls', hasToolCalls: true } ], testConditions: { model: 'gpt-4' }, priority: 'critical' } ]; } generateAnthropicProcessingTestMatrix() { return [ { testId: 'anthropic-tool-use-processing', testName: 'Anthropic工具使用处理', pipelineType: 'anthropic-processing', inputVariations: [ { contentType: 'tool_use', toolCount: 1 }, { contentType: 'tool_use', toolCount: 2 } ], expectedOutputs: [ { stopReason: 'tool_use', hasContent: true } ], testConditions: { model: 'claude-3-5-sonnet-20241022' }, priority: 'high' } ]; } generateGenericPipelineTestMatrix(pipelineType, rules) { return [ { testId: `${pipelineType}-basic-flow`, testName: `${pipelineType}基础流程测试`, pipelineType, inputVariations: [{ basicInput: true }], expectedOutputs: [{ basicOutput: true }], testConditions: {}, priority: 'high' } ]; } async createSessionDirectory(sessionId) { const sessionDir = path_1.default.join(this.debugDataDir, sessionId); await promises_1.default.mkdir(sessionDir, { recursive: true }); // Create subdirectories for organized storage await promises_1.default.mkdir(path_1.default.join(sessionDir, 'steps'), { recursive: true }); await promises_1.default.mkdir(path_1.default.join(sessionDir, 'replay'), { recursive: true }); await promises_1.default.mkdir(path_1.default.join(sessionDir, 'analysis'), { recursive: true }); return sessionDir; } async getSessionDirectory(sessionId) { return path_1.default.join(this.debugDataDir, sessionId); } async persistStepData(step) { if (!this.currentSession) return; const stepFile = path_1.default.join(this.debugDataDir, this.currentSession.sessionId, 'steps', `${step.stepId}.json`); await promises_1.default.writeFile(stepFile, JSON.stringify(step, null, 2)); } async loadStepData(sessionId, stepId) { try { const stepFile = path_1.default.join(this.debugDataDir, sessionId, 'steps', `${stepId}.json`); const data = await promises_1.default.readFile(stepFile, 'utf-8'); return JSON.parse(data); } catch (error) { logger_1.logger.error('Failed to load step data', { sessionId, stepId, error: error instanceof Error ? error.message : String(error) }); return null; } } async saveSession(session) { const sessionFile = path_1.default.join(this.debugDataDir, session.sessionId, 'session.json'); await promises_1.default.writeFile(sessionFile, JSON.stringify(session, null, 2)); } async loadSession(sessionId) { try { const sessionFile = path_1.default.join(this.debugDataDir, sessionId, 'session.json'); const data = await promises_1.default.readFile(sessionFile, 'utf-8'); return JSON.parse(data); } catch (error) { logger_1.logger.error('Failed to load session', { sessionId, error: error instanceof Error ? error.message : String(error) }); return null; } } async saveTestMatrix(pipelineType, testMatrix) { const matrixFile = path_1.default.join(this.debugDataDir, `test-matrix-${pipelineType}-${Date.now()}.json`); await promises_1.default.writeFile(matrixFile, JSON.stringify(testMatrix, null, 2)); } sanitizeData(data) { if (!data) return data; // Remove sensitive information const sanitized = JSON.parse(JSON.stringify(data)); // Remove API keys and other sensitive data this.removeSensitiveFields(sanitized); return sanitized; } removeSensitiveFields(obj, visited = new Set()) { if (!obj || typeof obj !== 'object' || visited.has(obj)) return; visited.add(obj); const sensitiveKeys = ['apiKey', 'api_key', 'token', 'password', 'secret', 'auth']; for (const key in obj) { if (sensitiveKeys.some(sensitive => key.toLowerCase().includes(sensitive))) { obj[key] = '[REDACTED]'; } else if (typeof obj[key] === 'object') { this.removeSensitiveFields(obj[key], visited); } } } calculateTotalDataSize(steps, field) { return steps.reduce((total, step) => { const data = step[field]; if (data) { return total + JSON.stringify(data).length; } return total; }, 0); } } exports.UniversalPipelineDebugger = UniversalPipelineDebugger; // Export debug wrapper functions for easy integration // export function createDebugHook(pipelineType: string, stepId: string) { // return async (debugger: UniversalPipelineDebugger, inputData: any, outputData?: any, errorData?: any): Promise<void> => { // await debugger.captureStepData( // stepId, // `${pipelineType}-${stepId}`, // `Debug hook for ${stepId}`, // inputData, // outputData, // errorData // ); // }; // } exports.default = UniversalPipelineDebugger; //# sourceMappingURL=universal-pipeline-debug.js.map