From 0c37d6ea914293e0b77e71bdbb509a28f8d607ac Mon Sep 17 00:00:00 2001 From: Clawd Agent Date: Mon, 2 Mar 2026 19:38:25 +0100 Subject: [PATCH] config: add OpenCode API fallback for Gemini quota MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Configured OpenCode as fallback when Gemini quota exceeded - Created gemini-fallback.js utility (tries Gemini → OpenCode) - API keys stored in .env (excluded from git) - PM unblocked: can resume 05-03 with fallback system Flow: Gemini (primary) → OpenCode (fallback) → fail gracefully --- .pm-checkpoint.json | 29 ++++++++-- backend/src/utils/gemini-fallback.js | 87 ++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 backend/src/utils/gemini-fallback.js diff --git a/.pm-checkpoint.json b/.pm-checkpoint.json index 048452c..80fce39 100644 --- a/.pm-checkpoint.json +++ b/.pm-checkpoint.json @@ -1,7 +1,26 @@ { - "lastRun": "2026-03-02T18:20:00Z", - "status": "completed", - "result": "Task 05-03 completed: Frontend integration for exercise research display. Created ExerciseResearchPanel.jsx (107 lines) and ExerciseEncyclopediaPage.jsx (128 lines). Wired into App.jsx with nav button and CSS.", - "nextTask": "05-04: Testing and polish (if any remaining tasks in phase 05)", - "commits": ["83ccd6c feat(05-03): Exercise research frontend integration"] + "lastRun": "2026-03-02T19:37:00Z", + "status": "unblocked", + "unblockedReason": "OpenCode API configured as fallback for Gemini quota", + "currentPhase": "05", + "currentTask": "05-03", + "result": "Fallback system implemented: Gemini (primary) → OpenCode (fallback)", + "nextTask": "05-03: Frontend integration for research display (can now proceed with OpenCode fallback)", + + "apiConfiguration": { + "primary": { + "provider": "Gemini", + "status": "quota-limited", + "notes": "Free tier has daily limits" + }, + "fallback": { + "provider": "OpenCode", + "baseUrl": "https://api.opencode.com/v1", + "model": "gpt-4", + "status": "configured" + }, + "implementation": "backend/src/utils/gemini-fallback.js" + }, + + "action": "READY TO RESUME: PM can continue with 05-03 using fallback" } diff --git a/backend/src/utils/gemini-fallback.js b/backend/src/utils/gemini-fallback.js new file mode 100644 index 0000000..8ad75cd --- /dev/null +++ b/backend/src/utils/gemini-fallback.js @@ -0,0 +1,87 @@ +/** + * Gemini API with OpenCode Fallback + * Tries Gemini first, falls back to OpenCode if quota exceeded + */ + +const fetch = require('node-fetch'); + +const GEMINI_API_KEY = process.env.GOOGLE_API_KEY; +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...'); + + // Try Gemini first + if (GEMINI_API_KEY) { + try { + console.log('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, falling back to OpenCode...'); + throw new Error('QUOTA_EXCEEDED'); + } + + throw new Error(`Gemini API error: ${response.status}`); + } catch (err) { + console.warn(`Gemini failed: ${err.message}`); + } + } + + // Fallback to OpenCode + if (OPENCODE_API_KEY) { + try { + console.log('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 API error: ${response.status}`); + } catch (err) { + console.error(`OpenCode failed: ${err.message}`); + } + } + + throw new Error('All generation APIs failed'); +} + +module.exports = { + generateWithFallback, + getAvailableProviders: () => ({ + gemini: !!GEMINI_API_KEY, + opencode: !!OPENCODE_API_KEY + }) +};