Skip to content

Commit 9112452

Browse files
committed
feat(provider): add Gemini provider support via Gemini CLI
- Introduce new Gemini provider for generating commit messages and PR reviews. - Update configuration wizard to support Gemini model selection and options. - Add supported Gemini models (Flash, Pro, Flash Lite, etc.) to the models package. - Update README.md with configuration instructions for the new provider.
1 parent ee2fa12 commit 9112452

8 files changed

Lines changed: 341 additions & 18 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ providers:
9393
anthropic:
9494
model: "claude-haiku-4-5" # Uses Claude Code CLI - no API key needed
9595
num_suggestions: 10 # Number of commit suggestions to generate
96+
gemini:
97+
model: "flash" # Uses Gemini CLI - no API key needed
98+
num_suggestions: 10 # Number of commit suggestions to generate
9699
```
97100
98101
> [!NOTE]

cmd/commit.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ var commitCmd = &cobra.Command{
8080
numSuggestions = 10
8181
}
8282
aiProvider = provider.NewAnthropicProvider(model, numSuggestions)
83+
case "gemini":
84+
numSuggestions := config.GetNumSuggestions()
85+
if numSuggestions <= 0 {
86+
numSuggestions = 10
87+
}
88+
aiProvider = provider.NewGeminiProvider(model, numSuggestions)
8389
default:
8490
// Default to copilot if provider is not set or unknown
8591
aiProvider = provider.NewCopilotProvider(apiKey, endpoint)

cmd/config.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func runInteractiveConfig() {
8181

8282
providerPrompt := &survey.Select{
8383
Message: "Choose a provider:",
84-
Options: []string{"openai", "copilot", "anthropic"},
84+
Options: []string{"openai", "copilot", "anthropic", "gemini"},
8585
Default: currentProvider,
8686
}
8787
var selectedProvider string
@@ -152,8 +152,8 @@ func runInteractiveConfig() {
152152
fmt.Printf("Language set to: %s\n", langValue)
153153
}
154154

155-
// API key configuration - skip for copilot and anthropic
156-
if selectedProvider != "copilot" && selectedProvider != "anthropic" {
155+
// API key configuration - skip for copilot, anthropic and gemini
156+
if selectedProvider != "copilot" && selectedProvider != "anthropic" && selectedProvider != "gemini" {
157157
apiKeyPrompt := &survey.Input{
158158
Message: fmt.Sprintf("Enter API Key for %s:", selectedProvider),
159159
}
@@ -173,12 +173,15 @@ func runInteractiveConfig() {
173173
}
174174
} else if selectedProvider == "anthropic" {
175175
fmt.Println("Anthropic provider uses Claude Code CLI - no API key needed.")
176+
} else if selectedProvider == "gemini" {
177+
fmt.Println("Gemini provider uses Gemini CLI - no API key needed.")
176178
}
177179

178180
availableModels := map[string][]string{
179181
"openai": {},
180182
"copilot": {},
181183
"anthropic": {},
184+
"gemini": {},
182185
}
183186

184187
modelDisplayToID := map[string]string{}
@@ -213,6 +216,12 @@ func runInteractiveConfig() {
213216
availableModels["anthropic"] = append(availableModels["anthropic"], display)
214217
modelDisplayToID[display] = m.APIModel
215218
}
219+
case "gemini":
220+
for _, m := range models.GeminiModels {
221+
display := fmt.Sprintf("%s (%s)", m.Name, m.APIModel)
222+
availableModels["gemini"] = append(availableModels["gemini"], display)
223+
modelDisplayToID[display] = m.APIModel
224+
}
216225
}
217226

218227
modelPrompt := &survey.Select{
@@ -223,7 +232,7 @@ func runInteractiveConfig() {
223232
// Try to set the default to the current model if possible
224233
isValidDefault := false
225234
currentDisplay := ""
226-
if selectedProvider == "openai" || selectedProvider == "anthropic" || selectedProvider == "copilot" {
235+
if selectedProvider == "openai" || selectedProvider == "anthropic" || selectedProvider == "copilot" || selectedProvider == "gemini" {
227236
for display, id := range modelDisplayToID {
228237
if id == currentModel || display == currentModel {
229238
isValidDefault = true
@@ -252,7 +261,7 @@ func runInteractiveConfig() {
252261
}
253262

254263
selectedModel := selectedDisplay
255-
if selectedProvider == "openai" || selectedProvider == "anthropic" || selectedProvider == "copilot" {
264+
if selectedProvider == "openai" || selectedProvider == "anthropic" || selectedProvider == "copilot" || selectedProvider == "gemini" {
256265
selectedModel = modelDisplayToID[selectedDisplay]
257266
}
258267

@@ -265,8 +274,8 @@ func runInteractiveConfig() {
265274
fmt.Printf("Model set to: %s\n", selectedModel)
266275
}
267276

268-
// Number of suggestions configuration for anthropic
269-
if selectedProvider == "anthropic" {
277+
// Number of suggestions configuration for anthropic and gemini
278+
if selectedProvider == "anthropic" || selectedProvider == "gemini" {
270279
numSuggestionsPrompt := &survey.Input{
271280
Message: "Number of commit message suggestions (default: 10):",
272281
Default: "10",
@@ -290,8 +299,8 @@ func runInteractiveConfig() {
290299
// Get current endpoint
291300
currentEndpoint, _ := config.GetEndpoint()
292301

293-
// Endpoint configuration prompt - skip for anthropic since it uses CLI
294-
if selectedProvider != "anthropic" {
302+
// Endpoint configuration prompt - skip for anthropic and gemini since they use CLI
303+
if selectedProvider != "anthropic" && selectedProvider != "gemini" {
295304
endpointPrompt := &survey.Input{
296305
Message: "Enter custom endpoint URL (leave empty for default):",
297306
Default: currentEndpoint,

cmd/pr.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ var prCmd = &cobra.Command{
8787
// Get num_suggestions from config
8888
numSuggestions := config.GetNumSuggestions()
8989
aiProvider = provider.NewAnthropicProvider(model, numSuggestions)
90+
case "gemini":
91+
numSuggestions := config.GetNumSuggestions()
92+
aiProvider = provider.NewGeminiProvider(model, numSuggestions)
9093
default:
9194
// Default to copilot if provider is not set or unknown
9295
aiProvider = provider.NewCopilotProvider(apiKey, endpoint)

internal/config/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ func InitConfig() {
4545

4646
viper.SetDefault("providers.anthropic.model", "claude-haiku-4-5")
4747
viper.SetDefault("providers.anthropic.num_suggestions", 10)
48+
viper.SetDefault("providers.gemini.model", "flash")
49+
viper.SetDefault("providers.gemini.num_suggestions", 10)
4850
viper.AutomaticEnv()
4951

5052
if err := viper.ReadInConfig(); err != nil {
@@ -149,6 +151,8 @@ func GetEndpoint() (string, error) {
149151
return "https://api.githubcopilot.com", nil
150152
case "anthropic":
151153
return "", nil // Anthropic uses CLI, no endpoint needed
154+
case "gemini":
155+
return "", nil // Gemini uses CLI, no endpoint needed
152156
default:
153157
return "", fmt.Errorf("no default endpoint available for provider '%s'", cfg.ActiveProvider)
154158
}

internal/config/config_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,8 @@ func TestGetEndpoint_CustomEndpoint(t *testing.T) {
162162
cfg.Providers = make(map[string]ProviderConfig)
163163
}
164164
cfg.Providers[testProvider] = ProviderConfig{
165-
APIKey: "test-key",
166-
Model: "test-model",
165+
APIKey: "test-key",
166+
Model: "test-model",
167167
EndpointURL: customEndpoint,
168168
}
169169

@@ -221,12 +221,12 @@ func TestSetEndpoint_Validation(t *testing.T) {
221221
endpoint string
222222
valid bool
223223
}{
224-
{"", true}, // Empty should be valid (default)
225-
{"https://api.openai.com/v1", true}, // Valid HTTPS URL
226-
{"http://localhost:11434", true}, // Valid HTTP URL
227-
{"ftp://invalid.com", false}, // Invalid protocol
228-
{"not-a-url", false}, // Invalid format
229-
{"https://", false}, // Missing host
224+
{"", true}, // Empty should be valid (default)
225+
{"https://api.openai.com/v1", true}, // Valid HTTPS URL
226+
{"http://localhost:11434", true}, // Valid HTTP URL
227+
{"ftp://invalid.com", false}, // Invalid protocol
228+
{"not-a-url", false}, // Invalid format
229+
{"https://", false}, // Missing host
230230
}
231231

232232
for _, tc := range testCases {
@@ -237,4 +237,4 @@ func TestSetEndpoint_Validation(t *testing.T) {
237237
t.Errorf("Expected invalid endpoint %s to fail, but it passed", tc.endpoint)
238238
}
239239
}
240-
}
240+
}

0 commit comments

Comments
 (0)