14 // configEndpointChatGPT names the ChatGPT backend with the subscription
16 configEndpointChatGPT = "chatgpt"
18 // configEndpointAPI names the OpenAI API with an API key.
19 configEndpointAPI = "api"
22 // Config holds the settings of config.json in the data directory.
24 Endpoint string `json:"endpoint"`
25 Voice VoiceConfig `json:"voice"`
26 Compactor ModelConfig `json:"compactor"`
29 // ModelConfig names a model and its reasoning effort.
30 type ModelConfig struct {
31 Model string `json:"model"`
32 Effort string `json:"effort"`
35 // loadConfig reads config.json in dir over the defaults. A missing file gives
36 // the defaults. An unknown field is an error, so a typo does not become a
38 func loadConfig(dir string) (Config, error) {
39 config := defaultConfig()
40 data, err := os.ReadFile(filepath.Join(dir, "config.json"))
41 if errors.Is(err, os.ErrNotExist) {
46 return Config{}, fmt.Errorf("read config: %w", err)
49 decoder := json.NewDecoder(bytes.NewReader(data))
50 decoder.DisallowUnknownFields()
52 err = decoder.Decode(&config)
53 if err == nil && !errors.Is(decoder.Decode(&struct{}{}), io.EOF) {
54 err = errors.New("data after the JSON object")
58 err = config.validate()
62 return Config{}, fmt.Errorf("config.json: %w", err)
68 // validate makes sure that each setting has a valid value.
69 func (c Config) validate() error {
70 if c.Endpoint != configEndpointChatGPT && c.Endpoint != configEndpointAPI {
71 return fmt.Errorf("endpoint %q is not %q or %q", c.Endpoint, configEndpointChatGPT, configEndpointAPI)
74 if c.Compactor.Model == "" || c.Compactor.Effort == "" {
75 return errors.New("compactor needs a model and an effort")
81 // defaultConfig returns the settings that apply when config.json does not
83 func defaultConfig() Config {
85 Endpoint: configEndpointChatGPT,
86 Compactor: ModelConfig{