feat: add Gemini direct API backend with OAuth and CLI fallback#131
feat: add Gemini direct API backend with OAuth and CLI fallback#131bbsngg merged 4 commits intoOpenLAIR:mainfrom
Conversation
Zhang-Henry
left a comment
There was a problem hiding this comment.
Code Review
Large PR (~2800 lines added), introducing a full Gemini direct API backend. Overall architecture is clean and test coverage is solid, but there are several issues that should be addressed before merging.
Blocking Issues
1. Undefined model variable in fallback path
server/gemini-api.js (~L2227): In the Code Assist bootstrap error handling block, the fallback call passes model but only requestedModel and activeModel are defined in scope. This will throw a ReferenceError at runtime.
2. contents.push inside multi-tool loop corrupts conversation history
server/gemini-api.js (~L2519): When a model turn returns multiple function calls, contents.push for the model turn happens inside the for loop. After the first iteration, pendingFunctionCalls.splice(...) is already empty, so subsequent model turns will have empty parts. All function responses should be collected and pushed once after the loop completes.
3. Shell injection in glob and grep_search tools
server/gemini-api.js (~L1143-1170): The target path from resolveToolPath is interpolated directly into shell command strings without escaping. If the path contains single quotes (e.g., a user's home dir like O'Brien/project), this breaks out of the quoting. Should use execFile with an argument array instead of string interpolation to avoid shell parsing entirely.
4. SSRF vulnerability in web_fetch
server/gemini-api.js (~L1176): No URL validation — the AI can request internal network addresses (http://169.254.169.254/, http://localhost:3000/admin, etc.). At minimum, reject private IP ranges and loopback addresses. Also note that web_fetch is in READ_ONLY_TOOLS, so it executes without user confirmation in plan mode despite being able to leak internal info.
Should Fix
5. Auth priority inconsistency
queryGeminiApi (~L2200) checks env.GEMINI_API_KEY first (API key priority), but getGeminiAuthHeaders in geminiOAuth.js (~L3013) tries OAuth first. These two layers disagree on precedence, making behavior unpredictable depending on where credentials are set.
6. codeAssistProjectCache never expires
server/gemini-api.js (~L847): The global Map has no TTL. If a user switches accounts, the cached Project ID from the old account persists until process restart.
7. Fragile isError detection
server/gemini-api.js (~L2495): output.startsWith('Error') will false-positive on normal content that begins with "Error" (e.g., reading a file named Error.md). Consider using a structured { success, output } return instead of string prefix conventions.
8. write_todos in READ_ONLY_TOOLS
server/gemini-api.js (~L1024): Semantically a write operation — shouldn't be allowed without confirmation in plan mode.
9. findInTree unbounded traversal
server/utils/geminiOAuth.js (~L3061): Depth-10 recursive readdirSync can block the event loop for seconds in large directories like node_modules. Consider constraining the search scope.
Minor
read_many_files(~L1098): Missing per-file error handling — exceptions bubble up and lose info about which file failed.refreshAccessToken: After writing tokens,Date.now()is used as mtime cache, but nextloadGeminiOAuthTokensreads the real mtime, causing immediate cache invalidation.sessionsDir()runsfs.mkdiron every call — could initialize once.
Response to Code ReviewThanks @Zhang-Henry for the thorough review — all issues were valid. Here's what was addressed in commit acbc370. Blocking Issues (all resolved)1. Undefined 2. 3. Shell injection in 4. SSRF vulnerability in Should Fix (all resolved)5. Auth priority inconsistency 6. 7. Fragile 8. Additional fixes beyond the reviewThese were found via a secondary automated scan and addressed in the same commit:
All 51 existing tests pass. |
…rect API - Fix undefined `model` variable in Code Assist bootstrap fallback (use `activeModel`) - Fix multi-tool loop: collect all function calls/responses, push to contents once instead of per-iteration (prevents corrupted conversation history) - Fix shell injection in glob/grep_search: use execFile with argument arrays instead of string interpolation into shell commands - Fix SSRF in web_fetch: block private/internal network addresses and loopback; remove web_fetch and write_todos from READ_ONLY_TOOLS (both had wrong semantics) - Fix auth priority inconsistency: getGeminiAuthHeaders now checks API key first, matching queryGeminiApi's env-var precedence - Add TTL (30m) to codeAssistProjectCache to prevent stale entries on account switch - Add path sandbox to resolveToolPath: reject paths that escape the working directory - Sanitize sessionId to prevent path traversal in session file storage - Fix getValidAccessToken to attempt refresh when only refresh_token exists - Fix stage tags using original sessionId instead of currentSessionId - Send gemini-error + gemini-complete on 401/403 after session-created (prevents phantom sessions in UI) - Add per-file error handling in read_many_files - Add typeof guard to isError string check Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
acbc370 to
5d63ddd
Compare
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
This PR replaces the Gemini subprocess-only path with a direct API backend that works for both API-key and OAuth-only users.
What changed
server/gemini-api.jsserver/utils/geminiOAuth.jsthoughtSignaturehandlinggemini-2.5-flashfor better runtime stabilityNotes
Test Plan
fnm exec --using=22 node --check server/gemini-api.jsfnm exec --using=22 npm test -- server/__tests__/gemini-api.test.mjs server/__tests__/gemini-session-index.test.mjs server/__tests__/gemini-legacy-thought.test.mjs server/__tests__/session-delete.test.mjs