on main help sign in
fix: fix config
b738f51Kujtim Hoxha
internal/config/config.go+100−80
@@ -200,6 +200,28 @@ var (
200
200
201)
201)
202
202
203+func readConfigFile(path string) (*Config, error) {
204+ var cfg *Config
205+ if _, err := os.Stat(path); err != nil && !os.IsNotExist(err) {
206+ // some other error occurred while checking the file
207+ return nil, err
208+ } else if err == nil {
209+ // config file exists, read it
210+ file, err := os.ReadFile(path)
211+ if err != nil {
212+ return nil, err
213+ }
214+ cfg = &Config{}
215+ if err := json.Unmarshal(file, cfg); err != nil {
216+ return nil, err
217+ }
218+ } else {
219+ // config file does not exist, create a new one
220+ cfg = &Config{}
221+ }
222+ return cfg, nil
223+}
224+
203func loadConfig(cwd string, debug bool) (*Config, error) {
225func loadConfig(cwd string, debug bool) (*Config, error) {
204 // First read the global config file
226 // First read the global config file
205 cfgPath := ConfigPath()
227 cfgPath := ConfigPath()
@@ -248,47 +270,29 @@ func loadConfig(cwd string, debug bool) (*Config, error) {
248 }))
270 }))
249 slog.SetDefault(logger)
271 slog.SetDefault(logger)
250 }
272 }
251− var globalCfg *Config
273+
252− if _, err := os.Stat(cfgPath); err != nil && !os.IsNotExist(err) {
274+ priorityOrderedConfigFiles := []string{
253− // some other error occurred while checking the file
254− return nil, err
255− } else if err == nil {
256− // config file exists, read it
275+ cfgPath, // Global config file
257− file, err := os.ReadFile(cfgPath)
276+ filepath.Join(cwd, "crush.json"), // Local config file
258− if err != nil {
277+ filepath.Join(cwd, ".crush.json"), // Local config file
259− return nil, err
260− }
261− globalCfg = &Config{}
262− if err := json.Unmarshal(file, globalCfg); err != nil {
263− return nil, err
264− }
265− } else {
266− // config file does not exist, create a new one
267− globalCfg = &Config{}
268 }
278 }
269
279
270− var localConfig *Config
280+ configs := make([]*Config, 0)
271− // Global config loaded, now read the local config file
281+ for _, path := range priorityOrderedConfigFiles {
272− localConfigPath := filepath.Join(cwd, "crush.json")
282+ localConfig, err := readConfigFile(path)
273− if _, err := os.Stat(localConfigPath); err != nil && !os.IsNotExist(err) {
274− // some other error occurred while checking the file
275− return nil, err
276− } else if err == nil {
277− // local config file exists, read it
278− file, err := os.ReadFile(localConfigPath)
279 if err != nil {
283 if err != nil {
280− return nil, err
284+ return nil, fmt.Errorf("failed to read config file %s: %w", path, err)
281 }
285 }
282− localConfig = &Config{}
286+ if localConfig != nil {
283− if err := json.Unmarshal(file, localConfig); err != nil {
287+ // If the config file was read successfully, add it to the list
284− return nil, err
288+ configs = append(configs, localConfig)
285 }
289 }
286 }
290 }
287
291
288 // merge options
292 // merge options
289− mergeOptions(cfg, globalCfg, localConfig)
293+ mergeOptions(cfg, configs...)
290
294
291− mergeProviderConfigs(cfg, globalCfg, localConfig)
295+ mergeProviderConfigs(cfg, configs...)
292 // no providers found the app is not initialized yet
296 // no providers found the app is not initialized yet
293 if len(cfg.Providers) == 0 {
297 if len(cfg.Providers) == 0 {
294 return cfg, nil
298 return cfg, nil
@@ -310,7 +314,7 @@ func loadConfig(cwd string, debug bool) (*Config, error) {
310 cfg.Models = PreferredModels{}
314 cfg.Models = PreferredModels{}
311 }
315 }
312
316
313− mergeModels(cfg, globalCfg, localConfig)
317+ mergeModels(cfg, configs...)
314
318
315 agents := map[AgentID]Agent{
319 agents := map[AgentID]Agent{
316 AgentCoder: {
320 AgentCoder: {
@@ -340,9 +344,9 @@ func loadConfig(cwd string, debug bool) (*Config, error) {
340 },
344 },
341 }
345 }
342 cfg.Agents = agents
346 cfg.Agents = agents
343− mergeAgents(cfg, globalCfg, localConfig)
347+ mergeAgents(cfg, configs...)
344− mergeMCPs(cfg, globalCfg, localConfig)
348+ mergeMCPs(cfg, configs...)
345− mergeLSPs(cfg, globalCfg, localConfig)
349+ mergeLSPs(cfg, configs...)
346
350
347 // Validate the final configuration
351 // Validate the final configuration
348 if err := cfg.Validate(); err != nil {
352 if err := cfg.Validate(); err != nil {
@@ -457,8 +461,8 @@ func validateProvider(p provider.InferenceProvider, providerConfig ProviderConfi
457 return nil
461 return nil
458}
462}
459
463
460−func mergeModels(base, global, local *Config) {
464+func mergeModels(base *Config, others ...*Config) {
461− for _, cfg := range []*Config{global, local} {
465+ for _, cfg := range others {
462 if cfg == nil {
466 if cfg == nil {
463 continue
467 continue
464 }
468 }
@@ -472,8 +476,8 @@ func mergeModels(base, global, local *Config) {
472 }
476 }
473}
477}
474
478
475−func mergeOptions(base, global, local *Config) {
479+func mergeOptions(base *Config, others ...*Config) {
476− for _, cfg := range []*Config{global, local} {
480+ for _, cfg := range others {
477 if cfg == nil {
481 if cfg == nil {
478 continue
482 continue
479 }
483 }
@@ -506,8 +510,8 @@ func mergeOptions(base, global, local *Config) {
506 }
510 }
507}
511}
508
512
509−func mergeAgents(base, global, local *Config) {
513+func mergeAgents(base *Config, others ...*Config) {
510− for _, cfg := range []*Config{global, local} {
514+ for _, cfg := range others {
511 if cfg == nil {
515 if cfg == nil {
512 continue
516 continue
513 }
517 }
@@ -575,8 +579,8 @@ func mergeAgents(base, global, local *Config) {
575 }
579 }
576}
580}
577
581
578−func mergeMCPs(base, global, local *Config) {
582+func mergeMCPs(base *Config, others ...*Config) {
579− for _, cfg := range []*Config{global, local} {
583+ for _, cfg := range others {
580 if cfg == nil {
584 if cfg == nil {
581 continue
585 continue
582 }
586 }
@@ -584,8 +588,8 @@ func mergeMCPs(base, global, local *Config) {
584 }
588 }
585}
589}
586
590
587−func mergeLSPs(base, global, local *Config) {
591+func mergeLSPs(base *Config, others ...*Config) {
588− for _, cfg := range []*Config{global, local} {
592+ for _, cfg := range others {
589 if cfg == nil {
593 if cfg == nil {
590 continue
594 continue
591 }
595 }
@@ -593,15 +597,27 @@ func mergeLSPs(base, global, local *Config) {
593 }
597 }
594}
598}
595
599
596−func mergeProviderConfigs(base, global, local *Config) {
600+func mergeProviderConfigs(base *Config, others ...*Config) {
597− for _, cfg := range []*Config{global, local} {
601+ for _, cfg := range others {
598 if cfg == nil {
602 if cfg == nil {
599 continue
603 continue
600 }
604 }
601 for providerName, p := range cfg.Providers {
605 for providerName, p := range cfg.Providers {
602 p.ID = providerName
606 p.ID = providerName
603 if _, ok := base.Providers[providerName]; !ok {
607 if _, ok := base.Providers[providerName]; !ok {
608+ if slices.Contains(provider.KnownProviders(), providerName) {
609+ providers := Providers()
610+ for _, providerDef := range providers {
611+ if providerDef.ID == providerName {
612+ logging.Info("Using default provider config for", "provider", providerName)
613+ baseProvider := getDefaultProviderConfig(providerDef, providerDef.APIKey)
604− base.Providers[providerName] = p
614+ base.Providers[providerName] = mergeProviderConfig(providerName, baseProvider, p)
615+ break
616+ }
617+ }
618+ } else {
619+ base.Providers[providerName] = p
620+ }
605 } else {
621 } else {
606 base.Providers[providerName] = mergeProviderConfig(providerName, base.Providers[providerName], p)
622 base.Providers[providerName] = mergeProviderConfig(providerName, base.Providers[providerName], p)
607 }
623 }
@@ -676,6 +692,40 @@ func providerDefaultConfig(providerID provider.InferenceProvider) ProviderConfig
676 }
692 }
677}
693}
678
694
695+func getDefaultProviderConfig(p provider.Provider, apiKey string) ProviderConfig {
696+ providerConfig := providerDefaultConfig(p.ID)
697+ providerConfig.APIKey = apiKey
698+ providerConfig.DefaultLargeModel = p.DefaultLargeModelID
699+ providerConfig.DefaultSmallModel = p.DefaultSmallModelID
700+ baseURL := p.APIEndpoint
701+ if strings.HasPrefix(baseURL, "$") {
702+ envVar := strings.TrimPrefix(baseURL, "$")
703+ baseURL = os.Getenv(envVar)
704+ }
705+ providerConfig.BaseURL = baseURL
706+ for _, model := range p.Models {
707+ configModel := Model{
708+ ID: model.ID,
709+ Name: model.Name,
710+ CostPer1MIn: model.CostPer1MIn,
711+ CostPer1MOut: model.CostPer1MOut,
712+ CostPer1MInCached: model.CostPer1MInCached,
713+ CostPer1MOutCached: model.CostPer1MOutCached,
714+ ContextWindow: model.ContextWindow,
715+ DefaultMaxTokens: model.DefaultMaxTokens,
716+ CanReason: model.CanReason,
717+ SupportsImages: model.SupportsImages,
718+ }
719+ // Set reasoning effort for reasoning models
720+ if model.HasReasoningEffort && model.DefaultReasoningEffort != "" {
721+ configModel.HasReasoningEffort = model.HasReasoningEffort
722+ configModel.ReasoningEffort = model.DefaultReasoningEffort
723+ }
724+ providerConfig.Models = append(providerConfig.Models, configModel)
725+ }
726+ return providerConfig
727+}
728+
679func defaultConfigBasedOnEnv() *Config {
729func defaultConfigBasedOnEnv() *Config {
680 cfg := &Config{
730 cfg := &Config{
681 Options: Options{
731 Options: Options{
@@ -694,37 +744,7 @@ func defaultConfigBasedOnEnv() *Config {
694 if strings.HasPrefix(p.APIKey, "$") {
744 if strings.HasPrefix(p.APIKey, "$") {
695 envVar := strings.TrimPrefix(p.APIKey, "$")
745 envVar := strings.TrimPrefix(p.APIKey, "$")
696 if apiKey := os.Getenv(envVar); apiKey != "" {
746 if apiKey := os.Getenv(envVar); apiKey != "" {
697− providerConfig := providerDefaultConfig(p.ID)
698− providerConfig.APIKey = apiKey
699− providerConfig.DefaultLargeModel = p.DefaultLargeModelID
700− providerConfig.DefaultSmallModel = p.DefaultSmallModelID
701− baseURL := p.APIEndpoint
702− if strings.HasPrefix(baseURL, "$") {
703− envVar := strings.TrimPrefix(baseURL, "$")
704− baseURL = os.Getenv(envVar)
705− }
706− providerConfig.BaseURL = baseURL
707− for _, model := range p.Models {
708− configModel := Model{
709− ID: model.ID,
710− Name: model.Name,
711− CostPer1MIn: model.CostPer1MIn,
712− CostPer1MOut: model.CostPer1MOut,
713− CostPer1MInCached: model.CostPer1MInCached,
714− CostPer1MOutCached: model.CostPer1MOutCached,
715− ContextWindow: model.ContextWindow,
716− DefaultMaxTokens: model.DefaultMaxTokens,
717− CanReason: model.CanReason,
718− SupportsImages: model.SupportsImages,
719− }
720− // Set reasoning effort for reasoning models
721− if model.HasReasoningEffort && model.DefaultReasoningEffort != "" {
722− configModel.HasReasoningEffort = model.HasReasoningEffort
723− configModel.ReasoningEffort = model.DefaultReasoningEffort
724− }
725− providerConfig.Models = append(providerConfig.Models, configModel)
726− }
727− cfg.Providers[p.ID] = providerConfig
747+ cfg.Providers[p.ID] = getDefaultProviderConfig(p, apiKey)
728 }
748 }
729 }
749 }
730 }
750 }
internal/llm/provider/anthropic.go+4−4
@@ -382,6 +382,10 @@ func (a *anthropicClient) shouldRetry(attempts int, err error) (bool, int64, err
382 return false, 0, err
382 return false, 0, err
383 }
383 }
384
384
385+ if attempts > maxRetries {
386+ return false, 0, fmt.Errorf("maximum retry attempts reached for rate limit: %d retries", maxRetries)
387+ }
388+
385 if apiErr.StatusCode == 401 {
389 if apiErr.StatusCode == 401 {
386 a.providerOptions.apiKey, err = config.ResolveAPIKey(a.providerOptions.config.APIKey)
390 a.providerOptions.apiKey, err = config.ResolveAPIKey(a.providerOptions.config.APIKey)
387 if err != nil {
391 if err != nil {
@@ -395,10 +399,6 @@ func (a *anthropicClient) shouldRetry(attempts int, err error) (bool, int64, err
395 return false, 0, err
399 return false, 0, err
396 }
400 }
397
401
398− if attempts > maxRetries {
399− return false, 0, fmt.Errorf("maximum retry attempts reached for rate limit: %d retries", maxRetries)
400− }
401−
402 retryMs := 0
402 retryMs := 0
403 retryAfterValues := apiErr.Response.Header.Values("Retry-After")
403 retryAfterValues := apiErr.Response.Header.Values("Retry-After")
404
404
internal/llm/provider/openai.go+4−4
@@ -347,6 +347,10 @@ func (o *openaiClient) shouldRetry(attempts int, err error) (bool, int64, error)
347 return false, 0, err
347 return false, 0, err
348 }
348 }
349
349
350+ if attempts > maxRetries {
351+ return false, 0, fmt.Errorf("maximum retry attempts reached for rate limit: %d retries", maxRetries)
352+ }
353+
350 // Check for token expiration (401 Unauthorized)
354 // Check for token expiration (401 Unauthorized)
351 if apiErr.StatusCode == 401 {
355 if apiErr.StatusCode == 401 {
352 o.providerOptions.apiKey, err = config.ResolveAPIKey(o.providerOptions.config.APIKey)
356 o.providerOptions.apiKey, err = config.ResolveAPIKey(o.providerOptions.config.APIKey)
@@ -361,10 +365,6 @@ func (o *openaiClient) shouldRetry(attempts int, err error) (bool, int64, error)
361 return false, 0, err
365 return false, 0, err
362 }
366 }
363
367
364− if attempts > maxRetries {
365− return false, 0, fmt.Errorf("maximum retry attempts reached for rate limit: %d retries", maxRetries)
366− }
367−
368 retryMs := 0
368 retryMs := 0
369 retryAfterValues := apiErr.Response.Header.Values("Retry-After")
369 retryAfterValues := apiErr.Response.Header.Values("Retry-After")
370
370