@@ -83,6 +83,8 @@ const (
83
83
defaultDataDirectory = ".opencode"
84
84
defaultLogLevel = "info"
85
85
appName = "opencode"
86
+
87
+ MaxTokensFallbackDefault = 4096
86
88
)
87
89
88
90
var defaultContextPaths = []string {
@@ -347,60 +349,33 @@ func applyDefaultValues() {
347
349
}
348
350
}
349
351
350
- // Validate checks if the configuration is valid and applies defaults where needed.
351
352
// It validates model IDs and providers, ensuring they are supported.
352
- func Validate () error {
353
- if cfg == nil {
354
- return fmt .Errorf ("config not loaded" )
355
- }
356
-
357
- // Validate agent models
358
- for name , agent := range cfg .Agents {
359
- // Check if model exists
360
- model , modelExists := models .SupportedModels [agent .Model ]
361
- if ! modelExists {
362
- logging .Warn ("unsupported model configured, reverting to default" ,
363
- "agent" , name ,
364
- "configured_model" , agent .Model )
365
-
366
- // Set default model based on available providers
367
- if setDefaultModelForAgent (name ) {
368
- logging .Info ("set default model for agent" , "agent" , name , "model" , cfg .Agents [name ].Model )
369
- } else {
370
- return fmt .Errorf ("no valid provider available for agent %s" , name )
371
- }
372
- continue
353
+ func validateAgent (cfg * Config , name AgentName , agent Agent ) error {
354
+ // Check if model exists
355
+ model , modelExists := models .SupportedModels [agent .Model ]
356
+ if ! modelExists {
357
+ logging .Warn ("unsupported model configured, reverting to default" ,
358
+ "agent" , name ,
359
+ "configured_model" , agent .Model )
360
+
361
+ // Set default model based on available providers
362
+ if setDefaultModelForAgent (name ) {
363
+ logging .Info ("set default model for agent" , "agent" , name , "model" , cfg .Agents [name ].Model )
364
+ } else {
365
+ return fmt .Errorf ("no valid provider available for agent %s" , name )
373
366
}
367
+ return nil
368
+ }
374
369
375
- // Check if provider for the model is configured
376
- provider := model .Provider
377
- providerCfg , providerExists := cfg .Providers [provider ]
370
+ // Check if provider for the model is configured
371
+ provider := model .Provider
372
+ providerCfg , providerExists := cfg .Providers [provider ]
378
373
379
- if ! providerExists {
380
- // Provider not configured, check if we have environment variables
381
- apiKey := getProviderAPIKey (provider )
382
- if apiKey == "" {
383
- logging .Warn ("provider not configured for model, reverting to default" ,
384
- "agent" , name ,
385
- "model" , agent .Model ,
386
- "provider" , provider )
387
-
388
- // Set default model based on available providers
389
- if setDefaultModelForAgent (name ) {
390
- logging .Info ("set default model for agent" , "agent" , name , "model" , cfg .Agents [name ].Model )
391
- } else {
392
- return fmt .Errorf ("no valid provider available for agent %s" , name )
393
- }
394
- } else {
395
- // Add provider with API key from environment
396
- cfg .Providers [provider ] = Provider {
397
- APIKey : apiKey ,
398
- }
399
- logging .Info ("added provider from environment" , "provider" , provider )
400
- }
401
- } else if providerCfg .Disabled || providerCfg .APIKey == "" {
402
- // Provider is disabled or has no API key
403
- logging .Warn ("provider is disabled or has no API key, reverting to default" ,
374
+ if ! providerExists {
375
+ // Provider not configured, check if we have environment variables
376
+ apiKey := getProviderAPIKey (provider )
377
+ if apiKey == "" {
378
+ logging .Warn ("provider not configured for model, reverting to default" ,
404
379
"agent" , name ,
405
380
"model" , agent .Model ,
406
381
"provider" , provider )
@@ -411,75 +386,110 @@ func Validate() error {
411
386
} else {
412
387
return fmt .Errorf ("no valid provider available for agent %s" , name )
413
388
}
389
+ } else {
390
+ // Add provider with API key from environment
391
+ cfg .Providers [provider ] = Provider {
392
+ APIKey : apiKey ,
393
+ }
394
+ logging .Info ("added provider from environment" , "provider" , provider )
395
+ }
396
+ } else if providerCfg .Disabled || providerCfg .APIKey == "" {
397
+ // Provider is disabled or has no API key
398
+ logging .Warn ("provider is disabled or has no API key, reverting to default" ,
399
+ "agent" , name ,
400
+ "model" , agent .Model ,
401
+ "provider" , provider )
402
+
403
+ // Set default model based on available providers
404
+ if setDefaultModelForAgent (name ) {
405
+ logging .Info ("set default model for agent" , "agent" , name , "model" , cfg .Agents [name ].Model )
406
+ } else {
407
+ return fmt .Errorf ("no valid provider available for agent %s" , name )
414
408
}
409
+ }
415
410
416
- // Validate max tokens
417
- if agent .MaxTokens <= 0 {
418
- logging .Warn ("invalid max tokens, setting to default" ,
419
- "agent" , name ,
420
- "model" , agent .Model ,
421
- "max_tokens" , agent .MaxTokens )
411
+ // Validate max tokens
412
+ if agent .MaxTokens <= 0 {
413
+ logging .Warn ("invalid max tokens, setting to default" ,
414
+ "agent" , name ,
415
+ "model" , agent .Model ,
416
+ "max_tokens" , agent .MaxTokens )
422
417
423
- // Update the agent with default max tokens
424
- updatedAgent := cfg .Agents [name ]
425
- if model .DefaultMaxTokens > 0 {
426
- updatedAgent .MaxTokens = model .DefaultMaxTokens
427
- } else {
428
- updatedAgent .MaxTokens = 4096 // Fallback default
429
- }
430
- cfg .Agents [name ] = updatedAgent
431
- } else if model .ContextWindow > 0 && agent .MaxTokens > model .ContextWindow / 2 {
432
- // Ensure max tokens doesn't exceed half the context window (reasonable limit)
433
- logging .Warn ("max tokens exceeds half the context window, adjusting" ,
418
+ // Update the agent with default max tokens
419
+ updatedAgent := cfg .Agents [name ]
420
+ if model .DefaultMaxTokens > 0 {
421
+ updatedAgent .MaxTokens = model .DefaultMaxTokens
422
+ } else {
423
+ updatedAgent .MaxTokens = MaxTokensFallbackDefault
424
+ }
425
+ cfg .Agents [name ] = updatedAgent
426
+ } else if model .ContextWindow > 0 && agent .MaxTokens > model .ContextWindow / 2 {
427
+ // Ensure max tokens doesn't exceed half the context window (reasonable limit)
428
+ logging .Warn ("max tokens exceeds half the context window, adjusting" ,
429
+ "agent" , name ,
430
+ "model" , agent .Model ,
431
+ "max_tokens" , agent .MaxTokens ,
432
+ "context_window" , model .ContextWindow )
433
+
434
+ // Update the agent with adjusted max tokens
435
+ updatedAgent := cfg .Agents [name ]
436
+ updatedAgent .MaxTokens = model .ContextWindow / 2
437
+ cfg .Agents [name ] = updatedAgent
438
+ }
439
+
440
+ // Validate reasoning effort for models that support reasoning
441
+ if model .CanReason && provider == models .ProviderOpenAI {
442
+ if agent .ReasoningEffort == "" {
443
+ // Set default reasoning effort for models that support it
444
+ logging .Info ("setting default reasoning effort for model that supports reasoning" ,
434
445
"agent" , name ,
435
- "model" , agent .Model ,
436
- "max_tokens" , agent .MaxTokens ,
437
- "context_window" , model .ContextWindow )
446
+ "model" , agent .Model )
438
447
439
- // Update the agent with adjusted max tokens
448
+ // Update the agent with default reasoning effort
440
449
updatedAgent := cfg .Agents [name ]
441
- updatedAgent .MaxTokens = model . ContextWindow / 2
450
+ updatedAgent .ReasoningEffort = "medium"
442
451
cfg .Agents [name ] = updatedAgent
443
- }
444
-
445
- // Validate reasoning effort for models that support reasoning
446
- if model .CanReason && provider == models .ProviderOpenAI {
447
- if agent .ReasoningEffort == "" {
448
- // Set default reasoning effort for models that support it
449
- logging .Info ("setting default reasoning effort for model that supports reasoning" ,
452
+ } else {
453
+ // Check if reasoning effort is valid (low, medium, high)
454
+ effort := strings .ToLower (agent .ReasoningEffort )
455
+ if effort != "low" && effort != "medium" && effort != "high" {
456
+ logging .Warn ("invalid reasoning effort, setting to medium" ,
450
457
"agent" , name ,
451
- "model" , agent .Model )
458
+ "model" , agent .Model ,
459
+ "reasoning_effort" , agent .ReasoningEffort )
452
460
453
- // Update the agent with default reasoning effort
461
+ // Update the agent with valid reasoning effort
454
462
updatedAgent := cfg .Agents [name ]
455
463
updatedAgent .ReasoningEffort = "medium"
456
464
cfg .Agents [name ] = updatedAgent
457
- } else {
458
- // Check if reasoning effort is valid (low, medium, high)
459
- effort := strings .ToLower (agent .ReasoningEffort )
460
- if effort != "low" && effort != "medium" && effort != "high" {
461
- logging .Warn ("invalid reasoning effort, setting to medium" ,
462
- "agent" , name ,
463
- "model" , agent .Model ,
464
- "reasoning_effort" , agent .ReasoningEffort )
465
-
466
- // Update the agent with valid reasoning effort
467
- updatedAgent := cfg .Agents [name ]
468
- updatedAgent .ReasoningEffort = "medium"
469
- cfg .Agents [name ] = updatedAgent
470
- }
471
465
}
472
- } else if ! model .CanReason && agent .ReasoningEffort != "" {
473
- // Model doesn't support reasoning but reasoning effort is set
474
- logging .Warn ("model doesn't support reasoning but reasoning effort is set, ignoring" ,
475
- "agent" , name ,
476
- "model" , agent .Model ,
477
- "reasoning_effort" , agent .ReasoningEffort )
466
+ }
467
+ } else if ! model .CanReason && agent .ReasoningEffort != "" {
468
+ // Model doesn't support reasoning but reasoning effort is set
469
+ logging .Warn ("model doesn't support reasoning but reasoning effort is set, ignoring" ,
470
+ "agent" , name ,
471
+ "model" , agent .Model ,
472
+ "reasoning_effort" , agent .ReasoningEffort )
478
473
479
- // Update the agent to remove reasoning effort
480
- updatedAgent := cfg .Agents [name ]
481
- updatedAgent .ReasoningEffort = ""
482
- cfg .Agents [name ] = updatedAgent
474
+ // Update the agent to remove reasoning effort
475
+ updatedAgent := cfg .Agents [name ]
476
+ updatedAgent .ReasoningEffort = ""
477
+ cfg .Agents [name ] = updatedAgent
478
+ }
479
+
480
+ return nil
481
+ }
482
+
483
+ // Validate checks if the configuration is valid and applies defaults where needed.
484
+ func Validate () error {
485
+ if cfg == nil {
486
+ return fmt .Errorf ("config not loaded" )
487
+ }
488
+
489
+ // Validate agent models
490
+ for name , agent := range cfg .Agents {
491
+ if err := validateAgent (cfg , name , agent ); err != nil {
492
+ return err
483
493
}
484
494
}
485
495
@@ -629,3 +639,36 @@ func WorkingDirectory() string {
629
639
}
630
640
return cfg .WorkingDir
631
641
}
642
+
643
+ func UpdateAgentModel (agentName AgentName , modelID models.ModelID ) error {
644
+ if cfg == nil {
645
+ panic ("config not loaded" )
646
+ }
647
+
648
+ existingAgentCfg := cfg .Agents [agentName ]
649
+
650
+ model , ok := models .SupportedModels [modelID ]
651
+ if ! ok {
652
+ return fmt .Errorf ("model %s not supported" , modelID )
653
+ }
654
+
655
+ maxTokens := existingAgentCfg .MaxTokens
656
+ if model .DefaultMaxTokens > 0 {
657
+ maxTokens = model .DefaultMaxTokens
658
+ }
659
+
660
+ newAgentCfg := Agent {
661
+ Model : modelID ,
662
+ MaxTokens : maxTokens ,
663
+ ReasoningEffort : existingAgentCfg .ReasoningEffort ,
664
+ }
665
+ cfg .Agents [agentName ] = newAgentCfg
666
+
667
+ if err := validateAgent (cfg , agentName , newAgentCfg ); err != nil {
668
+ // revert config update on failure
669
+ cfg .Agents [agentName ] = existingAgentCfg
670
+ return fmt .Errorf ("failed to update agent model: %w" , err )
671
+ }
672
+
673
+ return nil
674
+ }
0 commit comments