package ai import ( "context" "fmt" "gitea.oblak.solutions/dimitar/gitFlow/internal/config" "gitea.oblak.solutions/dimitar/gitFlow/internal/httpclient" "gitea.oblak.solutions/dimitar/gitFlow/pkg/status" ) const defaultOpenAIBaseURL = "https://api.openai.com/v1" // OpenAIProvider talks to the OpenAI Chat Completions API. type OpenAIProvider struct { model string baseURL string apiKey string http *httpclient.Client } // NewOpenAI builds an OpenAI provider. base_url and model fall back to // sensible defaults when unset in the configuration. func NewOpenAI(cfg config.AIConfig, apiKey string) *OpenAIProvider { baseURL := cfg.BaseURL if baseURL == "" { baseURL = defaultOpenAIBaseURL } model := cfg.Model if model == "" { model = "gpt-4o" } return &OpenAIProvider{model: model, baseURL: baseURL, apiKey: apiKey, http: httpclient.New()} } // Name implements Provider. func (p *OpenAIProvider) Name() string { return "openai" } // chatMessage is a single chat turn, shared with the other providers. type chatMessage struct { Role string `json:"role"` Content string `json:"content"` } type chatRequest struct { Model string `json:"model"` Messages []chatMessage `json:"messages"` ResponseFormat *responseFormat `json:"response_format,omitempty"` } type responseFormat struct { Type string `json:"type"` // "json_object" } type chatResponse struct { Choices []struct { Message chatMessage `json:"message"` } `json:"choices"` Error *struct { Message string `json:"message"` } `json:"error"` } // Suggest implements Provider. func (p *OpenAIProvider) Suggest(ctx context.Context, result status.ScanResult) ([]Suggestion, error) { req := chatRequest{ Model: p.model, Messages: []chatMessage{ {Role: "system", Content: "You are gitflow, a concise Git repository health assistant. You reply with JSON only."}, {Role: "user", Content: BuildPrompt(result)}, }, ResponseFormat: &responseFormat{Type: "json_object"}, } var resp chatResponse err := p.http.PostJSON(ctx, p.baseURL+"/chat/completions", map[string]string{"Authorization": "Bearer " + p.apiKey}, req, &resp) if err != nil { return nil, err } if resp.Error != nil { return nil, fmt.Errorf("ai: openai: %s", resp.Error.Message) } if len(resp.Choices) == 0 { return nil, fmt.Errorf("ai: openai: empty response") } return parseSuggestions(resp.Choices[0].Message.Content) }