@@ -83,6 +83,8 @@ const (
8383 defaultDataDirectory = ".opencode"
8484 defaultLogLevel = "info"
8585 appName = "opencode"
86+
87+ MaxTokensFallbackDefault = 4096
8688)
8789
8890var defaultContextPaths = []string {
@@ -347,60 +349,33 @@ func applyDefaultValues() {
347349 }
348350}
349351
350- // Validate checks if the configuration is valid and applies defaults where needed.
351352// 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 )
373366 }
367+ return nil
368+ }
374369
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 ]
378373
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" ,
404379 "agent" , name ,
405380 "model" , agent .Model ,
406381 "provider" , provider )
@@ -411,75 +386,110 @@ func Validate() error {
411386 } else {
412387 return fmt .Errorf ("no valid provider available for agent %s" , name )
413388 }
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 )
414408 }
409+ }
415410
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 )
422417
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" ,
434445 "agent" , name ,
435- "model" , agent .Model ,
436- "max_tokens" , agent .MaxTokens ,
437- "context_window" , model .ContextWindow )
446+ "model" , agent .Model )
438447
439- // Update the agent with adjusted max tokens
448+ // Update the agent with default reasoning effort
440449 updatedAgent := cfg .Agents [name ]
441- updatedAgent .MaxTokens = model . ContextWindow / 2
450+ updatedAgent .ReasoningEffort = "medium"
442451 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" ,
450457 "agent" , name ,
451- "model" , agent .Model )
458+ "model" , agent .Model ,
459+ "reasoning_effort" , agent .ReasoningEffort )
452460
453- // Update the agent with default reasoning effort
461+ // Update the agent with valid reasoning effort
454462 updatedAgent := cfg .Agents [name ]
455463 updatedAgent .ReasoningEffort = "medium"
456464 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- }
471465 }
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 )
478473
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
483493 }
484494 }
485495
@@ -629,3 +639,36 @@ func WorkingDirectory() string {
629639 }
630640 return cfg .WorkingDir
631641}
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