/** * AI API Fallback System * Tries: Ollama (local) → Gemini → OpenRouter → OpenCode */ const fetch = require('node-fetch'); const OLLAMA_URL = process.env.OLLAMA_URL || 'http://localhost:11434'; const OLLAMA_MODEL = process.env.OLLAMA_MODEL || 'deepseek-v3.2:cloud'; const GEMINI_API_KEY = process.env.GOOGLE_API_KEY; const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY; const OPENROUTER_BASE_URL = process.env.OPENROUTER_BASE_URL || 'https://openrouter.ai/api/v1'; const OPENCODE_API_KEY = process.env.OPENCODE_API_KEY; const OPENCODE_BASE_URL = process.env.OPENCODE_BASE_URL || 'https://api.opencode.com/v1'; async function generateWithFallback(prompt, options = {}) { console.log('🤖 Generating content...'); // Tier 1: Try Ollama (local, free) try { console.log(`📍 Tier 1: Attempting Ollama (${OLLAMA_MODEL})...`); const response = await fetch(`${OLLAMA_URL}/api/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, timeout: 30000, body: JSON.stringify({ model: OLLAMA_MODEL, prompt: prompt, stream: false, temperature: options.temperature || 0.7 }) }); if (response.ok) { const data = await response.json(); console.log('✅ Ollama success'); return { success: true, provider: 'ollama', data }; } console.warn(`⚠️ Ollama error: ${response.status}, trying next...`); } catch (err) { console.warn(`Ollama failed: ${err.message}`); } // Tier 2: Try Gemini if (GEMINI_API_KEY) { try { console.log('📍 Tier 2: Attempting Gemini API...'); const response = await fetch( `https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent?key=${GEMINI_API_KEY}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }], generationConfig: options.config || {} }) } ); if (response.ok) { const data = await response.json(); console.log('✅ Gemini API success'); return { success: true, provider: 'gemini', data }; } if (response.status === 429 || response.status === 403) { console.warn('⚠️ Gemini quota exceeded, trying next...'); } else { throw new Error(`Gemini error: ${response.status}`); } } catch (err) { console.warn(`Gemini failed: ${err.message}`); } } // Tier 3: Fallback to OpenRouter if (OPENROUTER_API_KEY) { try { console.log('📍 Tier 3: Attempting OpenRouter API...'); const response = await fetch(`${OPENROUTER_BASE_URL}/chat/completions`, { method: 'POST', headers: { 'Authorization': `Bearer ${OPENROUTER_API_KEY}`, 'Content-Type': 'application/json', 'HTTP-Referer': 'https://gravl.app' }, body: JSON.stringify({ model: options.model || 'openai/gpt-4', messages: [{ role: 'user', content: prompt }], temperature: options.temperature || 0.7, max_tokens: options.maxTokens || 2048 }) }); if (response.ok) { const data = await response.json(); console.log('✅ OpenRouter API success'); return { success: true, provider: 'openrouter', data }; } console.warn(`OpenRouter error: ${response.status}, trying next...`); } catch (err) { console.warn(`OpenRouter failed: ${err.message}`); } } // Tier 4: Final fallback to OpenCode if (OPENCODE_API_KEY) { try { console.log('📍 Tier 4: Attempting OpenCode API...'); const response = await fetch(`${OPENCODE_BASE_URL}/chat/completions`, { method: 'POST', headers: { 'Authorization': `Bearer ${OPENCODE_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ model: options.model || 'gpt-4', messages: [{ role: 'user', content: prompt }], temperature: options.temperature || 0.7, max_tokens: options.maxTokens || 2048 }) }); if (response.ok) { const data = await response.json(); console.log('✅ OpenCode API success'); return { success: true, provider: 'opencode', data }; } throw new Error(`OpenCode error: ${response.status}`); } catch (err) { console.error(`OpenCode failed: ${err.message}`); } } throw new Error('All generation APIs failed (Ollama → Gemini → OpenRouter → OpenCode)'); } module.exports = { generateWithFallback, getAvailableProviders: () => ({ ollama: true, // Always available locally gemini: !!GEMINI_API_KEY, openrouter: !!OPENROUTER_API_KEY, opencode: !!OPENCODE_API_KEY }) };