// Package ai turns scan results into suggested next actions using an LLM // provider (OpenAI, Ollama, or Anthropic). package ai import ( "context" "fmt" "os" "gitea.oblak.solutions/dimitar/gitFlow/internal/config" "gitea.oblak.solutions/dimitar/gitFlow/pkg/status" ) // Suggestion is one recommended next action for a repository. The JSON tags // match the schema the providers are instructed to emit. type Suggestion struct { RepoPath string `json:"repo_path"` // absolute path of the repository Action string `json:"action"` // commit, push, pull, stash, create_pr, cleanup, inspect, ... Message string `json:"message"` // human-readable explanation Command string `json:"command"` // suggested shell command, if any Priority int `json:"priority"` // 0 = low, 1 = medium, 2 = high } // Provider turns a scan result into suggestions. type Provider interface { // Name identifies the provider for logging and errors. Name() string // Suggest asks the provider for next actions on the given result. Suggest(ctx context.Context, result status.ScanResult) ([]Suggestion, error) } // NewProvider builds the provider named in the AI configuration. Cloud // providers require their API key to be present in the configured // environment variable; Ollama is local and needs no key. func NewProvider(cfg config.AIConfig) (Provider, error) { switch cfg.Provider { case "openai": key := os.Getenv(cfg.APIKeyEnv) if key == "" { return nil, fmt.Errorf("ai: %s is not set; export it or set ai.api_key_env", cfg.APIKeyEnv) } return NewOpenAI(cfg, key), nil case "anthropic": key := os.Getenv(cfg.APIKeyEnv) if key == "" { return nil, fmt.Errorf("ai: %s is not set; export it or set ai.api_key_env", cfg.APIKeyEnv) } return NewAnthropic(cfg, key), nil case "ollama": return NewOllama(cfg), nil default: return nil, fmt.Errorf("ai: unknown provider %q (want openai, ollama, or anthropic)", cfg.Provider) } }