route-claudecode
Version:
Advanced routing and transformation system for Claude Code outputs to multiple AI providers
452 lines • 18.7 kB
JavaScript
"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