UNPKG

ai-platform-converter

Version:

Lossless API parameter conversion between multiple AI platforms (OpenAI, Anthropic, Gemini, DeepSeek, Wenwen, Vertex AI, Huawei, BigModel)

209 lines (208 loc) 8.66 kB
"use strict"; /** * Convert Gemini response to OpenAI format */ Object.defineProperty(exports, "__esModule", { value: true }); exports.convertGeminiResponseToOpenAI = convertGeminiResponseToOpenAI; exports.recoverGeminiResponse = recoverGeminiResponse; exports.convertGeminiResponseToOpenAIWithRecovery = convertGeminiResponseToOpenAIWithRecovery; const common_1 = require("../../types/common"); const helpers_1 = require("../../utils/helpers"); function convertGeminiResponseToOpenAI(response, options) { // Handle stringified responses (common issue with nested JSON.stringify) let processedResponse = response; if (typeof response === 'string') { if (options?.debug) { console.log('🔧 Detected stringified response, attempting to parse...'); } try { // Try to parse the stringified response processedResponse = JSON.parse(response); if (options?.debug) { console.log('✅ Successfully parsed stringified response'); } } catch (parseError) { // If parsing fails, provide detailed error information const responseStr = response; const errorDetails = { responseType: typeof response, stringLength: responseStr.length, stringStart: responseStr.substring(0, 100) + '...', parseError: parseError instanceof Error ? parseError.message : 'Unknown parsing error' }; if (options?.debug) { console.log('❌ Failed to parse stringified response:', errorDetails); } throw new Error(`Gemini response is a string but could not be parsed as JSON. Details: ${JSON.stringify(errorDetails)}`); } } // Enhanced debugging for the candidates issue const debugInfo = { responseType: typeof processedResponse, hasCandidates: !!processedResponse?.candidates, candidatesType: typeof processedResponse?.candidates, candidatesIsArray: Array.isArray(processedResponse?.candidates), candidatesLength: processedResponse?.candidates?.length, responseKeys: processedResponse ? Object.keys(processedResponse) : [], allKeys: processedResponse ? JSON.stringify(Object.keys(processedResponse)) : 'null', wasStringified: typeof response === 'string' }; // Log debug information if requested if (options?.debug) { console.log('🔍 Gemini Response Debug Info:', debugInfo); console.log('🔍 Full Response Keys:', debugInfo.allKeys); if (processedResponse?.candidates) { console.log('🔍 Candidates Details:', { exists: !!processedResponse.candidates, isArray: Array.isArray(processedResponse.candidates), length: processedResponse.candidates.length, firstItem: processedResponse.candidates[0] }); } } // Check if candidates array exists and has at least one element if (!processedResponse.candidates || processedResponse.candidates.length === 0) { const errorDetails = { debugInfo, response: processedResponse ? 'object exists' : 'null/undefined', originalString: JSON.stringify(processedResponse).substring(0, 500) + '...' }; throw new Error(`Gemini response has no candidates. Debug info: ${JSON.stringify(errorDetails)}`); } const candidate = processedResponse.candidates[0]; // Check if candidate has content and parts if (!candidate.content || !candidate.content.parts || candidate.content.parts.length === 0) { throw new Error('Gemini candidate has no content parts'); } // Convert content to OpenAI message const message = convertGeminiCandidateToMessage(candidate); // Map finish reason const finishReason = (0, helpers_1.mapFinishReason)(candidate.finishReason, common_1.Platform.Gemini, common_1.Platform.OpenAI); // Build OpenAI response const openaiResponse = { id: `gemini-${Date.now()}`, object: 'chat.completion', created: Math.floor(Date.now() / 1000), model: 'gemini-model', choices: [{ index: candidate.index || 0, message, finish_reason: finishReason }], usage: processedResponse.usageMetadata ? { prompt_tokens: processedResponse.usageMetadata.promptTokenCount, completion_tokens: processedResponse.usageMetadata.candidatesTokenCount, total_tokens: processedResponse.usageMetadata.totalTokenCount } : undefined, // Preserve extensions _extensions: { platform: common_1.Platform.Gemini, originalParams: options?.preserveExtensions ? (0, helpers_1.deepClone)(processedResponse) : undefined, gemini: { safetyRatings: candidate.safetyRatings, citationMetadata: candidate.citationMetadata, promptFeedback: processedResponse.promptFeedback } } }; return (0, helpers_1.removeUndefined)(openaiResponse); } function convertGeminiCandidateToMessage(candidate) { const textParts = []; const toolCalls = []; // Defensive check for parts array if (!candidate.content || !candidate.content.parts) { return { role: 'assistant', content: null }; } for (const part of candidate.content.parts) { if ('text' in part) { textParts.push(part.text); } else if ('functionCall' in part) { toolCalls.push({ id: `call_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, type: 'function', function: { name: part.functionCall.name, arguments: JSON.stringify(part.functionCall.args) } }); } } const message = { role: 'assistant', content: textParts.length > 0 ? textParts.join('\n') : null }; if (toolCalls.length > 0) { message.tool_calls = toolCalls; } return message; } /** * Attempt to recover a corrupted Gemini response * This function tries to restore missing candidates from other parts of the response */ function recoverGeminiResponse(response) { if (!response || typeof response !== 'object') { return null; } // If candidates already exist and are valid, return as-is if (response.candidates && Array.isArray(response.candidates) && response.candidates.length > 0) { return response; } // Try to recover candidates from other fields let recoveredCandidates = []; // Check if there's a direct content field if (response.content) { recoveredCandidates.push({ content: response.content, finishReason: response.finishReason || 'STOP' }); } // Check if there's a data field that might contain the response if (response.data && response.data.candidates) { recoveredCandidates = response.data.candidates; } // Check if there's a result field if (response.result && response.result.candidates) { recoveredCandidates = response.result.candidates; } // If we couldn't recover any candidates, return null if (recoveredCandidates.length === 0) { return null; } // Return a reconstructed response return { ...response, candidates: recoveredCandidates }; } /** * Enhanced conversion function with recovery attempt */ function convertGeminiResponseToOpenAIWithRecovery(response, options) { const { enableRecovery = true, ...convertOptions } = options || {}; try { // Try direct conversion first return convertGeminiResponseToOpenAI(response, convertOptions); } catch (error) { if (enableRecovery && error instanceof Error && error.message.includes('no candidates')) { console.warn('🔧 Attempting to recover corrupted Gemini response...'); const recoveredResponse = recoverGeminiResponse(response); if (recoveredResponse) { console.log('✅ Successfully recovered Gemini response'); return convertGeminiResponseToOpenAI(recoveredResponse, { ...convertOptions, debug: true // Enable debug for recovered responses }); } } // If recovery failed or wasn't enabled, re-throw the original error throw error; } }