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
JavaScript
;
/**
* 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;
}
}