Pulse/internal/ai/providers/factory.go
rcourtman 27f1a11acb feat: add AI Intelligence system with investigation and forecasting
Major new AI capabilities for infrastructure monitoring:

Investigation System:
- Autonomous finding investigation with configurable autonomy levels
- Investigation orchestrator with rate limiting and guardrails
- Safety checks for read-only mode enforcement
- Chat-based investigation with approval workflows

Forecasting & Remediation:
- Trend forecasting for resource capacity planning
- Remediation engine for generating fix proposals
- Circuit breaker for AI operation protection

Unified Findings:
- Unified store bridging alerts and AI findings
- Correlation and root cause analysis
- Incident coordinator with metrics recording

New Frontend:
- AI Intelligence page with patrol controls
- Investigation drawer for finding details
- Unified findings panel with actions

Supporting Infrastructure:
- Learning store for user preference tracking
- Proxmox event ingestion and correlation
- Enhanced patrol with investigation triggers
2026-01-24 22:41:43 +00:00

141 lines
4.5 KiB
Go

package providers
import (
"fmt"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
)
// NewFromConfig creates a Provider based on the AIConfig settings
// DEPRECATED: Use NewForModel or NewForProvider for multi-provider support
func NewFromConfig(cfg *config.AIConfig) (Provider, error) {
if cfg == nil {
return nil, fmt.Errorf("Pulse Assistant config is nil")
}
if !cfg.Enabled {
return nil, fmt.Errorf("Pulse Assistant is not enabled")
}
// Try multi-provider format first (uses per-provider API keys)
provider, model := config.ParseModelString(cfg.Model)
if providerClient, err := NewForProvider(cfg, provider, model); err == nil {
return providerClient, nil
}
// Get the configured timeout
timeout := cfg.GetRequestTimeout()
// Fall back to legacy single-provider format
switch cfg.Provider {
case config.AIProviderAnthropic:
// If we have an API key (from direct entry or OAuth-created), use regular client
if cfg.APIKey != "" {
return NewAnthropicClient(cfg.APIKey, cfg.GetModel(), timeout), nil
}
// Pro/Max users without org:create_api_key will use OAuth tokens directly
if cfg.IsUsingOAuth() && cfg.OAuthAccessToken != "" {
client := NewAnthropicOAuthClient(
cfg.OAuthAccessToken,
cfg.OAuthRefreshToken,
cfg.OAuthExpiresAt,
cfg.GetModel(),
timeout,
)
return client, nil
}
return nil, fmt.Errorf("Anthropic API key is required (or use OAuth login for Pro/Max subscription)")
case config.AIProviderOpenAI:
if cfg.APIKey == "" {
return nil, fmt.Errorf("OpenAI API key is required")
}
return NewOpenAIClient(cfg.APIKey, cfg.GetModel(), cfg.GetBaseURL(), timeout), nil
case config.AIProviderOllama:
return NewOllamaClient(cfg.GetModel(), cfg.GetBaseURL(), timeout), nil
case config.AIProviderDeepSeek:
if cfg.APIKey == "" {
return nil, fmt.Errorf("DeepSeek API key is required")
}
// DeepSeek uses OpenAI-compatible API
return NewOpenAIClient(cfg.APIKey, cfg.GetModel(), cfg.GetBaseURL(), timeout), nil
case config.AIProviderGemini:
if cfg.APIKey == "" {
return nil, fmt.Errorf("Gemini API key is required")
}
return NewGeminiClient(cfg.APIKey, cfg.GetModel(), cfg.GetBaseURL(), timeout), nil
default:
return nil, fmt.Errorf("unknown provider: %s", cfg.Provider)
}
}
// NewForProvider creates a Provider for a specific provider using multi-provider credentials
func NewForProvider(cfg *config.AIConfig, provider, model string) (Provider, error) {
if cfg == nil {
return nil, fmt.Errorf("Pulse Assistant config is nil")
}
// Get the configured timeout
timeout := cfg.GetRequestTimeout()
switch provider {
case config.AIProviderAnthropic:
// Check for OAuth first
if cfg.IsUsingOAuth() && cfg.OAuthAccessToken != "" {
return NewAnthropicOAuthClient(
cfg.OAuthAccessToken,
cfg.OAuthRefreshToken,
cfg.OAuthExpiresAt,
model,
timeout,
), nil
}
// Then check for per-provider API key
apiKey := cfg.GetAPIKeyForProvider(config.AIProviderAnthropic)
if apiKey == "" {
return nil, fmt.Errorf("Anthropic API key not configured")
}
return NewAnthropicClient(apiKey, model, timeout), nil
case config.AIProviderOpenAI:
apiKey := cfg.GetAPIKeyForProvider(config.AIProviderOpenAI)
if apiKey == "" {
return nil, fmt.Errorf("OpenAI API key not configured")
}
baseURL := cfg.GetBaseURLForProvider(config.AIProviderOpenAI)
return NewOpenAIClient(apiKey, model, baseURL, timeout), nil
case config.AIProviderDeepSeek:
apiKey := cfg.GetAPIKeyForProvider(config.AIProviderDeepSeek)
if apiKey == "" {
return nil, fmt.Errorf("DeepSeek API key not configured")
}
baseURL := cfg.GetBaseURLForProvider(config.AIProviderDeepSeek)
return NewOpenAIClient(apiKey, model, baseURL, timeout), nil
case config.AIProviderOllama:
baseURL := cfg.GetBaseURLForProvider(config.AIProviderOllama)
return NewOllamaClient(model, baseURL, timeout), nil
case config.AIProviderGemini:
apiKey := cfg.GetAPIKeyForProvider(config.AIProviderGemini)
if apiKey == "" {
return nil, fmt.Errorf("Gemini API key not configured")
}
baseURL := cfg.GetBaseURLForProvider(config.AIProviderGemini)
return NewGeminiClient(apiKey, model, baseURL, timeout), nil
default:
return nil, fmt.Errorf("unknown provider: %s", provider)
}
}
// NewForModel creates a Provider for a specific model, automatically detecting the provider
func NewForModel(cfg *config.AIConfig, modelString string) (Provider, error) {
provider, model := config.ParseModelString(modelString)
return NewForProvider(cfg, provider, model)
}