summaryrefslogtreecommitdiff
path: root/internal/appconfig/config.go
blob: d12bdbea9a3e3026d67426b307666b0fe89d6bdf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package appconfig

import (
    "encoding/json"
    "log"
    "os"
    "path/filepath"
    "strings"
)

// App holds user-configurable settings read from ~/.config/hexai/config.json.
type App struct {
    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"`
    NoDiskIO           bool     `json:"no_disk_io"`
    TriggerCharacters  []string `json:"trigger_characters"`
    Provider           string   `json:"provider"`
    // Provider-specific options
    OpenAIBaseURL  string `json:"openai_base_url"`
    OpenAIModel    string `json:"openai_model"`
    OllamaBaseURL  string `json:"ollama_base_url"`
    OllamaModel    string `json:"ollama_model"`
    CopilotBaseURL string `json:"copilot_base_url"`
    CopilotModel   string `json:"copilot_model"`
}

// Load reads configuration from ~/.config/hexai/config.json and merges with defaults.
func Load(logger *log.Logger) App {
    cfg := App{
        MaxTokens:          4000,
        ContextMode:        "always-full",
        ContextWindowLines: 120,
        MaxContextTokens:   4000,
        LogPreviewLimit:    100,
        NoDiskIO:           true,
    }
    home, err := os.UserHomeDir()
    if err != nil {
        return cfg
    }
    path := filepath.Join(home, ".config", "hexai", "config.json")
    f, err := os.Open(path)
    if err != nil {
        return cfg
    }
    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 cfg
    }
    // Merge: file overrides defaults when provided
    if fileCfg.MaxTokens > 0 {
        cfg.MaxTokens = fileCfg.MaxTokens
    }
    if strings.TrimSpace(fileCfg.ContextMode) != "" {
        cfg.ContextMode = fileCfg.ContextMode
    }
    if fileCfg.ContextWindowLines > 0 {
        cfg.ContextWindowLines = fileCfg.ContextWindowLines
    }
    if fileCfg.MaxContextTokens > 0 {
        cfg.MaxContextTokens = fileCfg.MaxContextTokens
    }
    if fileCfg.LogPreviewLimit >= 0 {
        cfg.LogPreviewLimit = fileCfg.LogPreviewLimit
    }
    cfg.NoDiskIO = fileCfg.NoDiskIO
    if len(fileCfg.TriggerCharacters) > 0 {
        cfg.TriggerCharacters = append([]string{}, fileCfg.TriggerCharacters...)
    }
    if strings.TrimSpace(fileCfg.Provider) != "" {
        cfg.Provider = fileCfg.Provider
    }
    // Provider-specific options
    if strings.TrimSpace(fileCfg.OpenAIBaseURL) != "" {
        cfg.OpenAIBaseURL = fileCfg.OpenAIBaseURL
    }
    if strings.TrimSpace(fileCfg.OpenAIModel) != "" {
        cfg.OpenAIModel = fileCfg.OpenAIModel
    }
    if strings.TrimSpace(fileCfg.OllamaBaseURL) != "" {
        cfg.OllamaBaseURL = fileCfg.OllamaBaseURL
    }
    if strings.TrimSpace(fileCfg.OllamaModel) != "" {
        cfg.OllamaModel = fileCfg.OllamaModel
    }
    if strings.TrimSpace(fileCfg.CopilotBaseURL) != "" {
        cfg.CopilotBaseURL = fileCfg.CopilotBaseURL
    }
    if strings.TrimSpace(fileCfg.CopilotModel) != "" {
        cfg.CopilotModel = fileCfg.CopilotModel
    }
    return cfg
}