route-claudecode
Version:
Advanced routing and transformation system for Claude Code outputs to multiple AI providers
370 lines • 13.1 kB
JavaScript
;
/**
* 统一工具调用解析模块
* 为流式和非流式响应提供一致的工具调用解析和ID标准化
*
* 设计目标:
* 1. 统一工具ID格式 (toolu_前缀)
* 2. 统一参数解析逻辑
* 3. 统一错误处理
* 4. 消除流式/非流式差异
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.StreamingToolCallParser = void 0;
exports.standardizeToolId = standardizeToolId;
exports.generateTooluId = generateTooluId;
exports.parseOpenAIToolCalls = parseOpenAIToolCalls;
exports.validateToolCall = validateToolCall;
exports.convertToAnthropicToolCall = convertToAnthropicToolCall;
exports.extractToolCallsFromText = extractToolCallsFromText;
const logger_1 = require("./logger");
/**
* 统一工具ID格式标准化
*/
function standardizeToolId(originalId) {
if (!originalId) {
return generateTooluId();
}
// 如果已经是正确的toolu_格式,直接返回
if (originalId.startsWith('toolu_')) {
return originalId;
}
// 如果是其他格式(call_, tool_等),生成新的toolu_格式ID
logger_1.logger.debug('Standardizing tool ID format', {
originalId,
action: 'id_format_standardization'
});
return generateTooluId();
}
/**
* 生成标准的toolu_格式ID
*/
function generateTooluId() {
const timestamp = Date.now();
const random = Math.random().toString(36).substr(2, 8);
return `toolu_${timestamp}_${random}`;
}
/**
* 统一工具调用解析器 - 处理OpenAI SDK格式,增强容错性
*/
function parseOpenAIToolCalls(toolCalls) {
if (!Array.isArray(toolCalls)) {
if (toolCalls === null || toolCalls === undefined) {
return [];
}
// 尝试将单个工具调用转换为数组
toolCalls = [toolCalls];
}
if (toolCalls.length === 0) {
return [];
}
const results = toolCalls.map((toolCall, index) => {
// 增强容错:处理各种可能的工具调用格式
if (!toolCall || typeof toolCall !== 'object') {
logger_1.logger.warn('Invalid tool call object', { toolCall, index });
return null;
}
const standardId = standardizeToolId(toolCall.id);
// 多种名称获取方式
const toolName = toolCall.function?.name ||
toolCall.name ||
toolCall.tool_name ||
`tool_${index}`;
let parsedInput = {};
// 处理不同格式的参数 - 增强容错性
if (toolCall.function?.arguments) {
// OpenAI SDK格式: function.arguments
const args = toolCall.function.arguments;
if (typeof args === 'string') {
try {
parsedInput = JSON.parse(args);
}
catch (e) {
logger_1.logger.debug('Failed to parse JSON arguments, trying fallback', {
toolName,
arguments: args,
error: e instanceof Error ? e.message : String(e)
});
// 尝试修复常见的JSON问题
try {
const fixedArgs = args.replace(/'/g, '"').replace(/([{,]\s*)(\w+):/g, '$1"$2":');
parsedInput = JSON.parse(fixedArgs);
logger_1.logger.debug('Successfully fixed and parsed arguments', { toolName });
}
catch (e2) {
// 最后的降级处理:尝试提取键值对
parsedInput = extractKeyValuePairs(args) || {};
if (Object.keys(parsedInput).length === 0) {
// 完全无法解析,作为单个字符串参数
parsedInput = { content: args };
}
}
}
}
else if (typeof args === 'object' && args !== null) {
parsedInput = args;
}
}
else if (toolCall.input) {
// 直接input格式
if (typeof toolCall.input === 'object' && toolCall.input !== null) {
parsedInput = toolCall.input;
}
else if (typeof toolCall.input === 'string') {
try {
parsedInput = JSON.parse(toolCall.input);
}
catch (e) {
parsedInput = { content: toolCall.input };
}
}
}
else if (toolCall.arguments) {
// 直接arguments格式
if (typeof toolCall.arguments === 'string') {
try {
parsedInput = JSON.parse(toolCall.arguments);
}
catch (e) {
parsedInput = extractKeyValuePairs(toolCall.arguments) || { content: toolCall.arguments };
}
}
else if (typeof toolCall.arguments === 'object' && toolCall.arguments !== null) {
parsedInput = toolCall.arguments;
}
}
else if (toolCall.parameters) {
// parameters格式(某些API可能使用)
parsedInput = typeof toolCall.parameters === 'object' ? toolCall.parameters : {};
}
const result = {
type: 'tool_use',
id: standardId,
name: toolName,
input: parsedInput
};
logger_1.logger.debug('Parsed tool call with enhanced fallback', {
originalId: toolCall.id,
standardizedId: standardId,
toolName,
hasInput: Object.keys(parsedInput).length > 0,
inputKeys: Object.keys(parsedInput),
fallbackUsed: toolCall.function?.arguments && 'content' in parsedInput
});
return result;
});
// 过滤掉null值并返回
return results.filter((result) => result !== null);
}
/**
* 提取键值对的降级解析方法
*/
function extractKeyValuePairs(str) {
if (!str || typeof str !== 'string')
return null;
try {
const pairs = {};
// 尝试匹配 key: value 模式
const keyValuePattern = /["']?(\w+)["']?\s*:\s*["']([^"']+)["']/g;
let match;
while ((match = keyValuePattern.exec(str)) !== null) {
const [, key, value] = match;
pairs[key] = value;
}
if (Object.keys(pairs).length > 0) {
return pairs;
}
// 尝试提取路径参数 (常见场景)
const pathMatch = str.match(/["']?([^"'\s]+\.[^"'\s]+)["']?/);
if (pathMatch) {
return { path: pathMatch[1] };
}
return null;
}
catch (e) {
return null;
}
}
/**
* 统一流式工具调用处理器
*/
class StreamingToolCallParser {
toolCalls = new Map();
argumentBuffers = new Map();
/**
* 处理工具调用开始事件
*/
handleToolCallStart(index, toolCall) {
const standardId = standardizeToolId(toolCall.id);
const toolName = toolCall.function?.name || toolCall.name;
if (!toolName) {
logger_1.logger.warn('Tool call missing name', { index, toolCall });
return null;
}
const parsedCall = {
id: standardId,
name: toolName,
index,
input: {}
};
this.toolCalls.set(index, parsedCall);
this.argumentBuffers.set(index, ''); // 初始化参数缓冲区
logger_1.logger.debug('Started streaming tool call', {
index,
toolName,
standardizedId: standardId,
originalId: toolCall.id
});
return {
type: 'tool_use',
id: standardId,
name: toolName,
input: {} // 流式开始时为空,通过delta更新
};
}
/**
* 处理工具参数增量
*/
handleToolArgumentsDelta(index, argumentsDelta) {
const currentBuffer = this.argumentBuffers.get(index) || '';
const newBuffer = currentBuffer + argumentsDelta;
this.argumentBuffers.set(index, newBuffer);
logger_1.logger.debug('Received tool arguments delta', {
index,
deltaLength: argumentsDelta.length,
totalLength: newBuffer.length,
delta: argumentsDelta
});
}
/**
* 完成工具调用解析并返回最终结果
*/
finalizeToolCalls() {
const results = [];
for (const [index, toolCall] of this.toolCalls.entries()) {
const argumentsBuffer = this.argumentBuffers.get(index) || '';
let parsedInput = {};
if (argumentsBuffer.trim()) {
try {
parsedInput = JSON.parse(argumentsBuffer);
logger_1.logger.debug('Successfully parsed streaming tool arguments', {
index,
toolName: toolCall.name,
argumentsLength: argumentsBuffer.length,
parsedKeys: Object.keys(parsedInput)
});
}
catch (e) {
logger_1.logger.warn('Failed to parse streaming tool arguments', {
index,
toolName: toolCall.name,
argumentsBuffer,
error: e instanceof Error ? e.message : String(e)
});
parsedInput = {};
}
}
results.push({
type: 'tool_use',
id: toolCall.id,
name: toolCall.name,
input: parsedInput
});
}
// 清理状态
this.toolCalls.clear();
this.argumentBuffers.clear();
logger_1.logger.debug('Finalized streaming tool calls', {
count: results.length,
tools: results.map(t => ({ id: t.id, name: t.name, hasInput: Object.keys(t.input).length > 0 }))
});
return results;
}
/**
* 重置解析器状态
*/
reset() {
this.toolCalls.clear();
this.argumentBuffers.clear();
}
}
exports.StreamingToolCallParser = StreamingToolCallParser;
/**
* 验证工具调用完整性 - 优化为更宽松的验证
*/
function validateToolCall(toolCall) {
// 宽松的ID验证 - 只要有ID即可,格式在其他地方标准化
if (!toolCall.id || typeof toolCall.id !== 'string' || toolCall.id.trim() === '') {
logger_1.logger.debug('Tool call missing or invalid ID, will generate new one', {
id: toolCall.id,
name: toolCall.name
});
// 不拒绝,而是允许后续修复
}
// 必须有工具名称
if (!toolCall.name || typeof toolCall.name !== 'string' || toolCall.name.trim() === '') {
logger_1.logger.warn('Tool call missing valid name', { toolCall });
return false;
}
// 宽松的输入验证 - 允许各种格式
if (toolCall.input !== undefined && toolCall.input !== null) {
if (typeof toolCall.input !== 'object') {
logger_1.logger.debug('Tool call input is not object, will attempt conversion', {
name: toolCall.name,
inputType: typeof toolCall.input
});
// 允许通过,在使用时进行转换
}
}
return true;
}
/**
* 工具调用转换为Anthropic格式
*/
function convertToAnthropicToolCall(toolCall) {
return {
type: 'tool_use',
id: toolCall.id,
name: toolCall.name,
input: toolCall.input || {}
};
}
/**
* 从文本内容中提取工具调用 (fallback方法)
*/
function extractToolCallsFromText(content) {
const toolCalls = [];
// 匹配工具调用模式
const toolCallPattern = /<tool_use>\s*<name>([^<]+)<\/name>\s*<parameters>([^<]*)<\/parameters>\s*<\/tool_use>/g;
let match;
while ((match = toolCallPattern.exec(content)) !== null) {
const [, name, parametersStr] = match;
let parameters = {};
if (parametersStr.trim()) {
try {
parameters = JSON.parse(parametersStr);
}
catch (e) {
logger_1.logger.warn('Failed to parse tool parameters from text', {
name,
parametersStr,
error: e instanceof Error ? e.message : String(e)
});
}
}
toolCalls.push({
type: 'tool_use',
id: generateTooluId(),
name: name.trim(),
input: parameters
});
}
if (toolCalls.length > 0) {
logger_1.logger.debug('Extracted tool calls from text content', {
count: toolCalls.length,
tools: toolCalls.map(t => ({ name: t.name, hasInput: Object.keys(t.input).length > 0 }))
});
}
return toolCalls;
}
//# sourceMappingURL=unified-tool-parser.js.map