mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-31 06:42:03 +00:00
feat: add Gemini CLI provider integration (#647)
* feat: add Gemini CLI provider for AI model execution - Add GeminiProvider class extending CliProvider for Gemini CLI integration - Add Gemini models (Gemini 3 Pro/Flash Preview, 2.5 Pro/Flash/Flash-Lite) - Add gemini-models.ts with model definitions and types - Update ModelProvider type to include 'gemini' - Add isGeminiModel() to provider-utils.ts for model detection - Register Gemini provider in provider-factory with priority 4 - Add Gemini setup detection routes (status, auth, deauth) - Add GeminiCliStatus to setup store for UI state management - Add Gemini to PROVIDER_ICON_COMPONENTS for UI icon display - Add GEMINI_MODELS to model-display for dropdown population - Support thinking levels: off, low, medium, high Based on https://github.com/google-gemini/gemini-cli * chore: update package-lock.json * feat(ui): add Gemini provider to settings and setup wizard - Add GeminiCliStatus component for CLI detection display - Add GeminiSettingsTab component for global settings - Update provider-tabs.tsx to include Gemini as 5th tab - Update providers-setup-step.tsx with Gemini provider detection - Add useGeminiCliStatus hook for querying CLI status - Add getGeminiStatus, authGemini, deauthGemini to HTTP API client - Add gemini query key for React Query - Fix GeminiModelId type to not double-prefix model IDs * feat(ui): add Gemini to settings sidebar navigation - Add 'gemini-provider' to SettingsViewId type - Add GeminiIcon and gemini-provider to navigation config - Add gemini-provider to NAV_ID_TO_PROVIDER mapping - Add gemini-provider case in settings-view switch - Export GeminiSettingsTab from providers index This fixes the missing Gemini entry in the AI Providers sidebar menu. * feat(ui): add Gemini model configuration in settings - Create GeminiModelConfiguration component for model selection - Add enabledGeminiModels and geminiDefaultModel state to app-store - Add setEnabledGeminiModels, setGeminiDefaultModel, toggleGeminiModel actions - Update GeminiSettingsTab to show model configuration when CLI is installed - Import GeminiModelId and getAllGeminiModelIds from types This adds the ability to configure which Gemini models are available in the feature modal, similar to other providers like Codex and OpenCode. * feat(ui): add Gemini models to all model dropdowns - Add GEMINI_MODELS to model-constants.ts for UI dropdowns - Add Gemini to ALL_MODELS array used throughout the app - Add GeminiIcon to PROFILE_ICONS mapping - Fix GEMINI_MODELS in model-display.ts to use correct model IDs - Update getModelDisplayName to handle Gemini models correctly Gemini models now appear in all model selection dropdowns including Model Defaults, Feature Defaults, and feature card settings. * fix(gemini): fix CLI integration and event handling - Fix model ID prefix handling: strip gemini- prefix in agent-service, add it back in buildCliArgs for CLI invocation - Fix event normalization to match actual Gemini CLI output format: - type: 'init' (not 'system') - type: 'message' with role (not 'assistant') - tool_name/tool_id/parameters/output field names - Add --sandbox false and --approval-mode yolo for faster execution - Remove thinking level selector from UI (Gemini CLI doesn't support it) - Update auth status to show errors properly * test: update provider-factory tests for Gemini provider - Add GeminiProvider import and spy mock - Update expected provider count from 4 to 5 - Add test for GeminiProvider inclusion - Add gemini key to checkAllProviders test * fix(gemini): address PR review feedback - Fix npm package name from @anthropic-ai/gemini-cli to @google/gemini-cli - Fix comments in gemini-provider.ts to match actual CLI output format - Convert sync fs operations to async using fs/promises * fix(settings): add Gemini and Codex settings to sync Add enabledGeminiModels, geminiDefaultModel, enabledCodexModels, and codexDefaultModel to SETTINGS_FIELDS_TO_SYNC for persistence across sessions. * fix(gemini): address additional PR review feedback - Use 'Speed' badge for non-thinking Gemini models (consistency) - Fix installCommand mapping in gemini-settings-tab.tsx - Add hasEnvApiKey to GeminiCliStatus interface for API parity - Clarify GeminiThinkingLevel comment (CLI doesn't support --thinking-level) * fix(settings): restore Codex and Gemini settings from server Add sanitization and restoration logic for enabledCodexModels, codexDefaultModel, enabledGeminiModels, and geminiDefaultModel in refreshSettingsFromServer() to match the fields in SETTINGS_FIELDS_TO_SYNC. * feat(gemini): normalize tool names and fix workspace restrictions - Add tool name mapping to normalize Gemini CLI tool names to standard names (e.g., write_todos -> TodoWrite, read_file -> Read) - Add normalizeGeminiToolInput to convert write_todos format to TodoWrite format (description -> content, handle cancelled status) - Pass --include-directories with cwd to fix workspace restriction errors when Gemini CLI has a different cached workspace from previous sessions --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
7773db559d
commit
f480386905
101
libs/types/src/gemini-models.ts
Normal file
101
libs/types/src/gemini-models.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Gemini CLI Model Definitions
|
||||
*
|
||||
* Defines available models for Gemini CLI integration.
|
||||
* Based on https://github.com/google-gemini/gemini-cli
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gemini model configuration
|
||||
*/
|
||||
export interface GeminiModelConfig {
|
||||
label: string;
|
||||
description: string;
|
||||
supportsVision: boolean;
|
||||
supportsThinking: boolean;
|
||||
contextWindow?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Available Gemini models via the Gemini CLI
|
||||
* Models from Gemini 2.5 and 3.0 series
|
||||
*
|
||||
* Model IDs use 'gemini-' prefix for consistent provider routing (like Cursor).
|
||||
* When passed to the CLI, the prefix is part of the actual model name.
|
||||
*/
|
||||
export const GEMINI_MODEL_MAP = {
|
||||
// Gemini 3 Series (latest)
|
||||
'gemini-3-pro-preview': {
|
||||
label: 'Gemini 3 Pro Preview',
|
||||
description: 'Most advanced Gemini model with deep reasoning capabilities.',
|
||||
supportsVision: true,
|
||||
supportsThinking: true,
|
||||
contextWindow: 1000000,
|
||||
},
|
||||
'gemini-3-flash-preview': {
|
||||
label: 'Gemini 3 Flash Preview',
|
||||
description: 'Fast Gemini 3 model for quick tasks.',
|
||||
supportsVision: true,
|
||||
supportsThinking: true,
|
||||
contextWindow: 1000000,
|
||||
},
|
||||
// Gemini 2.5 Series
|
||||
'gemini-2.5-pro': {
|
||||
label: 'Gemini 2.5 Pro',
|
||||
description: 'Advanced model with strong reasoning and 1M context.',
|
||||
supportsVision: true,
|
||||
supportsThinking: true,
|
||||
contextWindow: 1000000,
|
||||
},
|
||||
'gemini-2.5-flash': {
|
||||
label: 'Gemini 2.5 Flash',
|
||||
description: 'Balanced speed and capability for most tasks.',
|
||||
supportsVision: true,
|
||||
supportsThinking: true,
|
||||
contextWindow: 1000000,
|
||||
},
|
||||
'gemini-2.5-flash-lite': {
|
||||
label: 'Gemini 2.5 Flash Lite',
|
||||
description: 'Fastest Gemini model for simple tasks.',
|
||||
supportsVision: true,
|
||||
supportsThinking: false,
|
||||
contextWindow: 1000000,
|
||||
},
|
||||
} as const satisfies Record<string, GeminiModelConfig>;
|
||||
|
||||
/**
|
||||
* Gemini model ID type (keys already have gemini- prefix)
|
||||
*/
|
||||
export type GeminiModelId = keyof typeof GEMINI_MODEL_MAP;
|
||||
|
||||
/**
|
||||
* Get all Gemini model IDs
|
||||
*/
|
||||
export function getAllGeminiModelIds(): GeminiModelId[] {
|
||||
return Object.keys(GEMINI_MODEL_MAP) as GeminiModelId[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Default Gemini model (balanced choice)
|
||||
*/
|
||||
export const DEFAULT_GEMINI_MODEL: GeminiModelId = 'gemini-2.5-flash';
|
||||
|
||||
/**
|
||||
* Thinking level configuration for Gemini models
|
||||
* Note: The Gemini CLI does not currently expose a --thinking-level flag.
|
||||
* Thinking control (thinkingLevel/thinkingBudget) is available via the Gemini API.
|
||||
* This type is defined for potential future CLI support or API-level configuration.
|
||||
*/
|
||||
export type GeminiThinkingLevel = 'off' | 'low' | 'medium' | 'high';
|
||||
|
||||
/**
|
||||
* Gemini CLI authentication status
|
||||
*/
|
||||
export interface GeminiAuthStatus {
|
||||
authenticated: boolean;
|
||||
method: 'google_login' | 'api_key' | 'vertex_ai' | 'none';
|
||||
hasApiKey?: boolean;
|
||||
hasEnvApiKey?: boolean;
|
||||
hasCredentialsFile?: boolean;
|
||||
error?: string;
|
||||
}
|
||||
@@ -205,6 +205,7 @@ export {
|
||||
export type { ModelOption, ThinkingLevelOption, ReasoningEffortOption } from './model-display.js';
|
||||
export {
|
||||
CLAUDE_MODELS,
|
||||
GEMINI_MODELS,
|
||||
THINKING_LEVELS,
|
||||
THINKING_LEVEL_LABELS,
|
||||
REASONING_EFFORT_LEVELS,
|
||||
@@ -249,6 +250,9 @@ export * from './cursor-cli.js';
|
||||
// OpenCode types
|
||||
export * from './opencode-models.js';
|
||||
|
||||
// Gemini types
|
||||
export * from './gemini-models.js';
|
||||
|
||||
// Provider utilities
|
||||
export {
|
||||
PROVIDER_PREFIXES,
|
||||
@@ -256,6 +260,7 @@ export {
|
||||
isClaudeModel,
|
||||
isCodexModel,
|
||||
isOpencodeModel,
|
||||
isGeminiModel,
|
||||
getModelProvider,
|
||||
stripProviderPrefix,
|
||||
addProviderPrefix,
|
||||
|
||||
@@ -10,20 +10,21 @@ import type { ReasoningEffort } from './provider.js';
|
||||
import type { CursorModelId } from './cursor-models.js';
|
||||
import type { AgentModel, CodexModelId } from './model.js';
|
||||
import { CODEX_MODEL_MAP } from './model.js';
|
||||
import { GEMINI_MODEL_MAP, type GeminiModelId } from './gemini-models.js';
|
||||
|
||||
/**
|
||||
* ModelOption - Display metadata for a model option in the UI
|
||||
*/
|
||||
export interface ModelOption {
|
||||
/** Model identifier (supports both Claude and Cursor models) */
|
||||
id: ModelAlias | CursorModelId;
|
||||
/** Model identifier (supports Claude, Cursor, Gemini models) */
|
||||
id: ModelAlias | CursorModelId | GeminiModelId;
|
||||
/** Display name shown to user */
|
||||
label: string;
|
||||
/** Descriptive text explaining model capabilities */
|
||||
description: string;
|
||||
/** Optional badge text (e.g., "Speed", "Balanced", "Premium") */
|
||||
badge?: string;
|
||||
/** AI provider (supports 'claude' and 'cursor') */
|
||||
/** AI provider */
|
||||
provider: ModelProvider;
|
||||
}
|
||||
|
||||
@@ -113,6 +114,22 @@ export const CODEX_MODELS: (ModelOption & { hasReasoning?: boolean })[] = [
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Gemini model options with full metadata for UI display
|
||||
* Based on https://github.com/google-gemini/gemini-cli
|
||||
* Model IDs match the keys in GEMINI_MODEL_MAP (e.g., 'gemini-2.5-flash')
|
||||
*/
|
||||
export const GEMINI_MODELS: (ModelOption & { hasThinking?: boolean })[] = Object.entries(
|
||||
GEMINI_MODEL_MAP
|
||||
).map(([id, config]) => ({
|
||||
id: id as GeminiModelId,
|
||||
label: config.label,
|
||||
description: config.description,
|
||||
badge: config.supportsThinking ? 'Thinking' : 'Speed',
|
||||
provider: 'gemini' as const,
|
||||
hasThinking: config.supportsThinking,
|
||||
}));
|
||||
|
||||
/**
|
||||
* Thinking level options with display labels
|
||||
*
|
||||
@@ -200,5 +217,16 @@ export function getModelDisplayName(model: ModelAlias | string): string {
|
||||
[CODEX_MODEL_MAP.gpt52]: 'GPT-5.2',
|
||||
[CODEX_MODEL_MAP.gpt51]: 'GPT-5.1',
|
||||
};
|
||||
return displayNames[model] || model;
|
||||
|
||||
// Check direct match first
|
||||
if (model in displayNames) {
|
||||
return displayNames[model];
|
||||
}
|
||||
|
||||
// Check Gemini model map - IDs are like 'gemini-2.5-flash'
|
||||
if (model in GEMINI_MODEL_MAP) {
|
||||
return GEMINI_MODEL_MAP[model as keyof typeof GEMINI_MODEL_MAP].label;
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
import type { CursorModelId } from './cursor-models.js';
|
||||
import type { OpencodeModelId } from './opencode-models.js';
|
||||
import type { GeminiModelId } from './gemini-models.js';
|
||||
|
||||
/**
|
||||
* Canonical Claude model IDs with provider prefix
|
||||
@@ -119,6 +120,7 @@ export type DynamicModelId = `${string}/${string}`;
|
||||
*/
|
||||
export type PrefixedCursorModelId = `cursor-${string}`;
|
||||
export type PrefixedOpencodeModelId = `opencode-${string}`;
|
||||
export type PrefixedGeminiModelId = `gemini-${string}`;
|
||||
|
||||
/**
|
||||
* ModelId - Unified model identifier across providers
|
||||
@@ -127,7 +129,9 @@ export type ModelId =
|
||||
| ModelAlias
|
||||
| CodexModelId
|
||||
| CursorModelId
|
||||
| GeminiModelId
|
||||
| OpencodeModelId
|
||||
| DynamicModelId
|
||||
| PrefixedCursorModelId
|
||||
| PrefixedOpencodeModelId;
|
||||
| PrefixedOpencodeModelId
|
||||
| PrefixedGeminiModelId;
|
||||
|
||||
@@ -10,12 +10,14 @@ import type { ModelProvider } from './settings.js';
|
||||
import { CURSOR_MODEL_MAP, LEGACY_CURSOR_MODEL_MAP } from './cursor-models.js';
|
||||
import { CLAUDE_MODEL_MAP, CODEX_MODEL_MAP } from './model.js';
|
||||
import { OPENCODE_MODEL_CONFIG_MAP, LEGACY_OPENCODE_MODEL_MAP } from './opencode-models.js';
|
||||
import { GEMINI_MODEL_MAP } from './gemini-models.js';
|
||||
|
||||
/** Provider prefix constants */
|
||||
export const PROVIDER_PREFIXES = {
|
||||
cursor: 'cursor-',
|
||||
codex: 'codex-',
|
||||
opencode: 'opencode-',
|
||||
gemini: 'gemini-',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
@@ -90,6 +92,28 @@ export function isCodexModel(model: string | undefined | null): boolean {
|
||||
return model in CODEX_MODEL_MAP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a model string represents a Gemini model
|
||||
*
|
||||
* @param model - Model string to check (e.g., "gemini-2.5-pro", "gemini-3-pro-preview")
|
||||
* @returns true if the model is a Gemini model
|
||||
*/
|
||||
export function isGeminiModel(model: string | undefined | null): boolean {
|
||||
if (!model || typeof model !== 'string') return false;
|
||||
|
||||
// Canonical format: gemini- prefix (e.g., "gemini-2.5-flash")
|
||||
if (model.startsWith(PROVIDER_PREFIXES.gemini)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it's a known Gemini model ID (map keys include gemini- prefix)
|
||||
if (model in GEMINI_MODEL_MAP) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a model string represents an OpenCode model
|
||||
*
|
||||
@@ -151,7 +175,11 @@ export function isOpencodeModel(model: string | undefined | null): boolean {
|
||||
* @returns The provider type, defaults to 'claude' for unknown models
|
||||
*/
|
||||
export function getModelProvider(model: string | undefined | null): ModelProvider {
|
||||
// Check OpenCode first since it uses provider-prefixed formats that could conflict
|
||||
// Check Gemini first since it uses gemini- prefix
|
||||
if (isGeminiModel(model)) {
|
||||
return 'gemini';
|
||||
}
|
||||
// Check OpenCode next since it uses provider-prefixed formats that could conflict
|
||||
if (isOpencodeModel(model)) {
|
||||
return 'opencode';
|
||||
}
|
||||
@@ -199,6 +227,7 @@ export function stripProviderPrefix(model: string): string {
|
||||
* addProviderPrefix('cursor-composer-1', 'cursor') // 'cursor-composer-1' (no change)
|
||||
* addProviderPrefix('gpt-5.2', 'codex') // 'codex-gpt-5.2'
|
||||
* addProviderPrefix('sonnet', 'claude') // 'sonnet' (Claude doesn't use prefix)
|
||||
* addProviderPrefix('2.5-flash', 'gemini') // 'gemini-2.5-flash'
|
||||
*/
|
||||
export function addProviderPrefix(model: string, provider: ModelProvider): string {
|
||||
if (!model || typeof model !== 'string') return model;
|
||||
@@ -215,6 +244,10 @@ export function addProviderPrefix(model: string, provider: ModelProvider): strin
|
||||
if (!model.startsWith(PROVIDER_PREFIXES.opencode)) {
|
||||
return `${PROVIDER_PREFIXES.opencode}${model}`;
|
||||
}
|
||||
} else if (provider === 'gemini') {
|
||||
if (!model.startsWith(PROVIDER_PREFIXES.gemini)) {
|
||||
return `${PROVIDER_PREFIXES.gemini}${model}`;
|
||||
}
|
||||
}
|
||||
// Claude models don't use prefixes
|
||||
return model;
|
||||
@@ -250,6 +283,7 @@ export function normalizeModelString(model: string | undefined | null): string {
|
||||
model.startsWith(PROVIDER_PREFIXES.cursor) ||
|
||||
model.startsWith(PROVIDER_PREFIXES.codex) ||
|
||||
model.startsWith(PROVIDER_PREFIXES.opencode) ||
|
||||
model.startsWith(PROVIDER_PREFIXES.gemini) ||
|
||||
model.startsWith('claude-')
|
||||
) {
|
||||
return model;
|
||||
|
||||
@@ -99,7 +99,7 @@ export function getThinkingTokenBudget(level: ThinkingLevel | undefined): number
|
||||
}
|
||||
|
||||
/** ModelProvider - AI model provider for credentials and API key management */
|
||||
export type ModelProvider = 'claude' | 'cursor' | 'codex' | 'opencode';
|
||||
export type ModelProvider = 'claude' | 'cursor' | 'codex' | 'opencode' | 'gemini';
|
||||
|
||||
// ============================================================================
|
||||
// Claude-Compatible Providers - Configuration for Claude-compatible API endpoints
|
||||
|
||||
Reference in New Issue
Block a user