route-claudecode
Version:
Advanced routing and transformation system for Claude Code outputs to multiple AI providers
381 lines • 15.2 kB
JavaScript
"use strict";
/**
* OpenAI Buffered Response Processor
* 完全缓冲式处理:类似CodeWhisperer的processBufferedResponse
* 专门解决多个工具调用被合并为文本的问题
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.processOpenAIBufferedResponse = processOpenAIBufferedResponse;
const logger_1 = require("@/utils/logger");
const finish_reason_handler_1 = require("@/utils/finish-reason-handler");
/**
* 完全缓冲式处理OpenAI响应:先收集所有事件,再统一处理,最后转换为流式格式
* 解决工具调用分段问题
*/
function processOpenAIBufferedResponse(allEvents, requestId, modelName) {
try {
logger_1.logger.info('Starting OpenAI buffered response processing', {
eventCount: allEvents.length,
hasToolCalls: allEvents.some(e => e.choices?.[0]?.delta?.tool_calls)
}, requestId);
// 第一步:将所有OpenAI事件合并为完整的非流式响应
const bufferedResponse = convertOpenAIEventsToBuffered(allEvents, requestId);
logger_1.logger.debug('Converted OpenAI events to buffered response', {
contentBlocks: bufferedResponse.content.length,
contentTypes: bufferedResponse.content.map(c => c.type),
hasUsage: !!bufferedResponse.usage
}, requestId);
// 第二步:将非流式响应转换回流式事件格式
const streamEvents = convertBufferedResponseToAnthropicStream(bufferedResponse, requestId, modelName);
logger_1.logger.info('Converted buffered response to Anthropic stream events', {
streamEventCount: streamEvents.length,
eventTypes: streamEvents.map(e => e.event)
}, requestId);
return streamEvents;
}
catch (error) {
logger_1.logger.error('Failed to process OpenAI buffered response', {
error: error instanceof Error ? error.message : String(error),
eventCount: allEvents.length
}, requestId);
return [];
}
}
/**
* 将OpenAI流事件合并为完整的非流式响应
* 关键:正确处理工具调用的累积
*/
function convertOpenAIEventsToBuffered(events, requestId) {
const content = [];
const toolCallMap = {};
let textContent = '';
let usage = null;
logger_1.logger.debug('Starting OpenAI events to buffered conversion', {
eventCount: events.length
}, requestId);
for (const event of events) {
try {
const choice = event.choices?.[0];
if (!choice?.delta)
continue;
// 处理文本内容
if (choice.delta.content !== undefined) {
textContent += choice.delta.content || '';
logger_1.logger.debug('Added text content', {
addedText: choice.delta.content,
totalTextLength: textContent.length
}, requestId);
}
// 处理工具调用
if (choice.delta.tool_calls) {
for (const toolCall of choice.delta.tool_calls) {
const index = toolCall.index;
if (!toolCallMap[index]) {
toolCallMap[index] = {
id: toolCall.id,
name: toolCall.function?.name,
arguments: ''
};
logger_1.logger.debug('Started tool call accumulation', {
index,
toolId: toolCall.id,
toolName: toolCall.function?.name
}, requestId);
}
// 累积工具参数
if (toolCall.function?.arguments) {
toolCallMap[index].arguments += toolCall.function.arguments;
logger_1.logger.debug('Added tool arguments', {
index,
addedArgs: toolCall.function.arguments,
totalArgsLength: toolCallMap[index].arguments.length
}, requestId);
}
}
}
// 处理usage信息
if (event.usage) {
usage = event.usage;
}
}
catch (error) {
logger_1.logger.error('Error processing OpenAI event in buffered conversion', {
error: error instanceof Error ? error.message : String(error)
}, requestId);
}
}
// 构建最终内容数组
const finalContent = [];
// 检查文本内容中是否包含工具调用模式
let processedTextContent = textContent.trim();
let extractedToolCalls = [];
if (processedTextContent) {
// 检测和提取工具调用模式 "⏺ Tool call: ToolName(...)" 或 "Tool call: ToolName(...)"
const toolCallPattern = /(?:⏺\s*)?Tool call:\s*(\w+)\((.*?)\)(?:\n|$)/g;
let match;
let toolCallIndex = Object.keys(toolCallMap).length; // 继续现有的工具调用索引
while ((match = toolCallPattern.exec(processedTextContent)) !== null) {
const [fullMatch, toolName, argsString] = match;
let toolInput = {};
logger_1.logger.info('Detected tool call pattern in text', {
fullMatch,
toolName,
argsString
}, requestId);
// 尝试解析工具参数
try {
if (argsString.trim()) {
// 如果参数看起来像JSON,尝试解析
if (argsString.trim().startsWith('{') && argsString.trim().endsWith('}')) {
toolInput = JSON.parse(argsString);
}
else {
// 否则,假设它是一个简单的命令字符串
toolInput = { command: argsString };
}
}
}
catch (e) {
logger_1.logger.warn('Failed to parse tool call arguments from text', {
argsString,
error: e instanceof Error ? e.message : String(e)
}, requestId);
toolInput = { command: argsString };
}
extractedToolCalls.push({
id: `extracted_${Date.now()}_${toolCallIndex}`,
name: toolName,
input: toolInput
});
toolCallIndex++;
}
if (extractedToolCalls.length > 0) {
logger_1.logger.info('Extracted tool calls from text content', {
extractedCount: extractedToolCalls.length,
toolNames: extractedToolCalls.map(t => t.name)
}, requestId);
// 移除文本中的工具调用模式,保留剩余文本
processedTextContent = processedTextContent.replace(toolCallPattern, '').trim();
}
}
// 添加剩余的文本内容(如果有)
if (processedTextContent) {
finalContent.push({
type: 'text',
text: processedTextContent
});
logger_1.logger.debug('Added text content block', {
textLength: processedTextContent.length
}, requestId);
}
// 添加工具调用(按index排序)
const sortedToolCalls = Object.entries(toolCallMap).sort(([a], [b]) => parseInt(a) - parseInt(b));
for (const [index, toolCall] of sortedToolCalls) {
try {
let parsedInput = {};
if (toolCall.arguments) {
try {
parsedInput = JSON.parse(toolCall.arguments);
logger_1.logger.info('Successfully parsed tool arguments', {
index,
toolId: toolCall.id,
toolName: toolCall.name,
argumentsJson: toolCall.arguments,
parsedInput: JSON.stringify(parsedInput)
}, requestId);
}
catch (parseError) {
logger_1.logger.error('Failed to parse tool arguments JSON', {
index,
toolId: toolCall.id,
argumentsJson: toolCall.arguments,
error: parseError instanceof Error ? parseError.message : String(parseError)
}, requestId);
parsedInput = {};
}
}
finalContent.push({
type: 'tool_use',
id: toolCall.id || `call_${Date.now()}_${index}`,
name: toolCall.name || `tool_${index}`,
input: parsedInput
});
}
catch (error) {
logger_1.logger.error('Error processing tool call', {
index,
error: error instanceof Error ? error.message : String(error)
}, requestId);
}
}
// 添加从文本中提取的工具调用
for (const extractedTool of extractedToolCalls) {
finalContent.push({
type: 'tool_use',
id: extractedTool.id,
name: extractedTool.name,
input: extractedTool.input
});
logger_1.logger.info('Added extracted tool call', {
toolId: extractedTool.id,
toolName: extractedTool.name,
toolInput: extractedTool.input
}, requestId);
}
logger_1.logger.info('OpenAI buffered response conversion completed', {
originalEvents: events.length,
contentBlocks: finalContent.length,
textBlocks: finalContent.filter(c => c.type === 'text').length,
toolBlocks: finalContent.filter(c => c.type === 'tool_use').length,
extractedToolCalls: extractedToolCalls.length,
regularToolCalls: Object.keys(toolCallMap).length
}, requestId);
return {
content: finalContent,
usage: usage ? {
input_tokens: usage.prompt_tokens || 0,
output_tokens: usage.completion_tokens || 0
} : { input_tokens: 0, output_tokens: 0 }
};
}
/**
* 将缓冲响应转换回Anthropic流式事件格式
* 重新生成标准的Anthropic流式事件
*/
function convertBufferedResponseToAnthropicStream(bufferedResponse, requestId, modelName) {
const events = [];
const messageId = `msg_${Date.now()}`;
logger_1.logger.debug('Converting buffered response to Anthropic stream events', {
contentBlocks: bufferedResponse.content.length,
modelName: modelName
}, requestId);
// 1. message_start event
events.push({
event: 'message_start',
data: {
type: 'message_start',
message: {
id: messageId,
type: 'message',
role: 'assistant',
content: [],
model: modelName,
stop_reason: null,
stop_sequence: null,
usage: bufferedResponse.usage || { input_tokens: 0, output_tokens: 0 }
}
}
});
// 2. ping event
events.push({
event: 'ping',
data: { type: 'ping' }
});
// 3. Process each content block
bufferedResponse.content.forEach((block, index) => {
// content_block_start
if (block.type === 'text') {
events.push({
event: 'content_block_start',
data: {
type: 'content_block_start',
index: index,
content_block: {
type: 'text',
text: ''
}
}
});
// Split text into chunks for realistic streaming
const text = block.text || '';
const chunkSize = 50; // Characters per chunk
for (let i = 0; i < text.length; i += chunkSize) {
const chunk = text.slice(i, i + chunkSize);
events.push({
event: 'content_block_delta',
data: {
type: 'content_block_delta',
index: index,
delta: {
type: 'text_delta',
text: chunk
}
}
});
}
}
else if (block.type === 'tool_use') {
events.push({
event: 'content_block_start',
data: {
type: 'content_block_start',
index: index,
content_block: {
type: 'tool_use',
id: block.id,
name: block.name,
input: {}
}
}
});
// Stream tool input JSON
const inputJson = JSON.stringify(block.input || {});
const chunkSize = 20; // Characters per chunk
for (let i = 0; i < inputJson.length; i += chunkSize) {
const chunk = inputJson.slice(i, i + chunkSize);
events.push({
event: 'content_block_delta',
data: {
type: 'content_block_delta',
index: index,
delta: {
type: 'input_json_delta',
partial_json: chunk
}
}
});
}
}
// content_block_stop
events.push({
event: 'content_block_stop',
data: {
type: 'content_block_stop',
index: index
}
});
});
// 4. message_delta event (with stop reason) - 简化逻辑
const hasToolCalls = bufferedResponse.content.some(block => block.type === 'tool_use');
const openaiFinishReason = hasToolCalls ? 'tool_calls' : 'stop';
const finishReason = (0, finish_reason_handler_1.mapFinishReason)(openaiFinishReason);
events.push({
event: 'message_delta',
data: {
type: 'message_delta',
delta: {
stop_reason: finishReason,
stop_sequence: null
},
usage: {
output_tokens: bufferedResponse.usage?.output_tokens || 0
}
}
});
// 5. message_stop event - 只有非工具调用场景才发送
if (finishReason !== 'tool_use') {
events.push({
event: 'message_stop',
data: {
type: 'message_stop'
}
});
}
logger_1.logger.info('Anthropic stream events generated from OpenAI buffered response', {
totalEvents: events.length,
messageId: messageId,
contentBlocks: bufferedResponse.content.length
}, requestId);
return events;
}
//# sourceMappingURL=buffered-processor.js.map