@@ -14,6 +14,7 @@ class RateLimitError < GitAuto::Errors::RateLimitError; end
1414
1515 OPENAI_API_URL = "https://api.openai.com/v1/chat/completions"
1616 CLAUDE_API_URL = "https://api.anthropic.com/v1/messages"
17+ GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta/models"
1718 MAX_DIFF_SIZE = 10_000
1819 MAX_RETRIES = 3
1920 BACKOFF_BASE = 2
@@ -25,10 +26,10 @@ def reset_temperature
2526 end
2627
2728 TEMPERATURE_VARIATIONS = [
28- { openai : 0.7 , claude : 0.7 } ,
29- { openai : 0.8 , claude : 0.8 } ,
30- { openai : 0.9 , claude : 0.9 } ,
31- { openai : 1.0 , claude : 1.0 }
29+ { openai : 0.7 , claude : 0.7 , gemini : 0.7 } ,
30+ { openai : 0.8 , claude : 0.8 , gemini : 0.8 } ,
31+ { openai : 0.9 , claude : 0.9 , gemini : 0.9 } ,
32+ { openai : 1.0 , claude : 1.0 , gemini : 1.0 }
3233 ] . freeze
3334
3435 def initialize ( settings )
@@ -169,6 +170,8 @@ def generate_commit_message(diff, style: :conventional, scope: nil)
169170 generate_openai_commit_message ( diff , style , scope )
170171 when "claude"
171172 generate_claude_commit_message ( diff , style , scope )
173+ when "gemini"
174+ generate_gemini_commit_message ( diff , style , scope )
172175 else
173176 raise GitAuto ::Errors ::InvalidProviderError , "Invalid AI provider specified"
174177 end
@@ -192,8 +195,7 @@ def add_suggestion(message)
192195 def previous_suggestions_prompt
193196 return "" if @previous_suggestions . empty?
194197
195- "\n Previous suggestions that you MUST NOT repeat:\n " +
196- @previous_suggestions . map { |s | "- #{ s } " } . join ( "\n " )
198+ "\n Previous suggestions that you MUST NOT repeat:\n #{ @previous_suggestions . map { |s | "- #{ s } " } . join ( "\n " ) } "
197199 end
198200
199201 def generate_openai_commit_message ( diff , style , scope = nil , retry_attempt = nil )
@@ -388,6 +390,110 @@ def generate_claude_commit_message(diff, style, scope = nil, retry_attempt = nil
388390 add_suggestion ( message )
389391 end
390392
393+ def generate_gemini_commit_message ( diff , style , scope = nil , retry_attempt = nil )
394+ api_key = @credential_store . get_api_key ( "gemini" )
395+ raise APIKeyError , "Gemini API key is not set. Please set it using `git_auto config`" unless api_key
396+
397+ # Only use temperature variations for retries
398+ temperature = retry_attempt ? get_temperature ( retry_attempt ) : TEMPERATURE_VARIATIONS [ 0 ] [ :gemini ]
399+ commit_types = [ "feat" , "fix" , "docs" , "style" , "refactor" , "test" , "chore" , "perf" , "ci" , "build" ,
400+ "revert" ] . join ( "|" )
401+
402+ system_message = case style . to_s
403+ when "minimal"
404+ "You are a commit message generator that MUST follow the minimal commit format: <type>: <description>\n " \
405+ "Valid types are: #{ commit_types } \n " \
406+ "Rules:\n " \
407+ "1. ALWAYS start with a type from the list above\n " \
408+ "2. NEVER include a scope\n " \
409+ "3. Keep the message under 72 characters\n " \
410+ "4. ALWAYS use lowercase - this is mandatory\n " \
411+ "5. Use present tense\n " \
412+ "6. Be descriptive but concise\n " \
413+ "7. Do not include a period at the end"
414+ when "conventional"
415+ "You are a commit message generator that MUST follow these rules EXACTLY:\n " \
416+ "1. ONLY output a single line containing the commit message\n " \
417+ "2. Use format: <type>(<scope>): <description>\n " \
418+ "3. Valid types are: #{ commit_types } \n " \
419+ "4. Keep under 72 characters\n " \
420+ "5. ALWAYS use lowercase - this is mandatory\n " \
421+ "6. Use present tense\n " \
422+ "7. Be descriptive but concise\n " \
423+ "8. No period at the end\n " \
424+ "9. NO explanations or additional text\n " \
425+ "10. NO markdown formatting"
426+ when "detailed"
427+ "You are a commit message generator that MUST follow this format EXACTLY:\n " \
428+ "<summary line>\n " \
429+ "\n " \
430+ "<detailed description>\n " \
431+ "\n " \
432+ "Rules:\n " \
433+ "1. First line is a summary under 72 characters\n " \
434+ "2. ALWAYS use lowercase - this is mandatory\n " \
435+ "3. ALWAYS include a blank line after the summary\n " \
436+ "4. ALWAYS include a detailed description explaining:\n " \
437+ " - What changes were made\n " \
438+ " - Why the changes were necessary\n " \
439+ " - Any technical details worth noting\n " \
440+ "5. Use bullet points for multiple changes\n " \
441+ "6. Use present tense\n " \
442+ "7. You can use periods in the detailed description\n " \
443+ "8. NO explanations or additional text\n " \
444+ "9. NO markdown formatting"
445+ else
446+ "You are an expert in writing clear and concise git commit messages.\n " \
447+ "Rules:\n " \
448+ "1. Keep the message under 72 characters\n " \
449+ "2. ALWAYS use lowercase - this is mandatory\n " \
450+ "3. Use present tense\n " \
451+ "4. Be descriptive but concise\n " \
452+ "5. Do not include a period at the end"
453+ end
454+
455+ user_message = if scope
456+ "Generate a conventional commit message with scope '#{ scope } ' for this diff:\n \n #{ diff } "
457+ else
458+ "Generate a #{ style } commit message for this diff:\n \n #{ diff } "
459+ end
460+
461+ model = @settings . get ( :ai_model )
462+ url = "#{ GEMINI_API_URL } /#{ model } :generateContent?key=#{ api_key } "
463+
464+ payload = {
465+ contents : [
466+ {
467+ parts : [
468+ {
469+ text : "#{ system_message } \n \n #{ user_message } "
470+ }
471+ ]
472+ }
473+ ] ,
474+ generationConfig : {
475+ temperature : temperature ,
476+ topK : 40 ,
477+ topP : 0.95 ,
478+ candidateCount : 1 ,
479+ maxOutputTokens : 1024
480+ }
481+ }
482+
483+ log_api_request ( "gemini" , payload , temperature ) if @debug_mode
484+
485+ response = HTTP . headers ( {
486+ "Content-Type" => "application/json"
487+ } ) . post ( url , json : payload )
488+
489+ log_api_response ( response . body ) if @debug_mode
490+
491+ message = handle_response ( response )
492+ message = message . downcase . strip
493+ message = message . sub ( /\. $/ , "" ) # Remove trailing period if present
494+ add_suggestion ( message )
495+ end
496+
391497 def style_description ( style , scope )
392498 case style
393499 when :conventional , "conventional"
@@ -436,6 +542,21 @@ def handle_response(response)
436542 raise Error , "No valid commit message found in response" if commit_message . nil?
437543 commit_message . strip
438544 end
545+
546+ when "gemini"
547+ content = json . dig ( "candidates" , 0 , "content" , "parts" , 0 , "text" )
548+ raise Error , "No message content in response" if content . nil? || content . empty?
549+
550+ # For detailed style, keep the full message
551+ if @settings . get ( :commit_style ) == "detailed"
552+ content . strip
553+ else
554+ # Clean up the response and extract just the commit message
555+ lines = content . strip . split ( "\n " )
556+ # Find the first line that looks like a commit message
557+ commit_line = lines . find { |line | line . match ( /^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)/ ) }
558+ commit_line || lines . first . strip
559+ end
439560 end
440561 when 401
441562 raise APIKeyError , "Invalid API key" unless ENV [ "RACK_ENV" ] == "test"
@@ -447,8 +568,27 @@ def handle_response(response)
447568
448569 "test commit message"
449570
571+ when 403
572+ # Gemini-specific error for invalid API key
573+ provider = @settings . get ( :ai_provider )
574+ if provider == "gemini"
575+ raise APIKeyError , "Invalid Gemini API key. Please check your API key at https://makersuite.google.com/app/apikey"
576+ else
577+ raise APIKeyError , "Access forbidden. Please check your API key."
578+ end
579+
450580 when 429
451- raise RateLimitError , "Rate limit exceeded"
581+ provider = @settings . get ( :ai_provider )
582+ case provider
583+ when "gemini"
584+ raise RateLimitError , "Gemini API rate limit exceeded. Please wait a moment and try again."
585+ when "openai"
586+ raise RateLimitError , "OpenAI API rate limit exceeded. Please try again later."
587+ when "claude"
588+ raise RateLimitError , "Claude API rate limit exceeded. Please try again later."
589+ else
590+ raise RateLimitError , "Rate limit exceeded"
591+ end
452592 else
453593 raise Error , "API request failed with status #{ response . code } : #{ response . body } "
454594 end
0 commit comments