/**
* Google Gemini provider adapter
*/
import { GoogleGenerativeAI } from '@google/generative-ai';
import { BaseAIProvider } from './base.js';
import { AIProviderConfig, AIResponse, Message } from '../types.js';
export class GeminiProvider extends BaseAIProvider {
private client: GoogleGenerativeAI | null = null;
private getClient(apiKey?: string): GoogleGenerativeAI {
if (!this.client) {
const key = apiKey || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
if (!key) {
throw new Error('Gemini API key not found. Set GEMINI_API_KEY or GOOGLE_API_KEY environment variable.');
}
this.client = new GoogleGenerativeAI(key);
}
return this.client;
}
async call(messages: Message[], config: AIProviderConfig): Promise<AIResponse> {
const client = this.getClient(config.apiKey);
// Get the model
const model = client.getGenerativeModel({
model: config.model,
systemInstruction: config.systemPrompt
});
// Gemini uses a different message format
// Combine system and user messages
const systemMessage = messages.find(m => m.role === 'system');
const userMessages = messages.filter(m => m.role !== 'system');
// Build the prompt
let prompt = '';
if (systemMessage && !config.systemPrompt) {
prompt += systemMessage.content + '\n\n';
}
// Add user messages
userMessages.forEach(msg => {
prompt += msg.content;
});
// Configure generation
const generationConfig = {
temperature: config.temperature ?? 0.7,
maxOutputTokens: config.maxTokens ?? 2000,
};
// Generate content
const result = await model.generateContent({
contents: [{ role: 'user', parts: [{ text: prompt }] }],
generationConfig
});
const response = result.response;
// Check if response was blocked
if (!response.candidates || response.candidates.length === 0) {
throw new Error(`Gemini response blocked. Reason: ${response.promptFeedback?.blockReason || 'Unknown'}`);
}
// Get text from the first candidate
const candidate = response.candidates[0];
if (!candidate.content || !candidate.content.parts || candidate.content.parts.length === 0) {
throw new Error(`No content in Gemini response. Finish reason: ${candidate.finishReason || 'Unknown'}`);
}
const text = candidate.content.parts.map(part => part.text || '').join('');
if (!text || text.trim().length === 0) {
throw new Error(`Empty response from Gemini. Finish reason: ${candidate.finishReason}, Safety ratings: ${JSON.stringify(candidate.safetyRatings)}`);
}
// Extract usage metadata if available
const usageMetadata = response.usageMetadata;
return {
content: text,
model: config.model,
usage: usageMetadata ? {
promptTokens: usageMetadata.promptTokenCount || 0,
completionTokens: usageMetadata.candidatesTokenCount || 0,
totalTokens: usageMetadata.totalTokenCount || 0
} : undefined,
metadata: {
finishReason: candidate.finishReason,
safetyRatings: candidate.safetyRatings
}
};
}
}