UNPKG

@stevekaplanai/google-ai-mcp

Version:

Model Context Protocol server for Google AI services (VEO 3, Imagen 4, Gemini, Lyria 2)

273 lines 11.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ImagenService = void 0; const google_auth_library_1 = require("google-auth-library"); const base_service_js_1 = require("./base.service.js"); class ImagenService extends base_service_js_1.BaseGoogleService { auth; MAX_RETRIES = 3; RETRY_DELAY = 1000; VALID_ASPECT_RATIOS = ['1:1', '16:9', '9:16', '4:3', '3:4']; VALID_PERSON_GENERATION = ['allow', 'disallow']; constructor(apiKey, config) { super(apiKey, config); // Initialize Google Auth with service account credentials const credentials = process.env.GOOGLE_APPLICATION_CREDENTIALS_JSON; if (credentials) { try { let parsedCredentials = JSON.parse(credentials); // Fix the private key by replacing \\n with actual newlines if (parsedCredentials.private_key) { parsedCredentials.private_key = parsedCredentials.private_key.replace(/\\n/g, '\n'); } this.auth = new google_auth_library_1.GoogleAuth({ credentials: parsedCredentials, scopes: ['https://www.googleapis.com/auth/cloud-platform'] }); } catch (error) { console.error('Failed to parse service account credentials:', error); this.auth = new google_auth_library_1.GoogleAuth({ scopes: ['https://www.googleapis.com/auth/cloud-platform'] }); } } else { // Fall back to Application Default Credentials this.auth = new google_auth_library_1.GoogleAuth({ scopes: ['https://www.googleapis.com/auth/cloud-platform'] }); } } async generateImage(prompt, aspectRatio = '1:1', sampleCount = 1, negativePrompt, personGeneration = 'allow', language = 'en', outputStorageUri) { if (this.mockMode) { return this.mockGenerateImage(prompt, aspectRatio, sampleCount); } try { // Validate parameters this.validateParameters({ prompt, aspectRatio, sampleCount, personGeneration, language }); // Build request payload const requestPayload = this.buildRequestPayload({ prompt, aspectRatio, sampleCount, negativePrompt, personGeneration, language, outputStorageUri }); // Execute with retry logic const response = await this.executeWithRetry(() => this.callImagenAPI(requestPayload)); return this.parseImagenResponse(response); } catch (error) { throw this.handleImagenError(error); } } validateParameters(params) { // Validate prompt if (!params.prompt || params.prompt.trim().length === 0) { throw new Error('Prompt cannot be empty'); } if (params.prompt.length > 10000) { throw new Error('Prompt exceeds maximum length of 10,000 characters'); } // Validate aspect ratio if (!this.VALID_ASPECT_RATIOS.includes(params.aspectRatio)) { throw new Error(`Invalid aspect ratio. Must be one of: ${this.VALID_ASPECT_RATIOS.join(', ')}`); } // Validate sample count if (params.sampleCount < 1 || params.sampleCount > 8) { throw new Error('Sample count must be between 1 and 8'); } // Validate person generation if (!this.VALID_PERSON_GENERATION.includes(params.personGeneration)) { throw new Error(`Invalid person generation setting. Must be one of: ${this.VALID_PERSON_GENERATION.join(', ')}`); } // Validate language code (basic check) if (params.language.length !== 2) { throw new Error('Language must be a 2-letter ISO code'); } } buildRequestPayload(params) { const payload = { instances: [{ prompt: params.prompt }], parameters: { sampleCount: params.sampleCount, aspectRatio: params.aspectRatio, addWatermark: false, safetySetting: params.personGeneration === 'disallow' ? 'block_all' : 'default', language: params.language } }; if (params.negativePrompt) { payload.parameters.negativePrompt = params.negativePrompt; } if (params.outputStorageUri) { payload.parameters.storageUri = params.outputStorageUri; } return payload; } async callImagenAPI(payload) { try { // Get authenticated client const client = await this.auth.getClient(); // Get access token const accessToken = await client.getAccessToken(); if (!accessToken.token) { throw new Error('Failed to obtain access token'); } // Construct the endpoint URL for Imagen 4 const projectId = this.getProjectId(); const location = this.getRegion(); const endpoint = `https://${location}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${location}/publishers/google/models/imagen-3.0-generate-001:predict`; this.log('Calling Imagen API', { endpoint, sampleCount: payload.parameters.sampleCount }); const response = await fetch(endpoint, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken.token}`, 'Content-Type': 'application/json', }, body: JSON.stringify(payload) }); const responseText = await response.text(); if (!response.ok) { this.logError('Imagen API error', { status: response.status, statusText: response.statusText, response: responseText }); let errorMessage = `Imagen API error: ${response.status} ${response.statusText}`; try { const errorData = JSON.parse(responseText); if (errorData.error?.message) { errorMessage = errorData.error.message; } } catch (e) { // If response is not JSON, use the text errorMessage += `: ${responseText}`; } throw new Error(errorMessage); } return JSON.parse(responseText); } catch (error) { this.logError('Failed to call Imagen API', error); throw error; } } parseImagenResponse(response) { const predictions = response.predictions || []; const images = []; predictions.forEach((prediction, index) => { if (prediction.bytesBase64Encoded) { images.push({ base64: prediction.bytesBase64Encoded, mimeType: prediction.mimeType || 'image/png', generationId: `imagen-${Date.now()}-${index}` }); } }); if (images.length === 0) { throw new Error('No images were generated'); } return { images, metadata: { model: 'imagen-3.0-generate-001', generatedAt: new Date().toISOString(), totalImages: images.length } }; } async executeWithRetry(operation, retryCount = 0) { try { return await operation(); } catch (error) { if (retryCount >= this.MAX_RETRIES) { throw error; } // Check if error is retryable if (this.isRetryableError(error)) { const delay = this.RETRY_DELAY * Math.pow(2, retryCount); this.log(`Retrying after ${delay}ms (attempt ${retryCount + 1}/${this.MAX_RETRIES})`); await this.delay(delay); return this.executeWithRetry(operation, retryCount + 1); } throw error; } } isRetryableError(error) { // Retry on rate limits, temporary failures const retryableCodes = [429, 503, 504]; if (error.status && retryableCodes.includes(error.status)) { return true; } // Check for specific error messages const retryableMessages = ['rate limit', 'temporarily unavailable', 'timeout', 'quota exceeded']; const errorMessage = error.message?.toLowerCase() || ''; return retryableMessages.some(msg => errorMessage.includes(msg)); } handleImagenError(error) { // Handle specific Imagen API errors if (error.message?.includes('429') || error.message?.includes('rate limit')) { return new Error('Rate limit exceeded. Please try again later.'); } if (error.message?.includes('400') || error.message?.includes('invalid')) { return new Error(`Invalid request: ${error.message || 'Check your parameters'}`); } if (error.message?.includes('403') || error.message?.includes('permission')) { return new Error('Permission denied. Check your API access and project permissions.'); } if (error.message?.includes('safety') || error.message?.includes('blocked')) { return new Error('Content was blocked by safety filters. Try modifying your prompt.'); } if (error.message?.includes('quota')) { return new Error('Quota exceeded. Check your project quotas in Google Cloud Console.'); } return new Error(`Imagen generation failed: ${error.message || 'Unknown error'}`); } delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } mockGenerateImage(prompt, aspectRatio, sampleCount) { this.log('Generating mock images', { prompt, aspectRatio, sampleCount }); const mockImages = Array(sampleCount).fill(null).map((_, index) => ({ base64: `mock-base64-image-data-${index}-for-prompt-${prompt.substring(0, 20)}`, mimeType: 'image/png', generationId: `mock-imagen-${Date.now()}-${index}` })); return { images: mockImages, metadata: { model: 'imagen-3.0-generate-001', generatedAt: new Date().toISOString(), totalImages: mockImages.length, mockData: true } }; } // Helper methods for configuration getRegion() { return process.env.GOOGLE_CLOUD_LOCATION || process.env.GOOGLE_CLOUD_REGION || 'us-central1'; } getProjectId() { const projectId = process.env.GOOGLE_CLOUD_PROJECT; if (!projectId) { throw new Error('GOOGLE_CLOUD_PROJECT environment variable is required'); } return projectId; } } exports.ImagenService = ImagenService; //# sourceMappingURL=imagen.service.js.map