diff options
| author | Paul Buetow <paul@buetow.org> | 2025-09-19 22:52:48 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-09-19 22:52:48 +0300 |
| commit | eb72b06fe8e62cb77af73f6dc558d384a5a5fe80 (patch) | |
| tree | efeb1165b9fbcb69a4ee675dba7bdc8c28fee3aa | |
| parent | acc400768153a7bfda1413f15579c9455b877c87 (diff) | |
fix
36 files changed, 18934 insertions, 17321 deletions
diff --git a/SCRATCHPAD.md b/SCRATCHPAD.md index d9874bc..1e5fb80 100644 --- a/SCRATCHPAD.md +++ b/SCRATCHPAD.md @@ -4,12 +4,7 @@ This document shows future items and items in progress. Already completed ones a ## Features -* [/] Keep global stats about LLM usage for the tmux pane! -* [/] No a feature, but verify my OpenAI API account so I can use GPT-5 via the API. - * [ ] Temperature must by default be 1 for GPT-5 - * [ ] Answers aren't streamed ot th eCLI anymore? - * [ ] GPT-5 is timing out on large responses? - * [ ] Any more tweaks for GPT-5 API? +* [ ] Fix that ollama fallback model as there is an extra backtick in it * [ ] Kagi FastGPT for in-editor search - Think about an in-editor chat trigger, maybe with S> for search! * [ ] Test whethe GitHub Copilot support actually works now, and if not, fix it! diff --git a/docs/coverage.html b/docs/coverage.html index 4c7532e..6828b9a 100644 --- a/docs/coverage.html +++ b/docs/coverage.html @@ -61,7 +61,7 @@ <option value="file2">codeberg.org/snonux/hexai/cmd/hexai/main.go (71.4%)</option> - <option value="file3">codeberg.org/snonux/hexai/internal/appconfig/config.go (90.6%)</option> + <option value="file3">codeberg.org/snonux/hexai/internal/appconfig/config.go (88.8%)</option> <option value="file4">codeberg.org/snonux/hexai/internal/editor/editor.go (58.3%)</option> @@ -69,9 +69,9 @@ <option value="file6">codeberg.org/snonux/hexai/internal/hexaiaction/parse.go (92.6%)</option> - <option value="file7">codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go (92.7%)</option> + <option value="file7">codeberg.org/snonux/hexai/internal/hexaiaction/prompts.go (92.0%)</option> - <option value="file8">codeberg.org/snonux/hexai/internal/hexaiaction/run.go (69.7%)</option> + <option value="file8">codeberg.org/snonux/hexai/internal/hexaiaction/run.go (71.0%)</option> <option value="file9">codeberg.org/snonux/hexai/internal/hexaiaction/tui.go (65.5%)</option> @@ -79,7 +79,7 @@ <option value="file11">codeberg.org/snonux/hexai/internal/hexaiaction/tui_delegate.go (100.0%)</option> - <option value="file12">codeberg.org/snonux/hexai/internal/hexaicli/run.go (89.7%)</option> + <option value="file12">codeberg.org/snonux/hexai/internal/hexaicli/run.go (90.0%)</option> <option value="file13">codeberg.org/snonux/hexai/internal/hexailsp/run.go (90.2%)</option> @@ -87,7 +87,7 @@ <option value="file15">codeberg.org/snonux/hexai/internal/llm/ollama.go (89.8%)</option> - <option value="file16">codeberg.org/snonux/hexai/internal/llm/openai.go (85.5%)</option> + <option value="file16">codeberg.org/snonux/hexai/internal/llm/openai.go (87.1%)</option> <option value="file17">codeberg.org/snonux/hexai/internal/llm/provider.go (100.0%)</option> @@ -107,7 +107,7 @@ <option value="file25">codeberg.org/snonux/hexai/internal/lsp/handlers_codeaction.go (82.3%)</option> - <option value="file26">codeberg.org/snonux/hexai/internal/lsp/handlers_completion.go (88.0%)</option> + <option value="file26">codeberg.org/snonux/hexai/internal/lsp/handlers_completion.go (87.2%)</option> <option value="file27">codeberg.org/snonux/hexai/internal/lsp/handlers_document.go (90.1%)</option> @@ -115,23 +115,25 @@ <option value="file29">codeberg.org/snonux/hexai/internal/lsp/handlers_init.go (63.6%)</option> - <option value="file30">codeberg.org/snonux/hexai/internal/lsp/handlers_utils.go (89.5%)</option> + <option value="file30">codeberg.org/snonux/hexai/internal/lsp/handlers_utils.go (90.0%)</option> - <option value="file31">codeberg.org/snonux/hexai/internal/lsp/server.go (83.0%)</option> + <option value="file31">codeberg.org/snonux/hexai/internal/lsp/server.go (79.8%)</option> - <option value="file32">codeberg.org/snonux/hexai/internal/lsp/transport.go (71.4%)</option> + <option value="file32">codeberg.org/snonux/hexai/internal/lsp/transport.go (73.0%)</option> - <option value="file33">codeberg.org/snonux/hexai/internal/stats/stats.go (75.4%)</option> + <option value="file33">codeberg.org/snonux/hexai/internal/stats/lock_posix.go (83.3%)</option> - <option value="file34">codeberg.org/snonux/hexai/internal/testutil/fixtures.go (100.0%)</option> + <option value="file34">codeberg.org/snonux/hexai/internal/stats/stats.go (75.8%)</option> - <option value="file35">codeberg.org/snonux/hexai/internal/textutil/human.go (92.3%)</option> + <option value="file35">codeberg.org/snonux/hexai/internal/testutil/fixtures.go (100.0%)</option> - <option value="file36">codeberg.org/snonux/hexai/internal/textutil/textutil.go (90.4%)</option> + <option value="file36">codeberg.org/snonux/hexai/internal/textutil/human.go (92.3%)</option> - <option value="file37">codeberg.org/snonux/hexai/internal/tmux/status.go (73.8%)</option> + <option value="file37">codeberg.org/snonux/hexai/internal/textutil/textutil.go (90.4%)</option> - <option value="file38">codeberg.org/snonux/hexai/internal/tmux/tmux.go (88.6%)</option> + <option value="file38">codeberg.org/snonux/hexai/internal/tmux/status.go (73.8%)</option> + + <option value="file39">codeberg.org/snonux/hexai/internal/tmux/tmux.go (88.6%)</option> </select> </div> @@ -347,7 +349,7 @@ type CustomAction struct { } // Constructor: defaults for App (kept first among functions) -func newDefaultConfig() App <span class="cov5" title="31">{ +func newDefaultConfig() App <span class="cov5" title="37">{ // Coding-friendly default temperature across providers // Users can override per provider in config.toml (including 0.0). t := 0.2 @@ -403,18 +405,18 @@ func newDefaultConfig() App <span class="cov5" title="31">{ // Load reads configuration from a file and merges with defaults. // It respects the XDG Base Directory Specification. -func Load(logger *log.Logger) App <span class="cov5" title="30">{ +func Load(logger *log.Logger) App <span class="cov5" title="36">{ cfg := newDefaultConfig() if logger == nil </span><span class="cov4" title="9">{ return cfg // Return defaults if no logger is provided (e.g. in tests) }</span> - <span class="cov5" title="21">configPath, err := getConfigPath() + <span class="cov5" title="27">configPath, err := getConfigPath() if err != nil </span><span class="cov0" title="0">{ logger.Printf("%v", err) // Even if config path cannot be resolved, still allow env overrides below. - }</span> else<span class="cov5" title="21"> { - if fileCfg, err := loadFromFile(configPath, logger); err == nil && fileCfg != nil </span><span class="cov5" title="16">{ + }</span> else<span class="cov5" title="27"> { + if fileCfg, err := loadFromFile(configPath, logger); err == nil && fileCfg != nil </span><span class="cov5" title="22">{ cfg.mergeWith(fileCfg) }</span> // When the config file is missing or invalid, we keep defaults and still @@ -422,10 +424,10 @@ func Load(logger *log.Logger) App <span class="cov5" title="30">{ } // Environment overrides (take precedence over file) - <span class="cov5" title="21">if envCfg := loadFromEnv(logger); envCfg != nil </span><span class="cov4" title="12">{ + <span class="cov5" title="27">if envCfg := loadFromEnv(logger); envCfg != nil </span><span class="cov3" title="5">{ cfg.mergeWith(envCfg) }</span> - <span class="cov5" title="21">return cfg</span> + <span class="cov5" title="27">return cfg</span> } // Private helpers @@ -488,9 +490,36 @@ type sectionStats struct { } type sectionOpenAI struct { - Model string `toml:"model"` - BaseURL string `toml:"base_url"` - Temperature *float64 `toml:"temperature"` + Model string `toml:"model"` + BaseURL string `toml:"base_url"` + Temperature *float64 `toml:"temperature"` + Presets map[string]string `toml:"presets"` +} + +func (s sectionOpenAI) isZero() bool <span class="cov5" title="22">{ + return strings.TrimSpace(s.Model) == "" && strings.TrimSpace(s.BaseURL) == "" && s.Temperature == nil && len(s.Presets) == 0 +}</span> + +func (s sectionOpenAI) resolvedModel() string <span class="cov2" title="4">{ + model := strings.TrimSpace(s.Model) + if model == "" </span><span class="cov0" title="0">{ + return "" + }</span> + <span class="cov2" title="4">if len(s.Presets) == 0 </span><span class="cov2" title="3">{ + return model + }</span> + <span class="cov1" title="1">if mapped := strings.TrimSpace(s.Presets[model]); mapped != "" </span><span class="cov1" title="1">{ + return mapped + }</span> + <span class="cov0" title="0">lower := strings.ToLower(model) + for k, v := range s.Presets </span><span class="cov0" title="0">{ + if strings.ToLower(strings.TrimSpace(k)) == lower </span><span class="cov0" title="0">{ + if mapped := strings.TrimSpace(v); mapped != "" </span><span class="cov0" title="0">{ + return mapped + }</span> + } + } + <span class="cov0" title="0">return model</span> } type sectionCopilot struct { @@ -565,7 +594,7 @@ type sectionTmux struct { CustomMenuHotkey string `toml:"custom_menu_hotkey"` } -func (fc *fileConfig) toApp() App <span class="cov5" title="16">{ +func (fc *fileConfig) toApp() App <span class="cov5" title="22">{ out := App{} // Merge section: general @@ -581,13 +610,13 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="16">{ }</span> // logging - <span class="cov5" title="16">if (fc.Logging != sectionLogging{}) </span><span class="cov1" title="1">{ + <span class="cov5" title="22">if (fc.Logging != sectionLogging{}) </span><span class="cov1" title="1">{ tmp := App{LogPreviewLimit: fc.Logging.LogPreviewLimit} out.mergeBasics(&tmp) }</span> // completion - <span class="cov5" title="16">if (fc.Completion != sectionCompletion{}) </span><span class="cov2" title="3">{ + <span class="cov5" title="22">if (fc.Completion != sectionCompletion{}) </span><span class="cov2" title="3">{ tmp := App{ CompletionDebounceMs: fc.Completion.CompletionDebounceMs, CompletionThrottleMs: fc.Completion.CompletionThrottleMs, @@ -597,41 +626,41 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="16">{ }</span> // triggers - <span class="cov5" title="16">if len(fc.Triggers.TriggerCharacters) > 0 </span><span class="cov2" title="3">{ + <span class="cov5" title="22">if len(fc.Triggers.TriggerCharacters) > 0 </span><span class="cov2" title="3">{ tmp := App{TriggerCharacters: fc.Triggers.TriggerCharacters} out.mergeBasics(&tmp) }</span> // inline - <span class="cov5" title="16">if (fc.Inline != sectionInline{}) </span><span class="cov1" title="1">{ + <span class="cov5" title="22">if (fc.Inline != sectionInline{}) </span><span class="cov1" title="1">{ tmp := App{InlineOpen: fc.Inline.InlineOpen, InlineClose: fc.Inline.InlineClose} out.mergeBasics(&tmp) }</span> // chat - <span class="cov5" title="16">if strings.TrimSpace(fc.Chat.ChatSuffix) != "" || len(fc.Chat.ChatPrefixes) > 0 </span><span class="cov1" title="1">{ + <span class="cov5" title="22">if strings.TrimSpace(fc.Chat.ChatSuffix) != "" || len(fc.Chat.ChatPrefixes) > 0 </span><span class="cov1" title="1">{ tmp := App{ChatSuffix: fc.Chat.ChatSuffix, ChatPrefixes: fc.Chat.ChatPrefixes} out.mergeBasics(&tmp) }</span> // provider - <span class="cov5" title="16">if strings.TrimSpace(fc.Provider.Name) != "" </span><span class="cov2" title="3">{ + <span class="cov5" title="22">if strings.TrimSpace(fc.Provider.Name) != "" </span><span class="cov2" title="4">{ tmp := App{Provider: fc.Provider.Name} out.mergeBasics(&tmp) }</span> // openai - <span class="cov5" title="16">if (fc.OpenAI != sectionOpenAI{}) || fc.OpenAI.Temperature != nil </span><span class="cov2" title="3">{ + <span class="cov5" title="22">if !fc.OpenAI.isZero() || fc.OpenAI.Temperature != nil </span><span class="cov2" title="4">{ tmp := App{ OpenAIBaseURL: fc.OpenAI.BaseURL, - OpenAIModel: fc.OpenAI.Model, + OpenAIModel: fc.OpenAI.resolvedModel(), OpenAITemperature: fc.OpenAI.Temperature, } out.mergeProviderFields(&tmp) }</span> // copilot - <span class="cov5" title="16">if (fc.Copilot != sectionCopilot{}) || fc.Copilot.Temperature != nil </span><span class="cov2" title="3">{ + <span class="cov5" title="22">if (fc.Copilot != sectionCopilot{}) || fc.Copilot.Temperature != nil </span><span class="cov2" title="3">{ tmp := App{ CopilotBaseURL: fc.Copilot.BaseURL, CopilotModel: fc.Copilot.Model, @@ -641,7 +670,7 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="16">{ }</span> // ollama - <span class="cov5" title="16">if (fc.Ollama != sectionOllama{}) || fc.Ollama.Temperature != nil </span><span class="cov2" title="3">{ + <span class="cov5" title="22">if (fc.Ollama != sectionOllama{}) || fc.Ollama.Temperature != nil </span><span class="cov2" title="3">{ tmp := App{ OllamaBaseURL: fc.Ollama.BaseURL, OllamaModel: fc.Ollama.Model, @@ -652,7 +681,7 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="16">{ // prompts // completion - <span class="cov5" title="16">if (fc.Prompts.Completion != sectionPromptsCompletion{}) </span><span class="cov1" title="1">{ + <span class="cov5" title="22">if (fc.Prompts.Completion != sectionPromptsCompletion{}) </span><span class="cov1" title="1">{ if strings.TrimSpace(fc.Prompts.Completion.SystemGeneral) != "" </span><span class="cov1" title="1">{ out.PromptCompletionSystemGeneral = fc.Prompts.Completion.SystemGeneral }</span> @@ -673,11 +702,11 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="16">{ }</span> } // chat - <span class="cov5" title="16">if strings.TrimSpace(fc.Prompts.Chat.System) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="22">if strings.TrimSpace(fc.Prompts.Chat.System) != "" </span><span class="cov1" title="1">{ out.PromptChatSystem = fc.Prompts.Chat.System }</span> // code action - <span class="cov5" title="16">if strings.TrimSpace(fc.Prompts.CodeAction.RewriteSystem) != "" || + <span class="cov5" title="22">if strings.TrimSpace(fc.Prompts.CodeAction.RewriteSystem) != "" || strings.TrimSpace(fc.Prompts.CodeAction.DiagnosticsSystem) != "" || strings.TrimSpace(fc.Prompts.CodeAction.DocumentSystem) != "" || strings.TrimSpace(fc.Prompts.CodeAction.RewriteUser) != "" || @@ -687,39 +716,39 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="16">{ strings.TrimSpace(fc.Prompts.CodeAction.GoTestUser) != "" || strings.TrimSpace(fc.Prompts.CodeAction.SimplifySystem) != "" || strings.TrimSpace(fc.Prompts.CodeAction.SimplifyUser) != "" || - len(fc.Prompts.CodeAction.Custom) > 0 </span><span class="cov4" title="12">{ + len(fc.Prompts.CodeAction.Custom) > 0 </span><span class="cov4" title="17">{ if strings.TrimSpace(fc.Prompts.CodeAction.RewriteSystem) != "" </span><span class="cov1" title="1">{ out.PromptCodeActionRewriteSystem = fc.Prompts.CodeAction.RewriteSystem }</span> - <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.DiagnosticsSystem) != "" </span><span class="cov1" title="1">{ + <span class="cov4" title="17">if strings.TrimSpace(fc.Prompts.CodeAction.DiagnosticsSystem) != "" </span><span class="cov1" title="1">{ out.PromptCodeActionDiagnosticsSystem = fc.Prompts.CodeAction.DiagnosticsSystem }</span> - <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.DocumentSystem) != "" </span><span class="cov1" title="1">{ + <span class="cov4" title="17">if strings.TrimSpace(fc.Prompts.CodeAction.DocumentSystem) != "" </span><span class="cov1" title="1">{ out.PromptCodeActionDocumentSystem = fc.Prompts.CodeAction.DocumentSystem }</span> - <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.RewriteUser) != "" </span><span class="cov1" title="1">{ + <span class="cov4" title="17">if strings.TrimSpace(fc.Prompts.CodeAction.RewriteUser) != "" </span><span class="cov1" title="1">{ out.PromptCodeActionRewriteUser = fc.Prompts.CodeAction.RewriteUser }</span> - <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.DiagnosticsUser) != "" </span><span class="cov1" title="1">{ + <span class="cov4" title="17">if strings.TrimSpace(fc.Prompts.CodeAction.DiagnosticsUser) != "" </span><span class="cov1" title="1">{ out.PromptCodeActionDiagnosticsUser = fc.Prompts.CodeAction.DiagnosticsUser }</span> - <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.DocumentUser) != "" </span><span class="cov1" title="1">{ + <span class="cov4" title="17">if strings.TrimSpace(fc.Prompts.CodeAction.DocumentUser) != "" </span><span class="cov1" title="1">{ out.PromptCodeActionDocumentUser = fc.Prompts.CodeAction.DocumentUser }</span> - <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.GoTestSystem) != "" </span><span class="cov1" title="1">{ + <span class="cov4" title="17">if strings.TrimSpace(fc.Prompts.CodeAction.GoTestSystem) != "" </span><span class="cov1" title="1">{ out.PromptCodeActionGoTestSystem = fc.Prompts.CodeAction.GoTestSystem }</span> - <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.GoTestUser) != "" </span><span class="cov1" title="1">{ + <span class="cov4" title="17">if strings.TrimSpace(fc.Prompts.CodeAction.GoTestUser) != "" </span><span class="cov1" title="1">{ out.PromptCodeActionGoTestUser = fc.Prompts.CodeAction.GoTestUser }</span> - <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.SimplifySystem) != "" </span><span class="cov0" title="0">{ + <span class="cov4" title="17">if strings.TrimSpace(fc.Prompts.CodeAction.SimplifySystem) != "" </span><span class="cov0" title="0">{ out.PromptCodeActionSimplifySystem = fc.Prompts.CodeAction.SimplifySystem }</span> - <span class="cov4" title="12">if strings.TrimSpace(fc.Prompts.CodeAction.SimplifyUser) != "" </span><span class="cov0" title="0">{ + <span class="cov4" title="17">if strings.TrimSpace(fc.Prompts.CodeAction.SimplifyUser) != "" </span><span class="cov0" title="0">{ out.PromptCodeActionSimplifyUser = fc.Prompts.CodeAction.SimplifyUser }</span> - <span class="cov4" title="12">if len(fc.Prompts.CodeAction.Custom) > 0 </span><span class="cov4" title="11">{ - for _, ca := range fc.Prompts.CodeAction.Custom </span><span class="cov5" title="20">{ + <span class="cov4" title="17">if len(fc.Prompts.CodeAction.Custom) > 0 </span><span class="cov4" title="16">{ + for _, ca := range fc.Prompts.CodeAction.Custom </span><span class="cov5" title="30">{ out.CustomActions = append(out.CustomActions, CustomAction{ ID: strings.TrimSpace(ca.ID), Title: strings.TrimSpace(ca.Title), @@ -734,7 +763,7 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="16">{ } } // cli - <span class="cov5" title="16">if (fc.Prompts.CLI != sectionPromptsCLI{}) </span><span class="cov1" title="1">{ + <span class="cov5" title="22">if (fc.Prompts.CLI != sectionPromptsCLI{}) </span><span class="cov1" title="1">{ if strings.TrimSpace(fc.Prompts.CLI.DefaultSystem) != "" </span><span class="cov1" title="1">{ out.PromptCLIDefaultSystem = fc.Prompts.CLI.DefaultSystem }</span> @@ -743,46 +772,46 @@ func (fc *fileConfig) toApp() App <span class="cov5" title="16">{ }</span> } // provider-native - <span class="cov5" title="16">if strings.TrimSpace(fc.Prompts.ProviderNative.Completion) != "" </span><span class="cov1" title="1">{ + <span class="cov5" title="22">if strings.TrimSpace(fc.Prompts.ProviderNative.Completion) != "" </span><span class="cov1" title="1">{ out.PromptNativeCompletion = fc.Prompts.ProviderNative.Completion }</span> // tmux - <span class="cov5" title="16">if (fc.Tmux != sectionTmux{}) </span><span class="cov2" title="3">{ + <span class="cov5" title="22">if (fc.Tmux != sectionTmux{}) </span><span class="cov2" title="3">{ out.TmuxCustomMenuHotkey = strings.TrimSpace(fc.Tmux.CustomMenuHotkey) }</span> // stats - <span class="cov5" title="16">if fc.Stats.WindowMinutes > 0 </span><span class="cov0" title="0">{ + <span class="cov5" title="22">if fc.Stats.WindowMinutes > 0 </span><span class="cov0" title="0">{ out.StatsWindowMinutes = fc.Stats.WindowMinutes }</span> - <span class="cov5" title="16">return out</span> + <span class="cov5" title="22">return out</span> } -func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="cov5" title="22">{ +func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="cov5" title="28">{ b, err := os.ReadFile(path) - if err != nil </span><span class="cov3" title="4">{ + if err != nil </span><span class="cov2" title="4">{ if !os.IsNotExist(err) && logger != nil </span><span class="cov0" title="0">{ logger.Printf("cannot open TOML config file %s: %v", path, err) }</span> - <span class="cov3" title="4">return nil, err</span> + <span class="cov2" title="4">return nil, err</span> } - <span class="cov5" title="18">var tables fileConfig + <span class="cov5" title="24">var tables fileConfig errTables := toml.NewDecoder(strings.NewReader(string(b))).Decode(&tables) // Raw map for validation/presence checks var raw map[string]any _ = toml.Unmarshal(b, &raw) - if errTables != nil </span><span class="cov2" title="2">{ - if logger != nil </span><span class="cov2" title="2">{ + if errTables != nil </span><span class="cov1" title="2">{ + if logger != nil </span><span class="cov1" title="2">{ logger.Printf("invalid TOML config file %s: %v", path, errTables) }</span> - <span class="cov2" title="2">return nil, errTables</span> + <span class="cov1" title="2">return nil, errTables</span> } // Reject legacy flat keys at top-level (sectioned-only config is allowed) - <span class="cov5" title="16">legacy := map[string]struct{}{ + <span class="cov5" title="22">legacy := map[string]struct{}{ "max_tokens": {}, "context_mode": {}, "context_window_lines": {}, "max_context_tokens": {}, "log_preview_limit": {}, "completion_debounce_ms": {}, "completion_throttle_ms": {}, "manual_invoke_min_prefix": {}, "trigger_characters": {}, "inline_open": {}, "inline_close": {}, @@ -791,8 +820,8 @@ func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="co "ollama_model": {}, "ollama_base_url": {}, "ollama_temperature": {}, "copilot_model": {}, "copilot_base_url": {}, "copilot_temperature": {}, } - for k := range raw </span><span class="cov6" title="41">{ - if _, isTable := map[string]struct{}{"general": {}, "logging": {}, "completion": {}, "triggers": {}, "inline": {}, "chat": {}, "provider": {}, "openai": {}, "copilot": {}, "ollama": {}, "prompts": {}}[k]; isTable </span><span class="cov6" title="38">{ + for k := range raw </span><span class="cov6" title="48">{ + if _, isTable := map[string]struct{}{"general": {}, "logging": {}, "completion": {}, "triggers": {}, "inline": {}, "chat": {}, "provider": {}, "openai": {}, "copilot": {}, "ollama": {}, "prompts": {}}[k]; isTable </span><span class="cov6" title="45">{ continue</span> } <span class="cov2" title="3">if _, isLegacy := legacy[k]; isLegacy </span><span class="cov0" title="0">{ @@ -800,13 +829,13 @@ func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="co }</span> } - <span class="cov5" title="16">if logger != nil </span><span class="cov5" title="16">{ + <span class="cov5" title="22">if logger != nil </span><span class="cov5" title="22">{ logger.Printf("loaded configuration from %s (TOML)", path) }</span> // Merge order: flat first, then tables (so tables win over zero flat values) // Build App from tables only - <span class="cov5" title="16">tab := tables.toApp() + <span class="cov5" title="22">tab := tables.toApp() // Ensure explicit values from raw map are respected (defensive for ints) if t, ok := raw["completion"].(map[string]any); ok </span><span class="cov2" title="3">{ if v, present := t["manual_invoke_min_prefix"]; present </span><span class="cov2" title="3">{ @@ -820,7 +849,7 @@ func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="co } } } - <span class="cov5" title="16">if t, ok := raw["logging"].(map[string]any); ok </span><span class="cov2" title="3">{ + <span class="cov5" title="22">if t, ok := raw["logging"].(map[string]any); ok </span><span class="cov2" title="3">{ if v, present := t["log_preview_limit"]; present </span><span class="cov2" title="3">{ switch vv := v.(type) </span>{ case int64:<span class="cov2" title="3"> @@ -832,10 +861,10 @@ func loadFromFile(path string, logger *log.Logger) (*App, error) <span class="co } } } - <span class="cov5" title="16">return &tab, nil</span> + <span class="cov5" title="22">return &tab, nil</span> } -func (a *App) mergeWith(other *App) <span class="cov5" title="28">{ +func (a *App) mergeWith(other *App) <span class="cov5" title="27">{ a.mergeBasics(other) a.mergeProviderFields(other) a.mergePrompts(other) @@ -873,95 +902,95 @@ func (a *App) mergeBasics(other *App) <span class="cov6" title="43">{ <span class="cov6" title="43">if len(other.TriggerCharacters) > 0 </span><span class="cov3" title="7">{ a.TriggerCharacters = slices.Clone(other.TriggerCharacters) }</span> - <span class="cov6" title="43">if s := strings.TrimSpace(other.InlineOpen); s != "" </span><span class="cov2" title="2">{ + <span class="cov6" title="43">if s := strings.TrimSpace(other.InlineOpen); s != "" </span><span class="cov1" title="2">{ a.InlineOpen = s }</span> - <span class="cov6" title="43">if s := strings.TrimSpace(other.InlineClose); s != "" </span><span class="cov2" title="2">{ + <span class="cov6" title="43">if s := strings.TrimSpace(other.InlineClose); s != "" </span><span class="cov1" title="2">{ |
