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