diff options
| author | Paul Buetow <paul@buetow.org> | 2025-08-18 09:28:48 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-08-18 09:28:48 +0300 |
| commit | 96ace6c7019a914e21b25fa94ddfc4ee9239c2fb (patch) | |
| tree | 30550bcab30c91e917a4d8b3feccda829a364437 /internal/appconfig | |
| parent | 6d29ac7e4b2604b5c7df50f33f8ef2357709faf2 (diff) | |
refactor(lsp,llm,hexailsp,appconfig): split long funcs; add tests
- Extract helpers to keep funcs <=50 lines; no behavior changes
- Add tests for prompt removal, code actions, and LLM request builders
- Table-drive TestInParamList; run gofmt
Diffstat (limited to 'internal/appconfig')
| -rw-r--r-- | internal/appconfig/config.go | 228 |
1 files changed, 119 insertions, 109 deletions
diff --git a/internal/appconfig/config.go b/internal/appconfig/config.go index 4fa3441..3067dd1 100644 --- a/internal/appconfig/config.go +++ b/internal/appconfig/config.go @@ -13,57 +13,57 @@ import ( // App holds user-configurable settings read from ~/.config/hexai/config.json. type App struct { - MaxTokens int `json:"max_tokens"` + MaxTokens int `json:"max_tokens"` ContextMode string `json:"context_mode"` ContextWindowLines int `json:"context_window_lines"` MaxContextTokens int `json:"max_context_tokens"` - LogPreviewLimit int `json:"log_preview_limit"` - // Single knob for LSP requests; if set, overrides hardcoded temps in LSP. - CodingTemperature *float64 `json:"coding_temperature"` + LogPreviewLimit int `json:"log_preview_limit"` + // Single knob for LSP requests; if set, overrides hardcoded temps in LSP. + CodingTemperature *float64 `json:"coding_temperature"` TriggerCharacters []string `json:"trigger_characters"` Provider string `json:"provider"` - // Provider-specific options - OpenAIBaseURL string `json:"openai_base_url"` - OpenAIModel string `json:"openai_model"` - // Default temperature for OpenAI requests (nil means use provider default) - OpenAITemperature *float64 `json:"openai_temperature"` - OllamaBaseURL string `json:"ollama_base_url"` - OllamaModel string `json:"ollama_model"` - // Default temperature for Ollama requests (nil means use provider default) - OllamaTemperature *float64 `json:"ollama_temperature"` - CopilotBaseURL string `json:"copilot_base_url"` - CopilotModel string `json:"copilot_model"` - // Default temperature for Copilot requests (nil means use provider default) - CopilotTemperature *float64 `json:"copilot_temperature"` + // Provider-specific options + OpenAIBaseURL string `json:"openai_base_url"` + OpenAIModel string `json:"openai_model"` + // Default temperature for OpenAI requests (nil means use provider default) + OpenAITemperature *float64 `json:"openai_temperature"` + OllamaBaseURL string `json:"ollama_base_url"` + OllamaModel string `json:"ollama_model"` + // Default temperature for Ollama requests (nil means use provider default) + OllamaTemperature *float64 `json:"ollama_temperature"` + CopilotBaseURL string `json:"copilot_base_url"` + CopilotModel string `json:"copilot_model"` + // Default temperature for Copilot requests (nil means use provider default) + CopilotTemperature *float64 `json:"copilot_temperature"` } // Constructor: defaults for App (kept first among functions) func newDefaultConfig() App { - // Coding-friendly default temperature across providers - // Users can override per provider in config.json (including 0.0). - t := 0.2 - return App{ - MaxTokens: 4000, - ContextMode: "always-full", - ContextWindowLines: 120, - MaxContextTokens: 4000, - LogPreviewLimit: 100, - CodingTemperature: &t, - OpenAITemperature: &t, - OllamaTemperature: &t, - CopilotTemperature: &t, - } + // Coding-friendly default temperature across providers + // Users can override per provider in config.json (including 0.0). + t := 0.2 + return App{ + MaxTokens: 4000, + ContextMode: "always-full", + ContextWindowLines: 120, + MaxContextTokens: 4000, + LogPreviewLimit: 100, + CodingTemperature: &t, + OpenAITemperature: &t, + OllamaTemperature: &t, + CopilotTemperature: &t, + } } // Load reads configuration from a file and merges with defaults. // It respects the XDG Base Directory Specification. func Load(logger *log.Logger) App { - cfg := newDefaultConfig() - if logger == nil { - return cfg // Return defaults if no logger is provided (e.g. in tests) - } + cfg := newDefaultConfig() + if logger == nil { + return cfg // Return defaults if no logger is provided (e.g. in tests) + } configPath, err := getConfigPath() if err != nil { @@ -76,91 +76,101 @@ func Load(logger *log.Logger) App { return cfg } - cfg.mergeWith(fileCfg) - return cfg + cfg.mergeWith(fileCfg) + return cfg } // Private helpers func loadFromFile(path string, logger *log.Logger) (*App, error) { - f, err := os.Open(path) - if err != nil { - if !os.IsNotExist(err) && logger != nil { - logger.Printf("cannot open config file %s: %v", path, err) - } - return nil, err - } - defer f.Close() + f, err := os.Open(path) + if err != nil { + if !os.IsNotExist(err) && logger != nil { + logger.Printf("cannot open config file %s: %v", path, err) + } + return nil, err + } + defer f.Close() - dec := json.NewDecoder(f) - var fileCfg App - if err := dec.Decode(&fileCfg); err != nil { - if logger != nil { - logger.Printf("invalid config file %s: %v", path, err) - } - return nil, err - } - return &fileCfg, nil + dec := json.NewDecoder(f) + var fileCfg App + if err := dec.Decode(&fileCfg); err != nil { + if logger != nil { + logger.Printf("invalid config file %s: %v", path, err) + } + return nil, err + } + return &fileCfg, nil } func (a *App) mergeWith(other *App) { - if other.MaxTokens > 0 { - a.MaxTokens = other.MaxTokens - } - if strings.TrimSpace(other.ContextMode) != "" { - a.ContextMode = other.ContextMode - } - if other.ContextWindowLines > 0 { - a.ContextWindowLines = other.ContextWindowLines - } - if other.MaxContextTokens > 0 { - a.MaxContextTokens = other.MaxContextTokens - } - if other.LogPreviewLimit >= 0 { - a.LogPreviewLimit = other.LogPreviewLimit - } - if other.CodingTemperature != nil { // allow explicit 0.0 - a.CodingTemperature = other.CodingTemperature - } - if len(other.TriggerCharacters) > 0 { - a.TriggerCharacters = slices.Clone(other.TriggerCharacters) - } - if strings.TrimSpace(other.Provider) != "" { - a.Provider = other.Provider - } - if strings.TrimSpace(other.OpenAIBaseURL) != "" { - a.OpenAIBaseURL = other.OpenAIBaseURL - } - if strings.TrimSpace(other.OpenAIModel) != "" { - a.OpenAIModel = other.OpenAIModel - } - if other.OpenAITemperature != nil { // allow explicit 0.0 - a.OpenAITemperature = other.OpenAITemperature - } - if strings.TrimSpace(other.OllamaBaseURL) != "" { - a.OllamaBaseURL = other.OllamaBaseURL - } - if strings.TrimSpace(other.OllamaModel) != "" { - a.OllamaModel = other.OllamaModel - } - if other.OllamaTemperature != nil { // allow explicit 0.0 - a.OllamaTemperature = other.OllamaTemperature - } - if strings.TrimSpace(other.CopilotBaseURL) != "" { - a.CopilotBaseURL = other.CopilotBaseURL - } - if strings.TrimSpace(other.CopilotModel) != "" { - a.CopilotModel = other.CopilotModel - } - if other.CopilotTemperature != nil { // allow explicit 0.0 - a.CopilotTemperature = other.CopilotTemperature - } + a.mergeBasics(other) + a.mergeProviderFields(other) +} + +// mergeBasics merges general (non-provider) fields. +func (a *App) mergeBasics(other *App) { + if other.MaxTokens > 0 { + a.MaxTokens = other.MaxTokens + } + if s := strings.TrimSpace(other.ContextMode); s != "" { + a.ContextMode = s + } + if other.ContextWindowLines > 0 { + a.ContextWindowLines = other.ContextWindowLines + } + if other.MaxContextTokens > 0 { + a.MaxContextTokens = other.MaxContextTokens + } + if other.LogPreviewLimit >= 0 { + a.LogPreviewLimit = other.LogPreviewLimit + } + if other.CodingTemperature != nil { // allow explicit 0.0 + a.CodingTemperature = other.CodingTemperature + } + if len(other.TriggerCharacters) > 0 { + a.TriggerCharacters = slices.Clone(other.TriggerCharacters) + } + if s := strings.TrimSpace(other.Provider); s != "" { + a.Provider = 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.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.CopilotBaseURL); s != "" { + a.CopilotBaseURL = s + } + if s := strings.TrimSpace(other.CopilotModel); s != "" { + a.CopilotModel = s + } + if other.CopilotTemperature != nil { // allow explicit 0.0 + a.CopilotTemperature = other.CopilotTemperature + } } func getConfigPath() (string, error) { - var configPath string - if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" { - configPath = filepath.Join(xdgConfigHome, "hexai", "config.json") - } else { + var configPath string + if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" { + configPath = filepath.Join(xdgConfigHome, "hexai", "config.json") + } else { home, err := os.UserHomeDir() if err != nil { return "", fmt.Errorf("cannot find user home directory: %v", err) |
