Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions internal/ai/gemini/issue_content_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ func (s *GeminiIssueContentGenerator) GenerateIssueContent(ctx context.Context,
log.Debug("calling gemini API for issue content",
"prompt_length", len(prompt))

if request.SkipConfirmation {
s.wrapper.SetSkipConfirmation(true)
defer s.wrapper.SetSkipConfirmation(false)
}

var usage *models.TokenUsage
var responseText string

Expand Down
67 changes: 67 additions & 0 deletions internal/ai/gemini/issue_content_generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/thomas-vilte/matecommit/internal/ai"
"github.com/thomas-vilte/matecommit/internal/config"
"github.com/thomas-vilte/matecommit/internal/models"
"google.golang.org/genai"
Expand Down Expand Up @@ -443,3 +444,69 @@ func TestGenerateIssueContent_HappyPath(t *testing.T) {
assert.Contains(t, result.Labels, "fix")
})
}

// TestGenerateIssueContent_SkipConfirmation verifies that a request with
// SkipConfirmation bypasses the cost confirmation prompt (used for internal
// orchestration calls like template auto-selection), while a normal request
// still goes through it — so the user isn't asked twice for one logical
// "generate an issue" action.
func TestGenerateIssueContent_SkipConfirmation(t *testing.T) {
tmpHome, err := os.MkdirTemp("", "matecommit-test-issue-skip-*")
assert.NoError(t, err)
defer func() {
if err := os.RemoveAll(tmpHome); err != nil {
return
}
}()
oldHome := os.Getenv("HOME")
_ = os.Setenv("HOME", tmpHome)
defer func() {
if err := os.Setenv("HOME", oldHome); err != nil {
return
}
}()

ctx := context.Background()
cfg := &config.Config{
AIProviders: map[string]config.AIProviderConfig{"gemini": {APIKey: "test"}},
AIConfig: config.AIConfig{Models: map[config.AI]config.Model{config.AIGemini: "gemini-pro"}},
}

confirmationCalls := 0
onConfirmation := func(ai.ConfirmationResult) (string, bool) {
confirmationCalls++
return "current", true
}

gen, err := NewGeminiIssueContentGenerator(ctx, cfg, onConfirmation)
assert.NoError(t, err)

expectedJSON := `{"title": "Issue Title", "description": "Issue Description", "labels": ["fix"]}`
gen.generateFn = func(ctx context.Context, mName string, p string) (interface{}, *models.TokenUsage, error) {
return &genai.GenerateContentResponse{
Candidates: []*genai.Candidate{
{Content: &genai.Content{Parts: []*genai.Part{{Text: expectedJSON}}}},
},
}, &models.TokenUsage{TotalTokens: 30}, nil
}

t.Run("normal request triggers confirmation", func(t *testing.T) {
_, err := gen.GenerateIssueContent(ctx, models.IssueGenerationRequest{Description: "unique-prompt-1"})
assert.NoError(t, err)
assert.Equal(t, 1, confirmationCalls, "confirmation should have been requested")
})

t.Run("SkipConfirmation bypasses the prompt", func(t *testing.T) {
confirmationCalls = 0
_, err := gen.GenerateIssueContent(ctx, models.IssueGenerationRequest{Description: "unique-prompt-2", SkipConfirmation: true})
assert.NoError(t, err)
assert.Equal(t, 0, confirmationCalls, "confirmation should have been skipped")
})

t.Run("wrapper returns to normal confirmation behavior afterwards", func(t *testing.T) {
confirmationCalls = 0
_, err := gen.GenerateIssueContent(ctx, models.IssueGenerationRequest{Description: "unique-prompt-3"})
assert.NoError(t, err)
assert.Equal(t, 1, confirmationCalls, "confirmation should be re-enabled after a SkipConfirmation call")
})
}
6 changes: 6 additions & 0 deletions internal/models/issue_generation.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ type IssueGenerationRequest struct {

// AvailableLabels is the list of labels available in the repository (optional)
AvailableLabels []string

// SkipConfirmation bypasses the cost confirmation prompt for this call.
// Used for small internal orchestration calls (e.g. template
// auto-selection) that shouldn't interrupt the user twice for what
// they experience as a single logical action.
SkipConfirmation bool
}

// IssueGenerationResult contains the result of an issue's content generation.
Expand Down
3 changes: 3 additions & 0 deletions internal/services/issue_generator_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,9 @@ If no template fits perfectly, choose "Custom Issue" or the most generic one.`,
request := models.IssueGenerationRequest{
Description: prompt,
Language: "en",
// This is a small internal call to pick a template, not the user's
// actual issue content — don't make them confirm cost for it too.
SkipConfirmation: true,
}

result, err := s.ai.GenerateIssueContent(ctx, request)
Expand Down
Loading