From de8b36d65d46e83c4e59e21dc5aaf747ad032618 Mon Sep 17 00:00:00 2001
From: rcourtman
Date: Fri, 12 Dec 2025 14:35:28 +0000
Subject: [PATCH] feat(settings): Add separate Auto-Fix Model setting for
remediation
Add configurable model specifically for automatic remediation actions:
Backend (internal/config/ai.go):
- Add AutoFixModel field to AIConfig
- Add GetAutoFixModel() getter with fallback chain:
AutoFixModel -> PatrolModel -> Model
Frontend (AISettings.tsx, types/ai.ts):
- Add auto_fix_model to AISettings types
- Add Auto-Fix Model dropdown (only shows when patrol_auto_fix enabled)
- Falls back to patrol model if not set
API (ai_handlers.go):
- Add auto_fix_model to response and update request
- Handle saving/loading the new field
Rationale:
- Auto-fix takes real actions, may warrant a more capable model
- Patrol observation can use cheaper models for cost savings
- Gives users granular control over model costs vs reliability
- Model hierarchy: Chat > AutoFix > Patrol > Default
---
.../src/components/Settings/AISettings.tsx | 49 +++++++++++++++++++
frontend-modern/src/types/ai.ts | 2 +
internal/api/ai_handlers.go | 16 ++++++
internal/config/ai.go | 11 +++++
4 files changed, 78 insertions(+)
diff --git a/frontend-modern/src/components/Settings/AISettings.tsx b/frontend-modern/src/components/Settings/AISettings.tsx
index ebe7bdb..698c9f0 100644
--- a/frontend-modern/src/components/Settings/AISettings.tsx
+++ b/frontend-modern/src/components/Settings/AISettings.tsx
@@ -79,6 +79,7 @@ export const AISettings: Component = () => {
model: '',
chatModel: '', // Empty means use default model
patrolModel: '', // Empty means use default model
+ autoFixModel: '', // Empty means use patrol model
baseUrl: '', // Legacy - kept for compatibility
clearApiKey: false,
autonomousMode: false,
@@ -105,6 +106,7 @@ export const AISettings: Component = () => {
model: '', // Will be set when provider is configured
chatModel: '',
patrolModel: '',
+ autoFixModel: '',
baseUrl: '',
clearApiKey: false,
autonomousMode: false,
@@ -130,6 +132,7 @@ export const AISettings: Component = () => {
model: data.model || '', // User must select a model
chatModel: data.chat_model || '',
patrolModel: data.patrol_model || '',
+ autoFixModel: data.auto_fix_model || '',
baseUrl: data.base_url || '',
clearApiKey: false,
autonomousMode: data.autonomous_mode || false,
@@ -280,6 +283,10 @@ export const AISettings: Component = () => {
payload.patrol_model = form.patrolModel;
}
+ if (form.autoFixModel !== (settings()?.auto_fix_model || '')) {
+ payload.auto_fix_model = form.autoFixModel;
+ }
+
// Include multi-provider credentials if set (non-empty)
if (form.anthropicApiKey.trim()) {
payload.anthropic_api_key = form.anthropicApiKey.trim();
@@ -607,6 +614,48 @@ export const AISettings: Component = () => {
+ {/* Auto-Fix Model Override */}
+
+
+
+
{/* AI Provider Configuration - Configure API keys for all providers */}
diff --git a/frontend-modern/src/types/ai.ts b/frontend-modern/src/types/ai.ts
index d73cde2..22a2f63 100644
--- a/frontend-modern/src/types/ai.ts
+++ b/frontend-modern/src/types/ai.ts
@@ -17,6 +17,7 @@ export interface AISettings {
model: string;
chat_model?: string; // Model for interactive chat (empty = use default)
patrol_model?: string; // Model for background patrol (empty = use default)
+ auto_fix_model?: string; // Model for auto-fix remediation (empty = use patrol model)
base_url?: string; // DEPRECATED: legacy base URL
configured: boolean; // true if AI is ready to use
autonomous_mode: boolean; // true if AI can execute commands without approval
@@ -55,6 +56,7 @@ export interface AISettingsUpdateRequest {
// Model overrides for different use cases
chat_model?: string; // Model for interactive chat
patrol_model?: string; // Model for background patrol
+ auto_fix_model?: string; // Model for auto-fix remediation
// Patrol settings for token efficiency
patrol_schedule_preset?: string; // DEPRECATED: use patrol_interval_minutes
patrol_interval_minutes?: number; // Custom interval in minutes (0 = disabled, minimum 10)
diff --git a/internal/api/ai_handlers.go b/internal/api/ai_handlers.go
index febeae4..611856c 100644
--- a/internal/api/ai_handlers.go
+++ b/internal/api/ai_handlers.go
@@ -144,6 +144,7 @@ type AISettingsResponse struct {
Model string `json:"model"`
ChatModel string `json:"chat_model,omitempty"` // Model for interactive chat (empty = use default)
PatrolModel string `json:"patrol_model,omitempty"` // Model for patrol (empty = use default)
+ AutoFixModel string `json:"auto_fix_model,omitempty"` // Model for auto-fix (empty = use patrol model)
BaseURL string `json:"base_url,omitempty"` // DEPRECATED: legacy base URL
Configured bool `json:"configured"` // true if AI is ready to use
AutonomousMode bool `json:"autonomous_mode"` // true if AI can execute without approval
@@ -154,6 +155,7 @@ type AISettingsResponse struct {
// Patrol settings for token efficiency
PatrolSchedulePreset string `json:"patrol_schedule_preset"` // DEPRECATED: legacy preset
PatrolIntervalMinutes int `json:"patrol_interval_minutes"` // Patrol interval in minutes (0 = disabled)
+ PatrolAutoFix bool `json:"patrol_auto_fix"` // true if patrol can auto-fix issues
AlertTriggeredAnalysis bool `json:"alert_triggered_analysis"` // true if AI analyzes when alerts fire
AvailableModels []config.ModelInfo `json:"available_models"` // List of models for current provider
// Multi-provider credentials - shows which providers are configured
@@ -176,6 +178,7 @@ type AISettingsUpdateRequest struct {
Model *string `json:"model,omitempty"`
ChatModel *string `json:"chat_model,omitempty"` // Model for interactive chat
PatrolModel *string `json:"patrol_model,omitempty"` // Model for background patrol
+ AutoFixModel *string `json:"auto_fix_model,omitempty"` // Model for auto-fix remediation
BaseURL *string `json:"base_url,omitempty"` // DEPRECATED: use per-provider URLs
AutonomousMode *bool `json:"autonomous_mode,omitempty"`
CustomContext *string `json:"custom_context,omitempty"` // user-provided infrastructure context
@@ -183,6 +186,7 @@ type AISettingsUpdateRequest struct {
// Patrol settings for token efficiency
PatrolSchedulePreset *string `json:"patrol_schedule_preset,omitempty"` // DEPRECATED: use patrol_interval_minutes
PatrolIntervalMinutes *int `json:"patrol_interval_minutes,omitempty"` // Custom interval in minutes (0 = disabled, minimum 10)
+ PatrolAutoFix *bool `json:"patrol_auto_fix,omitempty"` // true if patrol can auto-fix issues
AlertTriggeredAnalysis *bool `json:"alert_triggered_analysis,omitempty"` // true if AI analyzes when alerts fire
// Multi-provider credentials
AnthropicAPIKey *string `json:"anthropic_api_key,omitempty"` // Set Anthropic API key
@@ -230,6 +234,7 @@ func (h *AISettingsHandler) HandleGetAISettings(w http.ResponseWriter, r *http.R
Model: settings.GetModel(),
ChatModel: settings.ChatModel,
PatrolModel: settings.PatrolModel,
+ AutoFixModel: settings.AutoFixModel,
BaseURL: settings.BaseURL,
Configured: settings.IsConfigured(),
AutonomousMode: settings.AutonomousMode,
@@ -239,6 +244,7 @@ func (h *AISettingsHandler) HandleGetAISettings(w http.ResponseWriter, r *http.R
// Patrol settings
PatrolSchedulePreset: settings.PatrolSchedulePreset,
PatrolIntervalMinutes: settings.PatrolIntervalMinutes,
+ PatrolAutoFix: settings.PatrolAutoFix,
AlertTriggeredAnalysis: settings.AlertTriggeredAnalysis,
AvailableModels: nil, // Now populated via /api/ai/models endpoint
// Multi-provider configuration
@@ -332,6 +338,14 @@ func (h *AISettingsHandler) HandleUpdateAISettings(w http.ResponseWriter, r *htt
settings.PatrolModel = strings.TrimSpace(*req.PatrolModel)
}
+ if req.AutoFixModel != nil {
+ settings.AutoFixModel = strings.TrimSpace(*req.AutoFixModel)
+ }
+
+ if req.PatrolAutoFix != nil {
+ settings.PatrolAutoFix = *req.PatrolAutoFix
+ }
+
if req.BaseURL != nil {
settings.BaseURL = strings.TrimSpace(*req.BaseURL)
}
@@ -474,6 +488,7 @@ func (h *AISettingsHandler) HandleUpdateAISettings(w http.ResponseWriter, r *htt
Model: settings.GetModel(),
ChatModel: settings.ChatModel,
PatrolModel: settings.PatrolModel,
+ AutoFixModel: settings.AutoFixModel,
BaseURL: settings.BaseURL,
Configured: settings.IsConfigured(),
AutonomousMode: settings.AutonomousMode,
@@ -482,6 +497,7 @@ func (h *AISettingsHandler) HandleUpdateAISettings(w http.ResponseWriter, r *htt
OAuthConnected: settings.OAuthAccessToken != "",
PatrolSchedulePreset: settings.PatrolSchedulePreset,
PatrolIntervalMinutes: settings.PatrolIntervalMinutes,
+ PatrolAutoFix: settings.PatrolAutoFix,
AlertTriggeredAnalysis: settings.AlertTriggeredAnalysis,
AvailableModels: nil, // Now populated via /api/ai/models endpoint
// Multi-provider configuration
diff --git a/internal/config/ai.go b/internal/config/ai.go
index 7ee1018..3698973 100644
--- a/internal/config/ai.go
+++ b/internal/config/ai.go
@@ -47,6 +47,7 @@ type AIConfig struct {
PatrolAnalyzeDocker bool `json:"patrol_analyze_docker,omitempty"` // Include Docker hosts in patrol
PatrolAnalyzeStorage bool `json:"patrol_analyze_storage,omitempty"` // Include storage in patrol
PatrolAutoFix bool `json:"patrol_auto_fix,omitempty"` // When true, patrol can attempt automatic remediation (default: false, observe only)
+ AutoFixModel string `json:"auto_fix_model,omitempty"` // Model for automatic remediation (defaults to PatrolModel, may want more capable model)
// Alert-triggered AI analysis - analyze specific resources when alerts fire
AlertTriggeredAnalysis bool `json:"alert_triggered_analysis,omitempty"` // Enable AI analysis when alerts fire (token-efficient)
@@ -317,6 +318,16 @@ func (c *AIConfig) GetPatrolModel() string {
return c.GetModel()
}
+// GetAutoFixModel returns the model for automatic remediation actions
+// Falls back to PatrolModel, then to the main Model if AutoFixModel is not set
+// Auto-fix may warrant a more capable model since it takes actions
+func (c *AIConfig) GetAutoFixModel() string {
+ if c.AutoFixModel != "" {
+ return c.AutoFixModel
+ }
+ return c.GetPatrolModel()
+}
+
// ClearOAuthTokens clears OAuth tokens (used when switching back to API key auth)
func (c *AIConfig) ClearOAuthTokens() {
c.OAuthAccessToken = ""