route-claudecode
Version:
Advanced routing and transformation system for Claude Code outputs to multiple AI providers
391 lines • 14.6 kB
JavaScript
;
/**
* CodeWhisperer 实时流式解析器
* 基于demo2的Go实现移植,实现零延迟二进制流解析
* 项目所有者: Jason Zhang
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CodeWhispererRealtimeParser = void 0;
const logger_1 = require("@/utils/logger");
class CodeWhispererRealtimeParser {
maxFrameSize;
enableCompression;
constructor(options = {}) {
this.maxFrameSize = options.maxFrameSize || 1024 * 1024; // 默认1MB
this.enableCompression = options.enableCompression || false;
}
/**
* 实时解析二进制流 - 基于demo2的ParseEvents函数
* 返回AsyncIterable以支持真正的流式处理
*/
async *parseRealtimeStream(stream, onProgress) {
const buffer = await this.streamToBuffer(stream);
let offset = 0;
let eventsCount = 0;
const totalBytes = buffer.length;
logger_1.logger.debug('开始实时解析CodeWhisperer流', {
bufferSize: totalBytes,
maxFrameSize: this.maxFrameSize,
});
while (offset < buffer.length) {
try {
const frame = this.readBinaryFrame(buffer, offset);
if (!frame) {
break;
}
// 验证帧大小
if (frame.totalLength > this.maxFrameSize) {
logger_1.logger.warn('帧大小超过限制,跳过', {
frameLength: frame.totalLength,
maxFrameSize: this.maxFrameSize,
offset,
});
offset += frame.totalLength;
continue;
}
// 解析帧payload
const events = this.parseFramePayload(frame.payload);
eventsCount += events.length;
// 实时yield每个事件(零延迟转发)
for (const event of events) {
yield event;
}
offset += frame.totalLength;
// 报告进度
if (onProgress) {
onProgress({
parsedBytes: offset,
totalBytes,
eventsCount,
});
}
}
catch (error) {
logger_1.logger.error('解析二进制帧失败', {
error: error instanceof Error ? error.message : String(error),
offset,
remainingBytes: buffer.length - offset,
});
break;
}
}
logger_1.logger.debug('实时流解析完成', {
parsedBytes: offset,
totalBytes,
eventsCount,
});
}
/**
* 解析完整的缓冲区(向后兼容)
*/
parseEvents(responseBuffer) {
const events = [];
let offset = 0;
logger_1.logger.debug('开始解析CodeWhisperer响应', {
bufferLength: responseBuffer.length,
});
while (offset < responseBuffer.length) {
const frame = this.readBinaryFrame(responseBuffer, offset);
if (!frame) {
break;
}
const frameEvents = this.parseFramePayload(frame.payload);
events.push(...frameEvents);
offset += frame.totalLength;
}
logger_1.logger.debug('CodeWhisperer响应解析完成', {
totalEvents: events.length,
parsedBytes: offset,
totalBytes: responseBuffer.length,
});
return events;
}
/**
* 读取二进制帧 - 基于demo2的帧读取逻辑
*/
readBinaryFrame(buffer, offset) {
// 检查是否有足够的字节读取帧头(基于demo2的长度检查)
if (buffer.length - offset < 12) {
logger_1.logger.debug('剩余字节不足12,停止解析', {
remaining: buffer.length - offset,
offset,
});
return null;
}
try {
// 读取总长度和头部长度(基于demo2的binary.Read逻辑)
const totalLen = buffer.readUInt32BE(offset);
const headerLen = buffer.readUInt32BE(offset + 4);
logger_1.logger.debug('读取帧信息', {
totalLen,
headerLen,
currentOffset: offset + 8,
});
// 验证帧长度(基于demo2的长度验证)
if (totalLen > buffer.length - offset + 8) {
logger_1.logger.warn('帧长度无效,停止解析', {
totalLen,
remainingBytes: buffer.length - offset + 8,
offset,
});
return null;
}
// 跳过头部(基于demo2的header跳过逻辑)
const headerOffset = offset + 8;
const payloadOffset = headerOffset + headerLen;
// 计算payload长度
const payloadLen = totalLen - headerLen - 12;
if (payloadLen <= 0) {
// 空payload,跳过CRC32并继续
logger_1.logger.debug('空payload帧,跳过');
return {
totalLength: totalLen,
headerLength: headerLen,
payloadLength: 0,
payload: Buffer.alloc(0),
offset: offset + totalLen,
};
}
// 读取payload
const payload = buffer.subarray(payloadOffset, payloadOffset + payloadLen);
// 跳过CRC32(基于demo2的CRC32跳过)
const nextOffset = offset + totalLen;
logger_1.logger.debug('成功读取二进制帧', {
totalLen,
headerLen,
payloadLen,
payloadPreview: payload.toString('utf8').substring(0, 100),
offset: nextOffset,
});
return {
totalLength: totalLen,
headerLength: headerLen,
payloadLength: payloadLen,
payload,
offset: nextOffset,
};
}
catch (error) {
logger_1.logger.error('读取二进制帧失败', {
error: error instanceof Error ? error.message : String(error),
offset,
bufferLength: buffer.length,
});
return null;
}
}
/**
* 解析帧payload - 基于demo2的payload处理逻辑
*/
parseFramePayload(payload) {
if (payload.length === 0) {
return [];
}
let payloadStr = payload.toString('utf8');
// 移除"vent"前缀(基于demo2的TrimPrefix逻辑)
if (payloadStr.startsWith('vent')) {
payloadStr = payloadStr.substring(4);
}
logger_1.logger.debug('解析payload', {
payloadLen: payload.length,
payloadPreview: payloadStr.substring(0, 100),
});
// 尝试解析为JSON(基于demo2的json.Unmarshal)
try {
const evt = JSON.parse(payloadStr);
logger_1.logger.debug('成功解析事件', {
hasContent: !!evt.content,
hasToolInfo: !!(evt.toolUseId && evt.name),
stop: evt.stop,
});
// 转换为SSE事件(基于demo2的convertAssistantEventToSSE)
const sseEvents = [];
const mainEvent = this.convertAssistantEventToSSE(evt);
if (mainEvent.event) {
sseEvents.push(mainEvent);
}
// 处理工具调用的特殊情况(基于demo2的工具调用处理)
if (evt.toolUseId && evt.name && evt.stop) {
const stopEvent = {
event: 'message_delta',
data: {
type: 'message_delta',
delta: {
stop_reason: 'tool_use',
stop_sequence: null,
},
usage: { output_tokens: 0 },
},
};
sseEvents.push(stopEvent);
logger_1.logger.debug('添加工具调用停止事件');
}
return sseEvents;
}
catch (parseError) {
logger_1.logger.warn('JSON解析失败', {
error: parseError instanceof Error ? parseError.message : String(parseError),
payloadPreview: payloadStr.substring(0, 200),
});
return [];
}
}
/**
* 将AssistantResponseEvent转换为SSEEvent(完全基于demo2的convertAssistantEventToSSE函数)
*/
convertAssistantEventToSSE(evt) {
// 文本内容事件(基于demo2的文本处理)
if (evt.content) {
return {
event: 'content_block_delta',
data: {
type: 'content_block_delta',
index: 0,
delta: {
type: 'text_delta',
text: evt.content,
},
},
};
}
// 工具调用事件(基于demo2的工具调用处理)
if (evt.toolUseId && evt.name && !evt.stop) {
if (!evt.input) {
// 工具调用开始事件
return {
event: 'content_block_start',
data: {
type: 'content_block_start',
index: 1,
content_block: {
type: 'tool_use',
id: evt.toolUseId,
name: evt.name,
input: {},
},
},
};
}
else {
// 工具调用输入增量事件
return {
event: 'content_block_delta',
data: {
type: 'content_block_delta',
index: 1,
delta: {
type: 'input_json_delta',
id: evt.toolUseId,
name: evt.name,
partial_json: evt.input,
},
},
};
}
}
// 停止事件(基于demo2的停止处理)
if (evt.stop) {
return {
event: 'content_block_stop',
data: {
type: 'content_block_stop',
index: 1,
},
};
}
// 无效事件
return { event: '', data: null };
}
/**
* 将流转换为Buffer(用于向后兼容)
*/
async streamToBuffer(stream) {
return new Promise((resolve, reject) => {
const chunks = [];
let totalLength = 0;
stream.on('data', (chunk) => {
chunks.push(chunk);
totalLength += chunk.length;
});
stream.on('end', () => {
resolve(Buffer.concat(chunks, totalLength));
});
stream.on('error', reject);
});
}
/**
* 实时工具调用解析 - 立即处理而不缓冲
*/
parseToolCallsRealtime(events) {
const textContent = '';
const toolCalls = [];
let currentTool = null;
for (const event of events) {
if (!event.data || typeof event.data !== 'object')
continue;
const dataMap = event.data;
switch (dataMap.type) {
case 'content_block_start':
if (dataMap.content_block?.type === 'tool_use') {
// 开始新的工具调用
currentTool = {
id: dataMap.content_block.id,
name: dataMap.content_block.name,
jsonFragments: [],
};
}
break;
case 'content_block_delta':
if (dataMap.delta?.type === 'input_json_delta' && currentTool) {
// 收集JSON片段
if (dataMap.delta.partial_json) {
currentTool.jsonFragments.push(dataMap.delta.partial_json);
}
}
else if (dataMap.delta?.type === 'text_delta') {
// 收集文本内容
// textContent += dataMap.delta.text;
}
break;
case 'content_block_stop':
if (dataMap.index === 1 && currentTool) {
// 完成工具调用
try {
const completeJson = currentTool.jsonFragments.join('');
const toolInput = JSON.parse(completeJson);
toolCalls.push({
id: currentTool.id,
name: currentTool.name,
input: toolInput,
});
}
catch (parseError) {
logger_1.logger.warn('工具JSON解析失败', {
toolId: currentTool.id,
error: parseError instanceof Error ? parseError.message : String(parseError),
});
}
currentTool = null;
}
break;
}
}
return {
textContent,
toolCalls,
};
}
/**
* 构建非流式响应
*/
buildNonStreamResponse(events, model) {
// 简单构建响应对象
return {
content: events.filter(e => e.data?.content).map(e => e.data.content).join(''),
model,
events
};
}
}
exports.CodeWhispererRealtimeParser = CodeWhispererRealtimeParser;
//# sourceMappingURL=parser-realtime.js.map