route-claudecode
Version:
Advanced routing and transformation system for Claude Code outputs to multiple AI providers
1,273 lines • 64.4 kB
JavaScript
"use strict";
/**
* 统一兼容性预处理器
* 将所有AI服务的兼容性处理逻辑整合到预处理阶段
* 替代原有的补丁系统,提供更清晰的架构
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.UnifiedCompatibilityPreprocessor = void 0;
exports.getUnifiedCompatibilityPreprocessor = getUnifiedCompatibilityPreprocessor;
exports.createUnifiedCompatibilityPreprocessor = createUnifiedCompatibilityPreprocessor;
exports.resetUnifiedCompatibilityPreprocessor = resetUnifiedCompatibilityPreprocessor;
const logging_1 = require("../logging");
class UnifiedCompatibilityPreprocessor {
logger;
config;
processedCache = new Map();
performanceMetrics = {
totalProcessed: 0,
totalDuration: 0,
byStage: {
input: { count: 0, duration: 0 },
response: { count: 0, duration: 0 },
streaming: { count: 0, duration: 0 }
}
};
constructor(port, config) {
this.config = {
enabled: process.env.RCC_UNIFIED_PREPROCESSING !== 'false',
debugMode: process.env.RCC_PREPROCESSING_DEBUG === 'true',
forceAllInputs: process.env.RCC_FORCE_ALL_INPUTS === 'true',
performanceTracking: true,
cacheResults: process.env.RCC_CACHE_PREPROCESSING === 'true',
validateFinishReason: true,
strictFinishReasonValidation: process.env.RCC_STRICT_FINISH_REASON === 'true',
...config
};
this.logger = (0, logging_1.getLogger)(port);
if (this.config.debugMode) {
this.logger.info('UnifiedCompatibilityPreprocessor initialized', {
config: this.config,
port
});
}
}
/**
* 统一预处理入口:处理输入阶段数据
*/
async preprocessInput(inputData, provider, model, requestId) {
console.log('🚨🚨🚨 [ENTRY] UnifiedCompatibilityPreprocessor.preprocessInput CALLED!', {
requestId,
provider,
model,
hasData: !!inputData,
messageContent: inputData?.messages?.[0]?.content
});
const context = {
requestId,
provider,
model,
stage: 'input',
timestamp: Date.now(),
metadata: inputData.metadata
};
return this.processWithUnifiedPipeline(inputData, context);
}
/**
* 统一预处理入口:处理响应阶段数据
*/
async preprocessResponse(responseData, provider, model, requestId) {
const context = {
requestId,
provider,
model,
stage: 'response',
timestamp: Date.now(),
metadata: responseData.metadata
};
return this.processWithUnifiedPipeline(responseData, context);
}
/**
* 统一预处理入口:处理流式数据块
*/
async preprocessStreaming(chunkData, provider, model, requestId) {
const context = {
requestId,
provider,
model,
stage: 'streaming',
timestamp: Date.now(),
metadata: chunkData.metadata
};
return this.processWithUnifiedPipeline(chunkData, context);
}
/**
* 统一处理管道
*/
async processWithUnifiedPipeline(data, context) {
const startTime = Date.now();
let processedData = data;
try {
// 性能追踪
if (this.config.performanceTracking) {
this.performanceMetrics.totalProcessed++;
this.performanceMetrics.byStage[context.stage].count++;
}
// 1. 检查是否启用
if (!this.config.enabled) {
if (this.config.debugMode) {
this.logger.debug('UnifiedCompatibilityPreprocessor disabled, skipping', {
requestId: context.requestId,
provider: context.provider,
model: context.model
});
}
return data;
}
// 2. 缓存检查
const cacheKey = this.generateCacheKey(data, context);
if (this.config.cacheResults && this.processedCache.has(cacheKey)) {
if (this.config.debugMode) {
this.logger.debug('Using cached result', { cacheKey });
}
return this.processedCache.get(cacheKey);
}
// 3. 根据阶段进行不同的处理
if (context.stage === 'response') {
// 响应阶段处理
processedData = await this.processResponse(data, context);
}
else if (context.stage === 'input') {
// 输入阶段处理
processedData = await this.processInput(data, context);
}
else if (context.stage === 'streaming') {
// 流式处理
processedData = await this.processStreaming(data, context);
}
// 4. 缓存结果
if (this.config.cacheResults) {
this.processedCache.set(cacheKey, processedData);
}
return processedData;
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.logger.error('UnifiedCompatibilityPreprocessor processing failed', {
error: errorMessage,
requestId: context.requestId,
provider: context.provider,
model: context.model,
stage: context.stage
}, context.requestId, 'preprocessing');
// 发生错误时返回原始数据
return data;
}
finally {
// 性能统计
const duration = Date.now() - startTime;
if (this.config.performanceTracking) {
this.performanceMetrics.totalDuration += duration;
this.performanceMetrics.byStage[context.stage].duration += duration;
}
}
}
/**
* 处理响应数据
*/
async processResponse(data, context) {
let processedData = data;
// 1. OpenAI兼容性处理 (ModelScope, ShuaiHong, LMStudio等)
if (this.isOpenAICompatibleProvider(context.provider)) {
processedData = await this.processOpenAICompatibleResponse(processedData, context);
}
// 2. Gemini格式修复
if (this.isGeminiProvider(context.provider)) {
processedData = await this.processGeminiResponse(processedData, context);
}
// 3. Anthropic工具调用文本修复
if (this.needsAnthropicToolTextFix(processedData, context)) {
processedData = await this.processAnthropicToolTextFix(processedData, context);
}
// 4. 强制工具调用检测和finish reason修复
const toolDetectionResult = await this.forceToolCallDetection(processedData, context);
if (toolDetectionResult.hasTools) {
processedData = this.forceFinishReasonOverride(processedData, 'tool_calls', context);
console.log(`🔧 [COMPATIBILITY] Forced finish_reason override for ${toolDetectionResult.toolCount} tools`);
}
// 5. 验证响应有效性
if (this.config.validateFinishReason) {
this.validateFinishReason(processedData, context);
}
return processedData;
}
/**
* 处理输入数据
*/
async processInput(data, context) {
// 强制stderr debug - 确保这个方法被调用
process.stderr.write(`🔥 [PROCESS-INPUT-ENTRY] processInput called with: ${JSON.stringify({
requestId: context.requestId,
provider: context.provider,
model: context.model,
hasData: !!data,
debugMode: this.config.debugMode
})}\n`);
let processedData = data;
if (this.config.debugMode) {
this.logger.debug('ProcessInput: Starting input processing', {
hasTools: !!data.tools,
toolsCount: data.tools?.length || 0,
toolsType: typeof data.tools,
isArray: Array.isArray(data.tools),
requestId: context.requestId
});
}
// 🚨 Critical Fix 0: 对所有OpenAI兼容Provider应用通用格式修复
console.log('🔍 [PROVIDER-CHECK-BEFORE] About to check provider compatibility', {
requestId: context.requestId,
provider: context.provider,
model: context.model,
providerType: typeof context.provider
});
const isCompatible = this.isOpenAICompatibleProvider(context.provider);
console.log('🔍 [PROVIDER-CHECK-RESULT] Provider compatibility check result', {
requestId: context.requestId,
provider: context.provider,
isCompatible,
willApplyFix: isCompatible
});
if (isCompatible) {
console.log('🔧 [UNIVERSAL-FIX] Applying universal OpenAI compatibility fixes for input', {
requestId: context.requestId,
provider: context.provider,
model: context.model,
hasTools: !!data.tools,
toolsCount: data.tools?.length || 0,
messagesCount: data.messages?.length || 0
});
processedData = this.applyUniversalOpenAICompatibilityFixes(processedData, context);
console.log('🔧 [UNIVERSAL-FIX] Universal fixes applied successfully', {
requestId: context.requestId,
originalEquals: processedData === data,
hasFixedData: !!processedData
});
}
else {
console.log('🚫 [UNIVERSAL-FIX] Skipping universal fixes - not OpenAI compatible provider', {
requestId: context.requestId,
provider: context.provider
});
}
// 1. ModelScope请求格式修复
const isModelScopeCompatible = this.isModelScopeCompatible(context.provider, context.model);
console.log('🚨 [DEBUG] ModelScope compatibility check:', {
requestId: context.requestId,
provider: context.provider,
model: context.model,
isModelScopeCompatible
});
if (isModelScopeCompatible) {
console.log('🚨 [DEBUG] ENTERING ModelScope processing path');
// ⚠️ CRITICAL: Apply universal fixes BEFORE ModelScope-specific processing
processedData = this.applyUniversalOpenAICompatibilityFixes(processedData, context);
processedData = await this.processModelScopeRequest(processedData, context);
}
// 2. Gemini请求格式修复
if (this.isGeminiProvider(context.provider)) {
processedData = await this.processGeminiRequest(processedData, context);
}
// 3. 工具定义标准化
if (processedData.tools && Array.isArray(processedData.tools)) {
if (this.config.debugMode) {
this.logger.debug('ProcessInput: Standardizing tool definitions', {
originalToolsCount: processedData.tools.length,
firstToolName: processedData.tools[0]?.name,
requestId: context.requestId
});
}
const originalTools = [...processedData.tools];
processedData.tools = this.standardizeToolDefinitions(processedData.tools, context);
if (this.config.debugMode) {
this.logger.debug('ProcessInput: Tool standardization complete', {
originalCount: originalTools.length,
standardizedCount: processedData.tools.length,
standardizedFirstToolName: processedData.tools[0]?.function?.name,
requestId: context.requestId
});
}
}
else if (this.config.debugMode) {
this.logger.debug('ProcessInput: Skipping tool standardization', {
hasTools: !!processedData.tools,
toolsType: typeof processedData.tools,
isArray: Array.isArray(processedData.tools),
requestId: context.requestId
});
}
return processedData;
}
/**
* 处理流式数据
*/
async processStreaming(data, context) {
let processedData = data;
// 流式数据中的工具调用检测
if (this.hasStreamingToolCalls(data)) {
processedData = await this.processStreamingToolCalls(processedData, context);
}
return processedData;
}
// ====================
// OpenAI兼容性处理
// ====================
/**
* 处理OpenAI兼容服务的响应格式问题
*/
async processOpenAICompatibleResponse(data, context) {
// ModelScope/ShuaiHong格式修复:解决"OpenAI response missing choices"错误
if (data && typeof data === 'object' && !data.choices) {
console.log(`🔧 [COMPATIBILITY] Applying OpenAI format patch for missing choices field`);
console.log(`📍 [MODEL-MATCH] ${context.model} on ${context.provider}`);
const fixedData = {
id: data.id || `msg_${Date.now()}_${context.requestId.slice(-8)}`,
object: 'chat.completion',
created: data.created || Math.floor(Date.now() / 1000),
model: context.model,
choices: [{
index: 0,
message: {
role: 'assistant',
content: this.extractContent(data) || '',
tool_calls: this.extractToolCalls(data) || null
},
finish_reason: this.extractFinishReason(data) || 'stop'
}],
usage: data.usage || {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0
}
};
// 如果有工具调用但没有内容,设置content为null
if (fixedData.choices[0].message.tool_calls && !fixedData.choices[0].message.content) {
fixedData.choices[0].message.content = null;
}
return fixedData;
}
// LMStudio特殊处理:解析嵌入的工具调用文本
if (this.isLMStudioProvider(context.provider)) {
return await this.processLMStudioResponse(data, context);
}
// 检查choices存在但格式不完整的情况
if (data && data.choices && Array.isArray(data.choices)) {
let needsFix = false;
const fixedChoices = data.choices.map((choice) => {
if (!choice.message) {
needsFix = true;
return {
...choice,
index: choice.index || 0,
message: {
role: 'assistant',
content: choice.content || choice.text || '',
tool_calls: choice.tool_calls || null
},
finish_reason: choice.finish_reason || 'stop'
};
}
return choice;
});
if (needsFix) {
console.log(`🔧 [COMPATIBILITY] Fixing incomplete choices format for ${context.provider}`);
return {
...data,
choices: fixedChoices
};
}
}
return data;
}
/**
* LMStudio响应处理
*/
async processLMStudioResponse(data, context) {
// 处理OpenAI和Anthropic两种格式
let textContent = '';
let isOpenAIFormat = false;
// 检查OpenAI格式
if (data.choices && Array.isArray(data.choices) && data.choices.length > 0) {
const choice = data.choices[0];
textContent = choice.message?.content;
isOpenAIFormat = true;
}
// 检查Anthropic格式
else if (data.content && Array.isArray(data.content)) {
const textBlock = data.content.find((block) => block.type === 'text');
textContent = textBlock?.text;
isOpenAIFormat = false;
}
if (typeof textContent === 'string' && textContent.length > 0) {
const lmstudioToolCalls = this.parseLMStudioToolCalls(textContent, context);
if (lmstudioToolCalls.length > 0) {
console.log(`🔧 [COMPATIBILITY] Parsed ${lmstudioToolCalls.length} LMStudio tool calls (${isOpenAIFormat ? 'OpenAI' : 'Anthropic'} format)`);
// 清理工具调用标记
let newContent = textContent;
const lmstudioPattern = /<\|start\|>assistant<\|channel\|>commentary to=functions\.[^<]*\s*<\|constrain\|>[^<]*<\|message\|>\{[^}]*\}/g;
newContent = newContent.replace(lmstudioPattern, '').trim();
if (isOpenAIFormat) {
// 返回OpenAI格式
const choice = data.choices[0];
return {
...data,
choices: [{
...choice,
message: {
...choice.message,
content: newContent || null,
tool_calls: lmstudioToolCalls
},
finish_reason: 'tool_calls'
}]
};
}
else {
// 返回Anthropic格式
const toolUseBlocks = lmstudioToolCalls.map(toolCall => ({
type: 'tool_use',
id: toolCall.id,
name: toolCall.function.name,
input: JSON.parse(toolCall.function.arguments)
}));
const newContentBlocks = [];
if (newContent) {
newContentBlocks.push({
type: 'text',
text: newContent
});
}
newContentBlocks.push(...toolUseBlocks);
return {
...data,
content: newContentBlocks,
stop_reason: 'tool_use'
};
}
}
}
return data;
}
/**
* ModelScope请求格式处理
*/
async processModelScopeRequest(data, context) {
if (!data)
return data;
let patchedRequest = { ...data };
// 确保消息格式正确,特殊处理GLM-4.5和Qwen3-Coder
if (patchedRequest.messages && Array.isArray(patchedRequest.messages)) {
patchedRequest.messages = patchedRequest.messages.map((msg) => ({
role: msg.role,
content: this.ensureStringContentForModelScope(msg.content, context.model)
}));
// GLM-4.5特殊处理
if (this.isGLMModel(context.model)) {
patchedRequest = this.applyGLMSpecificPatches(patchedRequest, context);
}
// Qwen3-Coder特殊处理
else if (this.isQwen3CoderModel(context.model)) {
patchedRequest = this.applyQwen3CoderSpecificPatches(patchedRequest, context);
}
// 构建prompt字符串作为备用
const promptText = this.buildPromptFromMessages(patchedRequest.messages);
if (promptText) {
patchedRequest.prompt = promptText;
}
}
// 确保必要参数
if (!patchedRequest.max_tokens) {
patchedRequest.max_tokens = 4096;
}
if (typeof patchedRequest.temperature === 'undefined') {
patchedRequest.temperature = 0.7;
}
if (typeof patchedRequest.stream === 'undefined') {
patchedRequest.stream = true;
}
return patchedRequest;
}
// ====================
// Gemini格式处理
// ====================
/**
* Gemini响应格式修复
*/
async processGeminiResponse(data, context) {
// 如果已经是标准格式,直接返回
if (data.choices && Array.isArray(data.choices)) {
return data;
}
const fixedData = {
id: data.id || `gemini-${Date.now()}`,
object: 'chat.completion',
created: Math.floor(Date.now() / 1000),
model: context.model,
choices: []
};
// 处理candidates字段
if (data.candidates && Array.isArray(data.candidates)) {
fixedData.choices = data.candidates.map((candidate, index) => {
const choice = {
index,
message: {
role: 'assistant',
content: ''
},
finish_reason: this.mapGeminiFinishReason(candidate.finishReason)
};
// 处理content
if (candidate.content && candidate.content.parts) {
const textParts = candidate.content.parts
.filter((part) => part.text)
.map((part) => part.text);
choice.message.content = textParts.join('\n');
// 处理function calls
const functionCalls = candidate.content.parts
.filter((part) => part.functionCall);
if (functionCalls.length > 0) {
choice.message.tool_calls = functionCalls.map((part) => ({
id: `call_${Math.random().toString(36).substr(2, 9)}`,
type: 'function',
function: {
name: part.functionCall.name,
arguments: JSON.stringify(part.functionCall.args || {})
}
}));
choice.finish_reason = 'tool_calls';
}
}
return choice;
});
}
// 处理usage信息
if (data.usageMetadata) {
fixedData.usage = {
prompt_tokens: data.usageMetadata.promptTokenCount || 0,
completion_tokens: data.usageMetadata.candidatesTokenCount || 0,
total_tokens: data.usageMetadata.totalTokenCount || 0
};
}
return fixedData;
}
/**
* Gemini请求格式处理
*/
async processGeminiRequest(data, context) {
// Gemini请求格式转换逻辑
return data; // TODO: 实现Gemini请求格式转换
}
// ====================
// Anthropic工具调用文本修复
// ====================
/**
* Anthropic工具调用文本修复
*/
async processAnthropicToolTextFix(data, context) {
if (!data.content || !Array.isArray(data.content)) {
return data;
}
const fixedContent = [];
let extractedToolCalls = 0;
// 收集所有文本块到缓冲区进行批量处理
const textBuffer = [];
const nonTextBlocks = [];
for (const block of data.content) {
if (block.type === 'text' && block.text) {
textBuffer.push(block.text);
}
else {
nonTextBlocks.push(block);
}
}
// 批量处理文本内容
if (textBuffer.length > 0) {
const combinedText = textBuffer.join('\n');
const { textParts, toolCalls } = this.extractToolCallsFromText(combinedText);
// 添加清理后的文本
if (textParts.length > 0) {
const cleanText = textParts.join('\n').trim();
if (cleanText) {
fixedContent.push({
type: 'text',
text: cleanText
});
}
}
// 添加提取的工具调用
fixedContent.push(...toolCalls);
extractedToolCalls += toolCalls.length;
}
// 添加非文本块
fixedContent.push(...nonTextBlocks);
const result = {
...data,
content: fixedContent
};
// 更新finish reason
if (extractedToolCalls > 0) {
result.stop_reason = 'tool_use';
console.log(`🔧 [COMPATIBILITY] Extracted ${extractedToolCalls} tool calls from text content`);
}
return result;
}
// ====================
// 工具调用检测和修复
// ====================
/**
* 强制工具调用检测
*/
async forceToolCallDetection(data, context) {
let hasTools = false;
let toolCount = 0;
// 1. 检查Anthropic格式的工具调用
if (data.content && Array.isArray(data.content)) {
const directToolCalls = data.content.filter((block) => block.type === 'tool_use');
toolCount += directToolCalls.length;
// 检查文本格式的工具调用
const textBlocks = data.content.filter((block) => block.type === 'text');
for (const block of textBlocks) {
if (block.text && this.hasTextToolCallsSimplified(block.text)) {
hasTools = true;
}
}
}
// 2. 检查OpenAI格式的工具调用
if (data.choices && Array.isArray(data.choices)) {
for (const choice of data.choices) {
if (choice.message?.tool_calls) {
toolCount += choice.message.tool_calls.length;
hasTools = true;
}
if (choice.message?.content && this.hasTextToolCallsSimplified(choice.message.content)) {
hasTools = true;
}
}
}
// 3. 检查Gemini格式的工具调用
if (data.candidates && Array.isArray(data.candidates)) {
for (const candidate of data.candidates) {
if (candidate.content?.parts) {
const functionCalls = candidate.content.parts.filter((part) => part.functionCall);
toolCount += functionCalls.length;
hasTools = hasTools || functionCalls.length > 0;
}
}
}
hasTools = hasTools || toolCount > 0;
return { hasTools, toolCount };
}
/**
* 强制finish reason覆盖
*/
forceFinishReasonOverride(data, targetReason, context) {
const result = { ...data };
// OpenAI格式
if (data.choices && Array.isArray(data.choices)) {
result.choices = data.choices.map((choice) => ({
...choice,
finish_reason: targetReason
}));
}
// Anthropic格式
if (data.stop_reason !== undefined) {
result.stop_reason = targetReason === 'tool_calls' ? 'tool_use' : targetReason;
}
return result;
}
// ====================
// 辅助方法
// ====================
/**
* 检查是否为OpenAI兼容Provider
*/
isOpenAICompatibleProvider(provider) {
const isCompatible = provider.includes('openai') ||
provider.includes('lmstudio') ||
provider.includes('modelscope') ||
provider.includes('shuaihong') ||
provider.includes('deepseek');
// 强制输出到stderr进行debug
process.stderr.write(`🚨 [PROVIDER-CHECK] isOpenAICompatibleProvider: ${JSON.stringify({
provider,
isCompatible,
includesOpenai: provider.includes('openai'),
includesLmstudio: provider.includes('lmstudio'),
includesModelscope: provider.includes('modelscope'),
includesShuaihong: provider.includes('shuaihong'),
includesDeepseek: provider.includes('deepseek')
})}\n`);
return isCompatible;
}
/**
* 检查是否为LMStudio Provider
*/
isLMStudioProvider(provider) {
return provider.includes('lmstudio') || provider.includes('LMStudio');
}
/**
* 检查是否为Gemini Provider
*/
isGeminiProvider(provider) {
return provider.includes('gemini') || provider.includes('palm') || provider.includes('bison');
}
/**
* 检查是否需要Anthropic工具文本修复
*/
needsAnthropicToolTextFix(data, context) {
if (!data.content || !Array.isArray(data.content)) {
return false;
}
return data.content.some((block) => {
if (block.type !== 'text' || !block.text) {
return false;
}
// 检查是否包含工具调用文本模式
const toolCallPatterns = [
/\{\s*"type"\s*:\s*"tool_use"\s*,/i,
/\{\s*"id"\s*:\s*"toolu_[^"]+"\s*,/i,
/"name"\s*:\s*"[^"]+"\s*,\s*"input"\s*:\s*\{/i,
/Tool\s+call:\s*\w+\s*\(\s*\{[^}]*"[^"]*":[^}]*\}/i,
/\w+\s*\(\s*\{\s*"[^"]+"\s*:\s*"[^"]*"/i
];
return toolCallPatterns.some(pattern => pattern.test(block.text));
});
}
/**
* 检查是否为ModelScope兼容的provider/model组合
*/
isModelScopeCompatible(provider, model) {
if (provider.includes('modelscope') || provider.includes('shuaihong')) {
return true;
}
const modelLower = model.toLowerCase();
return modelLower.includes('qwen') ||
modelLower.includes('glm') ||
modelLower.includes('zhipuai') ||
modelLower.includes('coder');
}
/**
* 检查是否为GLM模型
*/
isGLMModel(model) {
const modelLower = model.toLowerCase();
return modelLower.includes('glm') || modelLower.includes('zhipuai');
}
/**
* 检查是否为Qwen3-Coder模型
*/
isQwen3CoderModel(model) {
const modelLower = model.toLowerCase();
return modelLower.includes('qwen3') ||
modelLower.includes('coder') ||
modelLower.includes('480b');
}
/**
* 解析LMStudio工具调用格式
*/
parseLMStudioToolCalls(content, context) {
const toolCalls = [];
const lmstudioPattern = /<\|start\|>assistant<\|channel\|>commentary to=functions\.(\w+)\s*<\|constrain\|>(?:JSON|json)<\|message\|>(\{[^}]*\})/g;
let match;
while ((match = lmstudioPattern.exec(content)) !== null) {
try {
const functionName = match[1];
const argsJson = match[2];
const args = JSON.parse(argsJson);
toolCalls.push({
id: `call_${Date.now()}_${toolCalls.length}`,
type: 'function',
function: {
name: functionName,
arguments: JSON.stringify(args)
}
});
}
catch (error) {
console.warn('Failed to parse LMStudio tool call:', error);
}
}
return toolCalls;
}
/**
* 从文本中提取工具调用
*/
extractToolCallsFromText(text) {
const textParts = [];
const toolCalls = [];
// 1. GLM-4.5格式:Tool call: FunctionName({...})
const glmPattern = /Tool\s+call:\s*(\w+)\s*\((\{[^}]*\})\)/gi;
let match;
while ((match = glmPattern.exec(text)) !== null) {
try {
const toolName = match[1];
const jsonStr = match[2];
const args = JSON.parse(jsonStr);
toolCalls.push({
type: 'tool_use',
id: `toolu_${Date.now()}_${Math.random().toString(36).substr(2, 8)}`,
name: toolName,
input: args
});
}
catch (error) {
console.warn('Failed to parse GLM tool call:', match[2]);
}
}
// 2. JSON格式:{"type": "tool_use", ...}
const jsonPattern = /\{\s*"type"\s*:\s*"tool_use"[^}]*\}/gi;
while ((match = jsonPattern.exec(text)) !== null) {
try {
const toolCallJson = JSON.parse(match[0]);
if (this.isValidToolCall(toolCallJson)) {
toolCalls.push({
type: 'tool_use',
id: toolCallJson.id,
name: toolCallJson.name,
input: toolCallJson.input
});
}
}
catch (error) {
console.warn('Failed to parse JSON tool call:', match[0].substring(0, 50));
}
}
// 清理文本内容
if (toolCalls.length > 0) {
let cleanedText = text;
cleanedText = cleanedText.replace(/Tool\s+call:\s*\w+\s*\([^)]*\)/gi, '');
cleanedText = cleanedText.replace(/\{\s*"type"\s*:\s*"tool_use"[^}]*\}/gi, '');
cleanedText = cleanedText.replace(/\n\s*\n/g, '\n').trim();
if (cleanedText && cleanedText.length > 10) {
textParts.push(cleanedText);
}
}
else {
textParts.push(text);
}
return { textParts, toolCalls };
}
/**
* 简化的文本工具调用检测
*/
hasTextToolCallsSimplified(text) {
const patterns = [
/Tool\s+call:\s*\w+\s*\(/i,
/"type"\s*:\s*"tool_use"/i,
/"name"\s*:\s*"\w+"/i
];
return patterns.some(pattern => pattern.test(text));
}
/**
* 验证是否为有效的工具调用
*/
isValidToolCall(obj) {
return obj &&
typeof obj === 'object' &&
obj.type === 'tool_use' &&
typeof obj.id === 'string' &&
typeof obj.name === 'string' &&
obj.input !== undefined;
}
/**
* 标准化工具定义
*/
standardizeToolDefinitions(tools, context) {
if (this.config.debugMode) {
this.logger.debug('StandardizeToolDefinitions: Starting tool standardization', {
toolsCount: tools?.length || 0,
toolsType: typeof tools,
isArray: Array.isArray(tools),
requestId: context.requestId
});
}
if (!Array.isArray(tools)) {
this.logger.warn('Invalid tools array provided', { tools, requestId: context.requestId });
return [];
}
const results = tools.map((tool, index) => {
if (this.config.debugMode) {
this.logger.debug(`StandardizeToolDefinitions: Processing tool ${index}`, {
toolName: tool?.name,
hasName: !!tool?.name,
hasFunction: !!tool?.function,
hasInputSchema: !!tool?.input_schema,
toolKeys: Object.keys(tool || {}),
requestId: context.requestId
});
}
try {
// 基础结构验证
if (!tool || typeof tool !== 'object') {
this.logger.warn(`Invalid tool at index ${index}`, { tool, requestId: context.requestId });
return null;
}
// 处理不同的工具定义格式
let standardizedTool = {
type: 'function' // 确保有type字段
};
// 如果已经有function字段,使用它
if (tool.function && typeof tool.function === 'object') {
standardizedTool.function = {
name: tool.function.name || tool.name || 'unknown', // 优先使用function.name,然后是tool.name
description: tool.function.description || tool.description || `Function: ${tool.function.name || tool.name || 'unknown'}`,
parameters: tool.function.parameters || {}
};
// 如果没有parameters但有input_schema,转换它
if (!tool.function.parameters && tool.input_schema && typeof tool.input_schema === 'object') {
standardizedTool.function.parameters = this.convertInputSchemaToParameters(tool.input_schema, context);
}
}
// 如果是直接格式(name在顶级),转换为function格式
else if (tool.name) {
standardizedTool.function = {
name: tool.name,
description: tool.description || `Function: ${tool.name}`,
parameters: {}
};
// 处理input_schema转换为parameters
if (tool.input_schema && typeof tool.input_schema === 'object') {
standardizedTool.function.parameters = this.convertInputSchemaToParameters(tool.input_schema, context);
}
}
// 保留其他字段并复制到function格式
else {
// 尝试从工具对象的其他字段推断
const toolName = tool.name || tool.function?.name || `tool_${index}`;
const toolDescription = tool.description || tool.function?.description || `Function: ${toolName}`;
standardizedTool.function = {
name: toolName,
description: toolDescription,
parameters: {}
};
// 处理input_schema
if (tool.input_schema && typeof tool.input_schema === 'object') {
standardizedTool.function.parameters = this.convertInputSchemaToParameters(tool.input_schema, context);
}
// 处理parameters
else if (tool.parameters && typeof tool.parameters === 'object') {
standardizedTool.function.parameters = tool.parameters;
}
// 处理function.parameters
else if (tool.function?.parameters && typeof tool.function.parameters === 'object') {
standardizedTool.function.parameters = tool.function.parameters;
}
if (!tool.name && !tool.function?.name) {
this.logger.warn(`Tool at index ${index} missing name, using generated name: ${toolName}`, {
tool,
generatedName: toolName,
requestId: context.requestId
});
}
}
// 验证最终结果
if (!this.isValidToolDefinition(standardizedTool)) {
this.logger.warn(`Tool at index ${index} failed validation after standardization`, {
tool: standardizedTool,
requestId: context.requestId
});
return null;
}
return standardizedTool;
}
catch (error) {
this.logger.error(`Error standardizing tool at index ${index}`, {
error: error instanceof Error ? error.message : String(error),
tool,
requestId: context.requestId
});
return null;
}
});
const validResults = results.filter(tool => tool !== null);
if (this.config.debugMode) {
this.logger.debug('StandardizeToolDefinitions: Standardization complete', {
originalCount: tools.length,
validCount: validResults.length,
resultToolNames: validResults.map(tool => tool?.function?.name),
requestId: context.requestId
});
}
return validResults; // 移除无效的工具
}
/**
* 转换input_schema为OpenAI parameters格式
*/
convertInputSchemaToParameters(inputSchema, context) {
if (!inputSchema || typeof inputSchema !== 'object') {
return {};
}
try {
// 基础验证和修复
const parameters = {
type: inputSchema.type || 'object',
properties: {},
required: inputSchema.required || []
};
// 处理properties字段
if (inputSchema.properties && typeof inputSchema.properties === 'object') {
for (const [key, value] of Object.entries(inputSchema.properties)) {
// 修复malformed properties
if (typeof value === 'string') {
// 如果是字符串,转换为正确的schema格式
parameters.properties[key] = {
type: 'string',
description: value
};
}
else if (typeof value === 'object' && value !== null) {
parameters.properties[key] = { ...value };
}
else {
// 默认为string类型
parameters.properties[key] = {
type: 'string',
description: `Parameter: ${key}`
};
}
}
}
return parameters;
}
catch (error) {
this.logger.warn('Failed to convert input_schema to parameters', {
error: error instanceof Error ? error.message : String(error),
inputSchema,
requestId: context.requestId
});
return {
type: 'object',
properties: {},
required: []
};
}
}
/**
* 验证工具定义是否有效
*/
isValidToolDefinition(tool) {
if (!tool || typeof tool !== 'object') {
return false;
}
if (tool.type !== 'function') {
return false;
}
if (!tool.function || typeof tool.function !== 'object') {
return false;
}
if (!tool.function.name || typeof tool.function.name !== 'string') {
return false;
}
// parameters字段是可选的,但如果存在必须是对象
if (tool.function.parameters !== undefined && typeof tool.function.parameters !== 'object') {
return false;
}
return true;
}
/**
* 检查流式数据中是否有工具调用
*/
hasStreamingToolCalls(data) {
return data?.event && data?.data && (data.event.includes('tool') ||
data.data.tool_calls ||
data.data.function_call);
}
/**
* 处理流式工具调用
*/
async processStreamingToolCalls(data, context) {
// TODO: 实现流式工具调用处理
return data;
}
/**
* 从非标准响应中提取内容
*/
extractContent(data) {
if (data.content)
return data.content;
if (data.message && typeof data.message === 'string')
return data.message;
if (data.text)
return data.text;
if (data.response)
return data.response;
if (data.output)
return data.output;
if (data.result?.content)
return data.result.content;
if (data.data?.content)
return data.data.content;
return null;
}
/**
* 从非标准响应中提取工具调用
*/
extractToolCalls(data) {
if (data.tool_calls && Array.isArray(data.tool_calls))
return data.tool_calls;
if (data.message?.tool_calls)
return data.message.tool_calls;
if (data.choices?.[0]?.message?.tool_calls)
return data.choices[0].message.tool_calls;
return null;
}
/**
* 从非标准响应中提取finish_reason
*/
extractFinishReason(data) {
if (data.finish_reason)
return data.finish_reason;
if (data.stop_reason)
return data.stop_reason;
if (data.finishReason)
return data.finishReason;
if (data.status)
return data.status;
if (data.result?.finish_reason)
return data.result.finish_reason;
if (data.choices?.[0]?.finish_reason)
return data.choices[0].finish_reason;
if (this.extractToolCalls(data))
return 'tool_calls';
return 'stop';
}
/**
* 映射Gemini finish reason
*/
mapGeminiFinishReason(geminiReason) {
const reasonMap = {
'STOP': 'stop',
'MAX_TOKENS': 'length',
'SAFETY': 'content_filter',
'RECITATION': 'content_filter',
'OTHER': 'stop'
};
return reasonMap[geminiReason] || 'stop';
}
/**
* 确保ModelScope内容为字符串格式
*/
ensureStringContentForModelScope(content, model) {
if (typeof content === 'string')
return content;
if (Array.isArray(content)) {
return content.map(block => {
if (block.type === 'text' && block.text)
return block.text;
if (block.type === 'tool_use') {
return this.convertToolUseToModelScopeFormat(block, model);
}
return '';
}).filter(text => text.trim()).join('\n');
}
if (typeof content === 'object' && content !== null) {
if (content.text)
return content.text;
return JSON.stringify(content);
}
return String(content);
}
/**
* 转换工具调用为ModelScope格式
*/
convertToolUseToModelScopeFormat(toolBlock, model) {
if (this.isGLMModel(model)) {
const functionName = toolBlock.name || 'unknown';
const inputData = JSON.stringify(toolBlock.input || {});
return `Tool call: ${functionName}(${inputData})`;
}
else if (this.isQwen3CoderModel(model)) {
return JSON.stringify({
type: 'tool_use',
name: toolBlock.name,
input: toolBlock.input
});
}
return JSON.stringify(toolBlock);
}
/**
* 🚨 Critical Fix: 通用OpenAI兼容格式验证和修复
* 适用于所有OpenAI兼容Provider (GLM, ShuaiHong, LMStudio等)
*/
applyUniversalOpenAICompatibilityFixes(request, context) {
const patchedRequest = { ...request };
console.log('🚨 [UNIVERSAL-FIX-DEBUG] Starting universal compatibility fixes', {
requestId: context.requestId,
provider: context.provider,
model: context.model,
hasMessages: !!patchedRequest.messages,
messagesCount: patchedRequest.messages?.length || 0,
firstMessageContent: patchedRequest.messages?.[0]?.content ? {
type: typeof patchedRequest.messages[0].content,
isArray: Array.isArray(patchedRequest.messages[0].content),
structure: patchedRequest.messages[0].content
} : null
});
// 🔧 Critical Fix 1: Messages Content Format Validation
if (patchedRequest.messages && Array.isArray(patchedRequest.messages)) {
patchedRequest.messages = patchedRequest.messages.map((message, index) => {
if (message.content && typeof message.content === 'object' && !Array.isArray(message.content)) {
console.log(`🔧 [UNIVERSAL-FIX] DETECTED OBJECT CONTENT - Fixing message[${index}].content from object to array format`, {
requestId: context.requestId,
provider: context.provider,
model: context.model,
originalContentType: typeof message.content,
hasTextType: message.content.type === 'text',
hasText: !!message.content.text,
originalContent: message.content
});
// 将object格式的content转换为array格式
if (message.content.type === 'text' && message.content.text) {
return {
...message,
content: [message.content]
};
}
else {
// 转换为字符串格式(备选方案)
return {
...message,
content: JSON.stringify(message.content)
};
}
}
return message;
});
}
// 🔧 Critical Fix 2: Tools Array Format Validation
if (patchedRequest.tools && Array.isArray(patchedRequest.tools)) {
console.log('🔧 [UNIVERSAL-FIX] Pre-fix tools format:', {
requestId: context.requestId,
provider: context.provider,
model: context.model,
toolsCount: patchedRequest.tools.length,
toolTypes: patchedRequest.tools.map((tool, i) => `[${i}]:${typeof tool}`).join(', '),
invalidTools: patchedRequest.tools.filter((tool) => typeof tool !== 'object' || tool === null).length
});
// 过滤和修复tools数组
patchedRequest.tools = patchedRequest.tools
.map((tool, index) => {
// 检查工具是否为null、undefined或非对象
if (typeof tool !== 'object' || tool === null) {
console.log(`🚨 [UNIVERSAL-FIX] Removing invalid tool at index ${index}: ${typeof tool}`, {
requestId: context.requestId,
provider: context.provider,
toolValue: tool,
toolType: typeof tool
});
return null; // 标记为删除
}
// 检查工具是否是字符串(需要解析)
if (typeof tool === 'string') {
console.log('🚨 [UNIVERSAL-FIX] Found string tool, attempting to parse:', tool);
try {
tool = JSON.parse(tool);
}
catch (e) {
const errorMessage = e instanceof Error ? e.message : String(e);
console.error('❌ [UNIVERSAL-FIX] Failed to parse string tool:', errorMessage);
console.log(`🗑️ [UNIVERSAL-FIX] Removing unparseable string tool at index ${index}`);
return null; // 标记为删除
}
}
// 修复混合格式和格式转换
return this.fixToolFormat(tool, index,