import { GoogleGenerativeAI } from '@google/generative-ai';
import { IGenAI, GenerateUrlContextInput, GenerateUrlContextResponse } from './IGenAI.js';
import { UpstreamUnavailableError, SafetyBlockedError, RateLimitError } from '../domain/DomainError.js';
export class GoogleGenAI implements IGenAI {
private readonly genAI: GoogleGenerativeAI;
constructor(apiKey: string) {
this.genAI = new GoogleGenerativeAI(apiKey);
}
async generateUrlContextJson(input: GenerateUrlContextInput): Promise<GenerateUrlContextResponse> {
const startTime = Date.now();
try {
const model = this.genAI.getGenerativeModel({
model: input.model,
generationConfig: {
responseMimeType: 'application/json'
}
});
const prompt = this.buildPrompt(input);
const result = await model.generateContent(prompt);
const response = result.response;
const text = response.text();
const processingTimeMs = Date.now() - startTime;
try {
const parsedResponse = JSON.parse(text);
// Update actual processing time
if (parsedResponse.url_context_metadata) {
parsedResponse.url_context_metadata.processingTimeMs = processingTimeMs;
}
return parsedResponse;
} catch (parseError) {
throw new Error(`Failed to parse JSON response: ${parseError}`);
}
} catch (error) {
const processingTimeMs = Date.now() - startTime;
// Map specific errors to domain errors
this.mapAndThrowError(error as Error);
// Return error response with metadata as fallback
return {
pages: [],
answer: '',
url_context_metadata: {
status: 'failed',
totalUrls: input.urls.length,
successfulUrls: 0,
failedUrls: input.urls,
processingTimeMs
}
};
}
}
private buildPrompt(input: GenerateUrlContextInput): string {
const urlsText = input.urls.join('\\n');
const queryText = input.query || 'Please extract and summarize the content';
return `
Please analyze the following URLs and extract their content. Return a JSON response with the exact schema specified.
URLs to analyze:
${urlsText}
Query: ${queryText}
Instructions:
1. For each URL, extract the title, main text content (max ${input.maxCharsPerPage} characters), and all image URLs
2. Make image URLs absolute (convert relative URLs to absolute ones based on the page's domain)
3. Provide a summary answer to the query based on all the content
4. Include metadata about the processing status
Return only valid JSON following the exact response schema.
`;
}
private mapAndThrowError(error: Error): void {
const errorMessage = error.message.toLowerCase();
if (errorMessage.includes('safety') || errorMessage.includes('blocked')) {
throw new SafetyBlockedError(error.message);
}
if (errorMessage.includes('rate limit') || errorMessage.includes('quota')) {
throw new RateLimitError(error.message);
}
if (errorMessage.includes('network') || errorMessage.includes('timeout') ||
errorMessage.includes('unavailable') || errorMessage.includes('connection')) {
throw new UpstreamUnavailableError(error.message, error);
}
// For other errors, throw as upstream unavailable
throw new UpstreamUnavailableError(`Gemini API error: ${error.message}`, error);
}
}