route-claudecode
Version:
Advanced routing and transformation system for Claude Code outputs to multiple AI providers
316 lines • 12.3 kB
JavaScript
;
/**
* Response Fixer - 通用的响应修复机制
* 在累积完整响应后执行全面修复,确保工具调用和其他结构的正确性
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.fixResponse = fixResponse;
exports.quickFixResponse = quickFixResponse;
const logger_1 = require("./logger");
/**
* 全面修复响应 - 处理各种常见问题
*/
function fixResponse(response, requestId) {
const fixes = [];
const fixedContent = [];
logger_1.logger.info('Starting comprehensive response fixing', {
originalContentBlocks: response.content.length,
contentTypes: response.content.map(c => c.type)
}, requestId);
for (const block of response.content) {
if (block.type === 'tool_use') {
const fixedToolBlock = fixToolCallBlock(block, requestId);
if (fixedToolBlock.fixes.length > 0) {
fixes.push(...fixedToolBlock.fixes);
}
fixedContent.push(fixedToolBlock.block);
}
else if (block.type === 'text') {
const fixedTextBlock = fixTextBlock(block, requestId);
if (fixedTextBlock.fixes.length > 0) {
fixes.push(...fixedTextBlock.fixes);
}
// 如果文本中发现工具调用,则拆分
if (fixedTextBlock.extractedTools.length > 0) {
// ⚠️ 关键修复:完全跳过包含工具调用的原始文本块,避免显示剩余内容
// 不添加修复后的文本块,只添加提取的工具调用
// 添加提取的工具调用块
fixedContent.push(...fixedTextBlock.extractedTools);
fixes.push(`extracted_${fixedTextBlock.extractedTools.length}_tools_from_text`);
fixes.push('skipped_original_text_with_tool_calls');
}
else {
fixedContent.push(fixedTextBlock.block);
}
}
else {
// 未知类型,直接保留
fixedContent.push(block);
}
}
// 验证修复结果
const validation = validateFixedResponse(fixedContent, requestId);
if (validation.issues.length > 0) {
fixes.push(...validation.fixes);
logger_1.logger.warn('Response validation found issues after fixing', {
issues: validation.issues,
additionalFixes: validation.fixes
}, requestId);
}
logger_1.logger.info('Response fixing completed', {
originalBlocks: response.content.length,
fixedBlocks: fixedContent.length,
fixesApplied: fixes,
toolBlocks: fixedContent.filter(c => c.type === 'tool_use').length,
textBlocks: fixedContent.filter(c => c.type === 'text').length
}, requestId);
return {
content: fixedContent,
usage: response.usage,
fixes_applied: fixes
};
}
/**
* 修复工具调用块
*/
function fixToolCallBlock(block, requestId) {
const fixes = [];
const fixedBlock = { ...block };
// 修复1: 空的工具输入
if (!fixedBlock.input || (typeof fixedBlock.input === 'object' && Object.keys(fixedBlock.input).length === 0)) {
// 尝试从ID或其他地方推断参数
if (fixedBlock.name && typeof block.input === 'string') {
try {
fixedBlock.input = JSON.parse(block.input);
fixes.push('parsed_string_input_to_object');
}
catch (error) {
logger_1.logger.debug('Failed to parse string input as JSON', {
toolName: fixedBlock.name,
input: block.input
}, requestId);
fixedBlock.input = {};
fixes.push('set_empty_input_object');
}
}
else {
fixedBlock.input = {};
fixes.push('ensured_input_object');
}
}
// 修复2: 确保ID存在
if (!fixedBlock.id) {
fixedBlock.id = `fixed_${Date.now()}_${Math.random().toString(36).slice(2)}`;
fixes.push('generated_tool_id');
}
// 修复3: 确保名称存在
if (!fixedBlock.name) {
fixedBlock.name = 'UnknownTool';
fixes.push('set_default_tool_name');
}
logger_1.logger.debug('Tool call block fixed', {
toolName: fixedBlock.name,
hasInput: Object.keys(fixedBlock.input).length > 0,
inputKeys: Object.keys(fixedBlock.input),
fixesApplied: fixes
}, requestId);
return { block: fixedBlock, fixes };
}
/**
* 修复文本块并提取嵌入的工具调用
*/
function fixTextBlock(block, requestId) {
const fixes = [];
const extractedTools = [];
let fixedText = block.text || '';
// 修复1: 提取嵌入的工具调用
// 使用更复杂的匹配来处理嵌套的JSON对象
const toolCallMatches = extractToolCallsWithBalancedBraces(fixedText);
for (const match of toolCallMatches) {
const toolName = match[1];
const toolArgsStr = match[2];
try {
// CRITICAL FIX: Properly escape control characters in JSON string before parsing
// Handle common control characters that break JSON parsing
let sanitizedArgsStr = toolArgsStr
.replace(/\n/g, '\\n') // Escape newlines
.replace(/\r/g, '\\r') // Escape carriage returns
.replace(/\t/g, '\\t') // Escape tabs
.replace(/\f/g, '\\f') // Escape form feeds
.replace(/\x08/g, '\\b') // Escape backspaces (correct pattern)
.replace(/\v/g, '\\v') // Escape vertical tabs
.replace(/\0/g, '\\0') // Escape null characters
.replace(/[\x00-\x1F\x7F-\x9F]/g, (match) => {
// Escape any remaining control characters
return '\\u' + ('0000' + match.charCodeAt(0).toString(16)).slice(-4);
});
logger_1.logger.debug('Sanitized tool arguments for JSON parsing', {
toolName,
originalLength: toolArgsStr.length,
sanitizedLength: sanitizedArgsStr.length,
hasControlChars: toolArgsStr !== sanitizedArgsStr,
originalPreview: toolArgsStr.slice(0, 100),
sanitizedPreview: sanitizedArgsStr.slice(0, 100)
}, requestId);
const toolInput = JSON.parse(sanitizedArgsStr);
const extractedTool = {
type: 'tool_use',
id: `extracted_${Date.now()}_${Math.random().toString(36).slice(2)}`,
name: toolName,
input: toolInput
};
extractedTools.push(extractedTool);
// 从文本中移除这个工具调用
fixedText = fixedText.replace(match[0], '').trim();
fixes.push(`extracted_tool_${toolName}`);
logger_1.logger.info('Extracted tool call from text', {
toolName,
toolInput,
originalMatch: match[0]
}, requestId);
}
catch (error) {
logger_1.logger.warn('Failed to parse extracted tool arguments', {
toolName,
argsString: toolArgsStr,
error: error instanceof Error ? error.message : String(error)
}, requestId);
}
}
// 修复2: 清理多余的空白和换行
if (fixedText !== block.text) {
fixedText = fixedText.replace(/\n{3,}/g, '\n\n').trim();
fixes.push('cleaned_whitespace');
}
return {
block: { type: 'text', text: fixedText },
fixes,
extractedTools
};
}
/**
* 验证修复后的响应
*/
function validateFixedResponse(content, requestId) {
const issues = [];
const fixes = [];
// 验证1: 检查重复的工具ID
const toolIds = new Set();
const duplicateIds = [];
content.forEach(block => {
if (block.type === 'tool_use') {
if (toolIds.has(block.id)) {
duplicateIds.push(block.id);
}
else {
toolIds.add(block.id);
}
}
});
if (duplicateIds.length > 0) {
issues.push(`duplicate_tool_ids: ${duplicateIds.join(', ')}`);
fixes.push('detected_duplicate_tool_ids');
}
// 验证2: 检查空的工具输入
const emptyToolInputs = content.filter(block => block.type === 'tool_use' &&
(!block.input || Object.keys(block.input).length === 0)).length;
if (emptyToolInputs > 0) {
issues.push(`empty_tool_inputs: ${emptyToolInputs} tools`);
fixes.push('detected_empty_tool_inputs');
}
// 验证3: 检查空的文本块
const emptyTextBlocks = content.filter(block => block.type === 'text' &&
(!block.text || block.text.trim().length === 0)).length;
if (emptyTextBlocks > 0) {
fixes.push('detected_empty_text_blocks');
}
return { issues, fixes };
}
/**
* 使用平衡括号匹配提取工具调用
* 正确处理嵌套的JSON对象
*/
function extractToolCallsWithBalancedBraces(text) {
const matches = [];
const toolCallRegex = /Tool call:\s*(\w+)\s*\(/g;
let match;
while ((match = toolCallRegex.exec(text)) !== null) {
const toolName = match[1];
const startPos = match.index;
const openParenPos = match.index + match[0].length - 1; // Position of the opening '('
// Find the matching closing parenthesis using bracket counting
let braceCount = 0;
let currentPos = openParenPos + 1; // Start after the opening '('
let jsonStart = -1;
let jsonEnd = -1;
// Skip whitespace to find the opening brace
while (currentPos < text.length && /\s/.test(text[currentPos])) {
currentPos++;
}
if (currentPos >= text.length || text[currentPos] !== '{') {
continue; // No JSON object found
}
jsonStart = currentPos;
braceCount = 1; // We found the opening brace
currentPos++;
// Find the matching closing brace
while (currentPos < text.length && braceCount > 0) {
const char = text[currentPos];
if (char === '{') {
braceCount++;
}
else if (char === '}') {
braceCount--;
}
else if (char === '"') {
// Skip string content to avoid counting braces inside strings
currentPos++; // Move past the opening quote
while (currentPos < text.length) {
if (text[currentPos] === '"' && text[currentPos - 1] !== '\\') {
break; // Found closing quote (not escaped)
}
currentPos++;
}
}
currentPos++;
}
if (braceCount === 0) {
jsonEnd = currentPos - 1; // Position of the closing brace
// Find the closing parenthesis
let parenPos = jsonEnd + 1;
while (parenPos < text.length && /\s/.test(text[parenPos])) {
parenPos++;
}
if (parenPos < text.length && text[parenPos] === ')') {
const fullMatch = text.substring(startPos, parenPos + 1);
const jsonString = text.substring(jsonStart, jsonEnd + 1);
matches.push([fullMatch, toolName, jsonString]);
}
}
}
return matches;
}
/**
* 简化版修复 - 只处理最关键的问题
*/
function quickFixResponse(response, requestId) {
const fixes = [];
const fixedContent = response.content.map(block => {
if (block.type === 'tool_use') {
const fixedBlock = { ...block };
// 只修复空的输入对象
if (!fixedBlock.input || Object.keys(fixedBlock.input).length === 0) {
fixedBlock.input = {};
fixes.push('ensured_tool_input_object');
}
return fixedBlock;
}
return block;
});
return {
content: fixedContent,
usage: response.usage,
fixes_applied: fixes
};
}
//# sourceMappingURL=response-fixer.js.map