diff options
Diffstat (limited to 'internal/appconfig/config_merge.go')
| -rw-r--r-- | internal/appconfig/config_merge.go | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/internal/appconfig/config_merge.go b/internal/appconfig/config_merge.go new file mode 100644 index 0000000..d474fb7 --- /dev/null +++ b/internal/appconfig/config_merge.go @@ -0,0 +1,242 @@ +package appconfig + +import ( + "slices" + "strings" +) + +func (a *App) mergeWith(other *App) { + a.mergeBasics(other) + a.mergeProviderFields(other) + a.mergeSurfaceModels(other) + a.mergePrompts(other) + a.mergeTmuxEdit(other) +} + +// mergeBasics merges general (non-provider) fields. +func (a *App) mergeBasics(other *App) { + mergeBasicCore(a, other) + mergeBasicCompletion(a, other) + mergeBasicInteraction(a, other) + mergeBasicRuntime(a, other) +} + +func mergeBasicCore(dst, src *App) { + if src.MaxTokens > 0 { + dst.MaxTokens = src.MaxTokens + } + if s := strings.TrimSpace(src.ContextMode); s != "" { + dst.ContextMode = s + } + if src.ContextWindowLines > 0 { + dst.ContextWindowLines = src.ContextWindowLines + } + if src.MaxContextTokens > 0 { + dst.MaxContextTokens = src.MaxContextTokens + } + if src.LogPreviewLimit >= 0 { + dst.LogPreviewLimit = src.LogPreviewLimit + } + if src.RequestTimeout > 0 { + dst.RequestTimeout = src.RequestTimeout + } + if src.CodingTemperature != nil { // allow explicit 0.0 + dst.CodingTemperature = src.CodingTemperature + } +} + +func mergeBasicCompletion(dst, src *App) { + if src.ManualInvokeMinPrefix >= 0 { + dst.ManualInvokeMinPrefix = src.ManualInvokeMinPrefix + } + if src.CompletionDebounceMs > 0 { + dst.CompletionDebounceMs = src.CompletionDebounceMs + } + if src.CompletionThrottleMs > 0 { + dst.CompletionThrottleMs = src.CompletionThrottleMs + } + if src.CompletionWaitAll != nil { + dst.CompletionWaitAll = src.CompletionWaitAll + } +} + +func mergeBasicInteraction(dst, src *App) { + if len(src.TriggerCharacters) > 0 { + dst.TriggerCharacters = slices.Clone(src.TriggerCharacters) + } + if s := strings.TrimSpace(src.InlineOpen); s != "" { + dst.InlineOpen = s + } + if s := strings.TrimSpace(src.InlineClose); s != "" { + dst.InlineClose = s + } + if s := strings.TrimSpace(src.ChatSuffix); s != "" { + dst.ChatSuffix = s + } + if len(src.ChatPrefixes) > 0 { + dst.ChatPrefixes = slices.Clone(src.ChatPrefixes) + } + if s := strings.TrimSpace(src.Provider); s != "" { + dst.Provider = s + } +} + +func mergeBasicRuntime(dst, src *App) { + if src.IgnoreGitignore != nil { + dst.IgnoreGitignore = src.IgnoreGitignore + } + if len(src.IgnoreExtraPatterns) > 0 { + dst.IgnoreExtraPatterns = slices.Clone(src.IgnoreExtraPatterns) + } + if src.IgnoreLSPNotify != nil { + dst.IgnoreLSPNotify = src.IgnoreLSPNotify + } + if s := strings.TrimSpace(src.MCPPromptsDir); s != "" { + dst.MCPPromptsDir = s + } + if src.MCPSlashCommandSync { + dst.MCPSlashCommandSync = src.MCPSlashCommandSync + } + if s := strings.TrimSpace(src.MCPSlashCommandDir); s != "" { + dst.MCPSlashCommandDir = s + } +} + +// mergeProviderFields merges per-provider configuration. +func (a *App) mergeProviderFields(other *App) { + if s := strings.TrimSpace(other.OpenAIBaseURL); s != "" { + a.OpenAIBaseURL = s + } + if s := strings.TrimSpace(other.OpenAIModel); s != "" { + a.OpenAIModel = s + } + if other.OpenAITemperature != nil { // allow explicit 0.0 + a.OpenAITemperature = other.OpenAITemperature + } + if s := strings.TrimSpace(other.OpenRouterBaseURL); s != "" { + a.OpenRouterBaseURL = s + } + if s := strings.TrimSpace(other.OpenRouterModel); s != "" { + a.OpenRouterModel = s + } + if other.OpenRouterTemperature != nil { // allow explicit 0.0 + a.OpenRouterTemperature = other.OpenRouterTemperature + } + if s := strings.TrimSpace(other.OllamaBaseURL); s != "" { + a.OllamaBaseURL = s + } + if s := strings.TrimSpace(other.OllamaModel); s != "" { + a.OllamaModel = s + } + if other.OllamaTemperature != nil { // allow explicit 0.0 + a.OllamaTemperature = other.OllamaTemperature + } + if s := strings.TrimSpace(other.AnthropicBaseURL); s != "" { + a.AnthropicBaseURL = s + } + if s := strings.TrimSpace(other.AnthropicModel); s != "" { + a.AnthropicModel = s + } + if other.AnthropicTemperature != nil { // allow explicit 0.0 + a.AnthropicTemperature = other.AnthropicTemperature + } +} + +// mergeSurfaceModels copies per-surface model and temperature overrides. +func (a *App) mergeSurfaceModels(other *App) { + if len(other.CompletionConfigs) > 0 { + a.CompletionConfigs = cloneSurfaceConfigs(other.CompletionConfigs) + } + if len(other.CodeActionConfigs) > 0 { + a.CodeActionConfigs = cloneSurfaceConfigs(other.CodeActionConfigs) + } + if len(other.ChatConfigs) > 0 { + a.ChatConfigs = cloneSurfaceConfigs(other.ChatConfigs) + } + if len(other.CLIConfigs) > 0 { + a.CLIConfigs = cloneSurfaceConfigs(other.CLIConfigs) + } +} + +func cloneSurfaceConfigs(src []SurfaceConfig) []SurfaceConfig { + if len(src) == 0 { + return nil + } + out := make([]SurfaceConfig, len(src)) + copy(out, src) + return out +} + +// mergePrompts copies non-empty prompt templates from other. +func (a *App) mergePrompts(other *App) { + mergeCompletionPrompts(a, other) + mergeProviderNativePrompt(a, other) + mergeChatPrompt(a, other) + mergeCodeActionPrompts(a, other) + mergeCLIPrompts(a, other) + mergeCustomActionPrompts(a, other) +} + +func mergeCompletionPrompts(dst, src *App) { + mergeStringField(&dst.PromptCompletionSystemGeneral, src.PromptCompletionSystemGeneral) + mergeStringField(&dst.PromptCompletionSystemParams, src.PromptCompletionSystemParams) + mergeStringField(&dst.PromptCompletionSystemInline, src.PromptCompletionSystemInline) + mergeStringField(&dst.PromptCompletionUserGeneral, src.PromptCompletionUserGeneral) + mergeStringField(&dst.PromptCompletionUserParams, src.PromptCompletionUserParams) + mergeStringField(&dst.PromptCompletionExtraHeader, src.PromptCompletionExtraHeader) +} + +func mergeProviderNativePrompt(dst, src *App) { + mergeStringField(&dst.PromptNativeCompletion, src.PromptNativeCompletion) +} + +func mergeChatPrompt(dst, src *App) { + mergeStringField(&dst.PromptChatSystem, src.PromptChatSystem) +} + +func mergeCodeActionPrompts(dst, src *App) { + mergeStringField(&dst.PromptCodeActionRewriteSystem, src.PromptCodeActionRewriteSystem) + mergeStringField(&dst.PromptCodeActionDiagnosticsSystem, src.PromptCodeActionDiagnosticsSystem) + mergeStringField(&dst.PromptCodeActionDocumentSystem, src.PromptCodeActionDocumentSystem) + mergeStringField(&dst.PromptCodeActionRewriteUser, src.PromptCodeActionRewriteUser) + mergeStringField(&dst.PromptCodeActionDiagnosticsUser, src.PromptCodeActionDiagnosticsUser) + mergeStringField(&dst.PromptCodeActionDocumentUser, src.PromptCodeActionDocumentUser) + mergeStringField(&dst.PromptCodeActionGoTestSystem, src.PromptCodeActionGoTestSystem) + mergeStringField(&dst.PromptCodeActionGoTestUser, src.PromptCodeActionGoTestUser) + mergeStringField(&dst.PromptCodeActionSimplifySystem, src.PromptCodeActionSimplifySystem) + mergeStringField(&dst.PromptCodeActionSimplifyUser, src.PromptCodeActionSimplifyUser) +} + +func mergeCLIPrompts(dst, src *App) { + mergeStringField(&dst.PromptCLIDefaultSystem, src.PromptCLIDefaultSystem) + mergeStringField(&dst.PromptCLIExplainSystem, src.PromptCLIExplainSystem) +} + +func mergeCustomActionPrompts(dst, src *App) { + if len(src.CustomActions) > 0 { + dst.CustomActions = append([]CustomAction{}, src.CustomActions...) + } + mergeStringField(&dst.TmuxCustomMenuHotkey, src.TmuxCustomMenuHotkey) +} + +func mergeStringField(dst *string, src string) { + if strings.TrimSpace(src) != "" { + *dst = src + } +} + +// mergeTmuxEdit copies non-empty tmux edit settings from other. +func (a *App) mergeTmuxEdit(other *App) { + if s := strings.TrimSpace(other.TmuxEditPopupWidth); s != "" { + a.TmuxEditPopupWidth = s + } + if s := strings.TrimSpace(other.TmuxEditPopupHeight); s != "" { + a.TmuxEditPopupHeight = s + } + if s := strings.TrimSpace(other.TmuxEditDefaultAgent); s != "" { + a.TmuxEditDefaultAgent = s + } + if len(other.TmuxEditAgents) > 0 { + a.TmuxEditAgents = append([]TmuxEditAgentCfg{}, other.TmuxEditAgents...) + } +} |
