-
Notifications
You must be signed in to change notification settings - Fork 548
feat: add Gemini CLI provider integration #647
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1bba6a1
9ecc25b
b94d2f8
981cd6a
de674f1
db37522
eb9cb3d
ed735ef
8a13c41
57a234a
10c3005
2b0f1ff
5065b61
c662da3
e4b84b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,13 @@ | |
|
|
||
| import { BaseProvider } from './base-provider.js'; | ||
| import type { InstallationStatus, ModelDefinition } from './types.js'; | ||
| import { isCursorModel, isCodexModel, isOpencodeModel, type ModelProvider } from '@automaker/types'; | ||
| import { | ||
| isCursorModel, | ||
| isCodexModel, | ||
| isOpencodeModel, | ||
| isGeminiModel, | ||
| type ModelProvider, | ||
| } from '@automaker/types'; | ||
| import * as fs from 'fs'; | ||
| import * as path from 'path'; | ||
|
|
||
|
|
@@ -16,6 +22,7 @@ const DISCONNECTED_MARKERS: Record<string, string> = { | |
| codex: '.codex-disconnected', | ||
| cursor: '.cursor-disconnected', | ||
| opencode: '.opencode-disconnected', | ||
| gemini: '.gemini-disconnected', | ||
| }; | ||
|
|
||
| /** | ||
|
|
@@ -239,8 +246,8 @@ export class ProviderFactory { | |
| model.modelString === modelId || | ||
| model.id.endsWith(`-${modelId}`) || | ||
| model.modelString.endsWith(`-${modelId}`) || | ||
| model.modelString === modelId.replace(/^(claude|cursor|codex)-/, '') || | ||
| model.modelString === modelId.replace(/-(claude|cursor|codex)$/, '') | ||
| model.modelString === modelId.replace(/^(claude|cursor|codex|gemini)-/, '') || | ||
| model.modelString === modelId.replace(/-(claude|cursor|codex|gemini)$/, '') | ||
| ) { | ||
| return model.supportsVision ?? true; | ||
| } | ||
|
|
@@ -267,6 +274,7 @@ import { ClaudeProvider } from './claude-provider.js'; | |
| import { CursorProvider } from './cursor-provider.js'; | ||
| import { CodexProvider } from './codex-provider.js'; | ||
| import { OpencodeProvider } from './opencode-provider.js'; | ||
| import { GeminiProvider } from './gemini-provider.js'; | ||
|
|
||
| // Register Claude provider | ||
| registerProvider('claude', { | ||
|
|
@@ -301,3 +309,11 @@ registerProvider('opencode', { | |
| canHandleModel: (model: string) => isOpencodeModel(model), | ||
| priority: 3, // Between codex (5) and claude (0) | ||
| }); | ||
|
|
||
| // Register Gemini provider | ||
| registerProvider('gemini', { | ||
| factory: () => new GeminiProvider(), | ||
| aliases: ['google'], | ||
| canHandleModel: (model: string) => isGeminiModel(model), | ||
| priority: 4, // Between opencode (3) and codex (5) | ||
| }); | ||
|
Comment on lines
+313
to
+319
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
echo "=== Checking provider-factory.ts for Gemini and other provider priorities ==="
rg -n "priority:" --type ts apps/server/src/providers/provider-factory.ts
echo ""
echo "=== Looking for test file with gemini-3-pro routing test ==="
fd -e test.ts -e spec.ts | xargs grep -l "gemini-3-pro" 2>/dev/null | head -5Repository: AutoMaker-Org/automaker Length of output: 559 🏁 Script executed: #!/bin/bash
echo "=== Checking isGeminiModel and isCursorModel implementation ==="
rg -n "isGeminiModel|isCursorModel" --type ts -A 10 libs/types/src/provider-utils.ts
echo ""
echo "=== Looking for Cursor model map definitions ==="
rg -n "CursorModel|cursorModels" --type ts libs/ -A 5 | head -50Repository: AutoMaker-Org/automaker Length of output: 4737 🏁 Script executed: #!/bin/bash
echo "=== Checking if Cursor provider includes gemini models ==="
rg -n "gemini" --type ts libs/ apps/ | grep -i "cursor\|model" | head -20Repository: AutoMaker-Org/automaker Length of output: 1697 🏁 Script executed: #!/bin/bash
echo "=== Checking test file lines 156-159 ==="
sed -n '150,165p' apps/server/tests/unit/providers/provider-factory.test.ts
echo ""
echo "=== Checking if gemini-3-pro is in GEMINI_MODEL_MAP ==="
rg -n "gemini-3-pro|gemini-3-flash" --type ts libs/types/src/gemini-models.ts -B 2 -A 2
echo ""
echo "=== Checking LEGACY_CURSOR_MODEL_MAP for gemini models ==="
rg -n "LEGACY_CURSOR_MODEL_MAP|gemini-3" --type ts libs/types/src/cursor-models.ts | head -20Repository: AutoMaker-Org/automaker Length of output: 1758 🏁 Script executed: #!/bin/bash
echo "=== Check full GEMINI_MODEL_MAP structure ==="
cat libs/types/src/gemini-models.ts | head -50Repository: AutoMaker-Org/automaker Length of output: 1483 🏁 Script executed: #!/bin/bash
echo "=== Verify the actual check order and model routing logic ==="
sed -n '160,195p' libs/types/src/provider-utils.tsRepository: AutoMaker-Org/automaker Length of output: 1163 🏁 Script executed: #!/bin/bash
echo "=== Check ProviderFactory.getProviderForModel implementation ==="
rg -n "getProviderForModel" --type ts apps/server/src/providers/provider-factory.ts -A 20Repository: AutoMaker-Org/automaker Length of output: 2817 🏁 Script executed: #!/bin/bash
echo "=== Check if there's special handling that overrides getModelProvider ==="
rg -n "gemini-3-pro|LEGACY_CURSOR_MODEL_MAP" --type ts apps/server/src/providers/ -B 3 -A 3Repository: AutoMaker-Org/automaker Length of output: 140 🏁 Script executed: #!/bin/bash
echo "=== Check Cursor provider's canHandleModel implementation ==="
rg -n "canHandleModel" --type ts apps/server/src/providers/provider-factory.ts -B 5 -A 5 | grep -A 5 -B 5 "cursor"Repository: AutoMaker-Org/automaker Length of output: 440 🏁 Script executed: #!/bin/bash
echo "=== Get full provider registrations with canHandleModel details ==="
sed -n '280,330p' apps/server/src/providers/provider-factory.tsRepository: AutoMaker-Org/automaker Length of output: 1357 Correct the line number reference for the test assertion. The test at lines 160-163 (not 156-159) confirms that The architectural concern remains valid: 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| /** | ||
| * POST /auth-gemini endpoint - Connect Gemini CLI to the app | ||
| */ | ||
|
|
||
| import type { Request, Response } from 'express'; | ||
| import { getErrorMessage, logError } from '../common.js'; | ||
| import * as fs from 'fs/promises'; | ||
| import * as path from 'path'; | ||
|
|
||
| const DISCONNECTED_MARKER_FILE = '.gemini-disconnected'; | ||
|
|
||
| /** | ||
| * Creates handler for POST /api/setup/auth-gemini | ||
| * Removes the disconnection marker to allow Gemini CLI to be used | ||
| */ | ||
| export function createAuthGeminiHandler() { | ||
| return async (_req: Request, res: Response): Promise<void> => { | ||
| try { | ||
| const projectRoot = process.cwd(); | ||
| const automakerDir = path.join(projectRoot, '.automaker'); | ||
| const markerPath = path.join(automakerDir, DISCONNECTED_MARKER_FILE); | ||
|
|
||
| // Remove the disconnection marker if it exists | ||
| try { | ||
| await fs.unlink(markerPath); | ||
| } catch { | ||
| // File doesn't exist, nothing to remove | ||
| } | ||
|
|
||
| res.json({ | ||
| success: true, | ||
| message: 'Gemini CLI connected to app', | ||
| }); | ||
| } catch (error) { | ||
| logError(error, 'Auth Gemini failed'); | ||
| res.status(500).json({ | ||
| success: false, | ||
| error: getErrorMessage(error), | ||
| }); | ||
| } | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| /** | ||
| * POST /deauth-gemini endpoint - Disconnect Gemini CLI from the app | ||
| */ | ||
|
|
||
| import type { Request, Response } from 'express'; | ||
| import { getErrorMessage, logError } from '../common.js'; | ||
| import * as fs from 'fs/promises'; | ||
| import * as path from 'path'; | ||
|
|
||
| const DISCONNECTED_MARKER_FILE = '.gemini-disconnected'; | ||
|
|
||
| /** | ||
| * Creates handler for POST /api/setup/deauth-gemini | ||
| * Creates a marker file to disconnect Gemini CLI from the app | ||
| */ | ||
| export function createDeauthGeminiHandler() { | ||
| return async (_req: Request, res: Response): Promise<void> => { | ||
| try { | ||
| const projectRoot = process.cwd(); | ||
| const automakerDir = path.join(projectRoot, '.automaker'); | ||
|
|
||
| // Ensure .automaker directory exists | ||
| await fs.mkdir(automakerDir, { recursive: true }); | ||
|
|
||
| const markerPath = path.join(automakerDir, DISCONNECTED_MARKER_FILE); | ||
|
|
||
| // Create the disconnection marker | ||
| await fs.writeFile(markerPath, 'Gemini CLI disconnected from app'); | ||
|
|
||
| res.json({ | ||
| success: true, | ||
| message: 'Gemini CLI disconnected from app', | ||
| }); | ||
| } catch (error) { | ||
| logError(error, 'Deauth Gemini failed'); | ||
| res.status(500).json({ | ||
| success: false, | ||
| error: getErrorMessage(error), | ||
| }); | ||
| } | ||
| }; | ||
| } | ||
stefandevo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| /** | ||
| * GET /gemini-status endpoint - Get Gemini CLI installation and auth status | ||
| */ | ||
|
|
||
| import type { Request, Response } from 'express'; | ||
| import { GeminiProvider } from '../../../providers/gemini-provider.js'; | ||
| import { getErrorMessage, logError } from '../common.js'; | ||
| import * as fs from 'fs/promises'; | ||
| import * as path from 'path'; | ||
|
|
||
| const DISCONNECTED_MARKER_FILE = '.gemini-disconnected'; | ||
|
|
||
| async function isGeminiDisconnectedFromApp(): Promise<boolean> { | ||
| try { | ||
| const projectRoot = process.cwd(); | ||
| const markerPath = path.join(projectRoot, '.automaker', DISCONNECTED_MARKER_FILE); | ||
| await fs.access(markerPath); | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Creates handler for GET /api/setup/gemini-status | ||
| * Returns Gemini CLI installation and authentication status | ||
| */ | ||
| export function createGeminiStatusHandler() { | ||
| const installCommand = 'npm install -g @google/gemini-cli'; | ||
| const loginCommand = 'gemini'; | ||
|
|
||
| return async (_req: Request, res: Response): Promise<void> => { | ||
| try { | ||
| // Check if user has manually disconnected from the app | ||
| if (await isGeminiDisconnectedFromApp()) { | ||
| res.json({ | ||
| success: true, | ||
| installed: true, | ||
| version: null, | ||
| path: null, | ||
| auth: { | ||
| authenticated: false, | ||
| method: 'none', | ||
| hasApiKey: false, | ||
| }, | ||
| installCommand, | ||
| loginCommand, | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| const provider = new GeminiProvider(); | ||
| const status = await provider.detectInstallation(); | ||
| const auth = await provider.checkAuth(); | ||
|
|
||
| res.json({ | ||
| success: true, | ||
| installed: status.installed, | ||
| version: status.version || null, | ||
| path: status.path || null, | ||
| auth: { | ||
| authenticated: auth.authenticated, | ||
| method: auth.method, | ||
| hasApiKey: auth.hasApiKey || false, | ||
| hasEnvApiKey: auth.hasEnvApiKey || false, | ||
| error: auth.error, | ||
| }, | ||
| installCommand, | ||
| loginCommand, | ||
| }); | ||
| } catch (error) { | ||
| logError(error, 'Get Gemini status failed'); | ||
| res.status(500).json({ | ||
| success: false, | ||
| error: getErrorMessage(error), | ||
| }); | ||
| } | ||
| }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update provider-count tests for the new Gemini registration.
CI reports failures because provider counts now return 5 instead of 4. Please update the test expectations for
getAllProviders()andcheckAllProviders()to account for Gemini.🤖 Prompt for AI Agents