UNPKG

route-claudecode

Version:

Advanced routing and transformation system for Claude Code outputs to multiple AI providers

209 lines 8.29 kB
"use strict"; /** * Gemini Streaming Simulator Module * 流式响应模拟器,基于非流式API响应生成流式输出 * Project owner: Jason Zhang */ Object.defineProperty(exports, "__esModule", { value: true }); exports.GeminiStreamingSimulator = void 0; const logger_1 = require("@/utils/logger"); /** * 流式响应模拟器 * 职责:将非流式响应转换为Anthropic兼容的流式事件 * 🔧 Critical Fix: Use content-driven stop_reason (OpenAI success pattern) */ class GeminiStreamingSimulator { /** * 模拟流式响应 * 基于OpenAI Provider的成功模式:非流式API + 流式模拟 */ static async *simulateStreamingResponse(response, originalRequest, requestId) { if (!response) { throw new Error('GeminiStreamingSimulator: response is required'); } if (!response.content || !Array.isArray(response.content)) { throw new Error('GeminiStreamingSimulator: response must have content array'); } logger_1.logger.debug('Starting streaming simulation', { contentBlocks: response.content.length, responseId: response.id, stopReason: response.stop_reason }, requestId, 'gemini-streaming-simulator'); if (!response.id) { throw new Error('GeminiStreamingSimulator: response.id is required'); } const messageId = response.id; try { // Send message_start event yield { event: 'message_start', data: { type: 'message_start', message: { id: messageId, type: 'message', role: 'assistant', content: [], model: response.model, stop_reason: null, stop_sequence: null, usage: { input_tokens: 0, output_tokens: 0 } } } }; // Send ping event yield { event: 'ping', data: { type: 'ping' } }; // Process each content block for (let i = 0; i < response.content.length; i++) { const block = response.content[i]; // Send content_block_start yield { event: 'content_block_start', data: { type: 'content_block_start', index: i, content_block: block } }; if (block.type === 'text' && block.text) { // Simulate streaming text with chunks yield* this.simulateTextStreaming(block.text, i, requestId); } else if (block.type === 'tool_use') { // Simulate tool use streaming yield* this.simulateToolUseStreaming(block, i, requestId); } // Send content_block_stop yield { event: 'content_block_stop', data: { type: 'content_block_stop', index: i } }; } // Send message_delta with usage yield { event: 'message_delta', data: { type: 'message_delta', delta: {}, usage: { output_tokens: response.usage?.output_tokens ?? 0 } } }; // 🔧 Critical Fix: Use content-driven stop_reason (OpenAI success pattern) if (!response.stop_reason) { throw new Error('GeminiStreamingSimulator: response.stop_reason is required'); } let actualStopReason = response.stop_reason; const hasToolCalls = response.content.some(block => block.type === 'tool_use'); if (hasToolCalls) { actualStopReason = 'tool_use'; // Force tool_use if we have tool calls logger_1.logger.debug('Streaming: Content-driven stop_reason applied', { toolCallCount: response.content.filter(block => block.type === 'tool_use').length, actualStopReason }, requestId, 'gemini-streaming-simulator'); } // Send message_stop event yield { event: 'message_stop', data: { type: 'message_stop', stop_reason: actualStopReason, stop_sequence: null } }; logger_1.logger.debug('Streaming simulation completed', { contentBlocks: response.content.length, actualStopReason, hasToolCalls }, requestId, 'gemini-streaming-simulator'); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger_1.logger.error('Streaming simulation failed', { error: errorMessage, responseId: response.id, contentBlocks: response.content?.length ?? 0 }, requestId, 'gemini-streaming-simulator'); throw error; } } /** * 模拟文本流式输出 */ static async *simulateTextStreaming(text, index, requestId) { const chunkSize = 10; // 每次发送10个字符 const delay = 10; // 10ms延迟模拟真实流式体验 for (let i = 0; i < text.length; i += chunkSize) { const chunk = text.slice(i, i + chunkSize); yield { event: 'content_block_delta', data: { type: 'content_block_delta', index: index, delta: { type: 'text_delta', text: chunk } } }; // Add small delay for realistic streaming effect if (i > 0 && delay > 0) { await new Promise(resolve => setTimeout(resolve, delay)); } } logger_1.logger.debug('Text streaming simulation completed', { index, textLength: text.length, chunkCount: Math.ceil(text.length / chunkSize) }, requestId, 'gemini-streaming-simulator'); } /** * 模拟工具调用流式输出 */ static async *simulateToolUseStreaming(toolUse, index, requestId) { if (!toolUse.input) { logger_1.logger.debug('Tool use has no input to stream', { index, toolName: toolUse.name }, requestId, 'gemini-streaming-simulator'); return; } // 模拟工具参数的流式输出 const inputJson = JSON.stringify(toolUse.input, null, 2); const chunkSize = 20; const delay = 15; // 稍微慢一点模拟工具调用的思考过程 for (let i = 0; i < inputJson.length; i += chunkSize) { const chunk = inputJson.slice(i, i + chunkSize); yield { event: 'content_block_delta', data: { type: 'content_block_delta', index: index, delta: { type: 'input_json_delta', partial_json: chunk } } }; // Add delay for tool use simulation if (i > 0 && delay > 0) { await new Promise(resolve => setTimeout(resolve, delay)); } } logger_1.logger.debug('Tool use streaming simulation completed', { index, toolName: toolUse.name, inputLength: inputJson.length, chunkCount: Math.ceil(inputJson.length / chunkSize) }, requestId, 'gemini-streaming-simulator'); } } exports.GeminiStreamingSimulator = GeminiStreamingSimulator; //# sourceMappingURL=streaming-simulator.js.map