diff options
| author | Aiden Cline <[email protected]> | 2025-04-28 12:25:06 -0500 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-04-28 19:25:06 +0200 |
| commit | b3c0285db3dd5d5140481bf5118812e8dbc89795 (patch) | |
| tree | 3ead4b4447743c57cec2eef672d18615b7bdb2cb /internal/config | |
| parent | 805aeff83cad4c17e25acdd671d2731be104b3e0 (diff) | |
| download | opencode-b3c0285db3dd5d5140481bf5118812e8dbc89795.tar.gz opencode-b3c0285db3dd5d5140481bf5118812e8dbc89795.zip | |
feat: model selection for given provider (#57)
* feat: model selection for given provider
* tweak: adjust cfg validation func, remove duplicated logic, consolidate agent updating into agent.go
* tweak: make the model dialog scrollable, adjust padding slightly for modal"
* feat: add provider selection, add hints, simplify some logic, add horizontal scrolling support, additional scroll indicators"
* remove nav help
* update docs
* increase number of visible models, make horizontal scroll "wrap"
* add provider popularity rankings
Diffstat (limited to 'internal/config')
| -rw-r--r-- | internal/config/config.go | 253 |
1 files changed, 148 insertions, 105 deletions
diff --git a/internal/config/config.go b/internal/config/config.go index b3a9861e1..9aa22bd4e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -83,6 +83,8 @@ const ( defaultDataDirectory = ".opencode" defaultLogLevel = "info" appName = "opencode" + + MaxTokensFallbackDefault = 4096 ) var defaultContextPaths = []string{ @@ -347,60 +349,33 @@ func applyDefaultValues() { } } -// Validate checks if the configuration is valid and applies defaults where needed. // It validates model IDs and providers, ensuring they are supported. -func Validate() error { - if cfg == nil { - return fmt.Errorf("config not loaded") - } - - // Validate agent models - for name, agent := range cfg.Agents { - // Check if model exists - model, modelExists := models.SupportedModels[agent.Model] - if !modelExists { - logging.Warn("unsupported model configured, reverting to default", - "agent", name, - "configured_model", agent.Model) - - // Set default model based on available providers - if setDefaultModelForAgent(name) { - logging.Info("set default model for agent", "agent", name, "model", cfg.Agents[name].Model) - } else { - return fmt.Errorf("no valid provider available for agent %s", name) - } - continue +func validateAgent(cfg *Config, name AgentName, agent Agent) error { + // Check if model exists + model, modelExists := models.SupportedModels[agent.Model] + if !modelExists { + logging.Warn("unsupported model configured, reverting to default", + "agent", name, + "configured_model", agent.Model) + + // Set default model based on available providers + if setDefaultModelForAgent(name) { + logging.Info("set default model for agent", "agent", name, "model", cfg.Agents[name].Model) + } else { + return fmt.Errorf("no valid provider available for agent %s", name) } + return nil + } - // Check if provider for the model is configured - provider := model.Provider - providerCfg, providerExists := cfg.Providers[provider] + // Check if provider for the model is configured + provider := model.Provider + providerCfg, providerExists := cfg.Providers[provider] - if !providerExists { - // Provider not configured, check if we have environment variables - apiKey := getProviderAPIKey(provider) - if apiKey == "" { - logging.Warn("provider not configured for model, reverting to default", - "agent", name, - "model", agent.Model, - "provider", provider) - - // Set default model based on available providers - if setDefaultModelForAgent(name) { - logging.Info("set default model for agent", "agent", name, "model", cfg.Agents[name].Model) - } else { - return fmt.Errorf("no valid provider available for agent %s", name) - } - } else { - // Add provider with API key from environment - cfg.Providers[provider] = Provider{ - APIKey: apiKey, - } - logging.Info("added provider from environment", "provider", provider) - } - } else if providerCfg.Disabled || providerCfg.APIKey == "" { - // Provider is disabled or has no API key - logging.Warn("provider is disabled or has no API key, reverting to default", + if !providerExists { + // Provider not configured, check if we have environment variables + apiKey := getProviderAPIKey(provider) + if apiKey == "" { + logging.Warn("provider not configured for model, reverting to default", "agent", name, "model", agent.Model, "provider", provider) @@ -411,75 +386,110 @@ func Validate() error { } else { return fmt.Errorf("no valid provider available for agent %s", name) } + } else { + // Add provider with API key from environment + cfg.Providers[provider] = Provider{ + APIKey: apiKey, + } + logging.Info("added provider from environment", "provider", provider) + } + } else if providerCfg.Disabled || providerCfg.APIKey == "" { + // Provider is disabled or has no API key + logging.Warn("provider is disabled or has no API key, reverting to default", + "agent", name, + "model", agent.Model, + "provider", provider) + + // Set default model based on available providers + if setDefaultModelForAgent(name) { + logging.Info("set default model for agent", "agent", name, "model", cfg.Agents[name].Model) + } else { + return fmt.Errorf("no valid provider available for agent %s", name) } + } - // Validate max tokens - if agent.MaxTokens <= 0 { - logging.Warn("invalid max tokens, setting to default", - "agent", name, - "model", agent.Model, - "max_tokens", agent.MaxTokens) + // Validate max tokens + if agent.MaxTokens <= 0 { + logging.Warn("invalid max tokens, setting to default", + "agent", name, + "model", agent.Model, + "max_tokens", agent.MaxTokens) - // Update the agent with default max tokens - updatedAgent := cfg.Agents[name] - if model.DefaultMaxTokens > 0 { - updatedAgent.MaxTokens = model.DefaultMaxTokens - } else { - updatedAgent.MaxTokens = 4096 // Fallback default - } - cfg.Agents[name] = updatedAgent - } else if model.ContextWindow > 0 && agent.MaxTokens > model.ContextWindow/2 { - // Ensure max tokens doesn't exceed half the context window (reasonable limit) - logging.Warn("max tokens exceeds half the context window, adjusting", + // Update the agent with default max tokens + updatedAgent := cfg.Agents[name] + if model.DefaultMaxTokens > 0 { + updatedAgent.MaxTokens = model.DefaultMaxTokens + } else { + updatedAgent.MaxTokens = MaxTokensFallbackDefault + } + cfg.Agents[name] = updatedAgent + } else if model.ContextWindow > 0 && agent.MaxTokens > model.ContextWindow/2 { + // Ensure max tokens doesn't exceed half the context window (reasonable limit) + logging.Warn("max tokens exceeds half the context window, adjusting", + "agent", name, + "model", agent.Model, + "max_tokens", agent.MaxTokens, + "context_window", model.ContextWindow) + + // Update the agent with adjusted max tokens + updatedAgent := cfg.Agents[name] + updatedAgent.MaxTokens = model.ContextWindow / 2 + cfg.Agents[name] = updatedAgent + } + + // Validate reasoning effort for models that support reasoning + if model.CanReason && provider == models.ProviderOpenAI { + if agent.ReasoningEffort == "" { + // Set default reasoning effort for models that support it + logging.Info("setting default reasoning effort for model that supports reasoning", "agent", name, - "model", agent.Model, - "max_tokens", agent.MaxTokens, - "context_window", model.ContextWindow) + "model", agent.Model) - // Update the agent with adjusted max tokens + // Update the agent with default reasoning effort updatedAgent := cfg.Agents[name] - updatedAgent.MaxTokens = model.ContextWindow / 2 + updatedAgent.ReasoningEffort = "medium" cfg.Agents[name] = updatedAgent - } - - // Validate reasoning effort for models that support reasoning - if model.CanReason && provider == models.ProviderOpenAI { - if agent.ReasoningEffort == "" { - // Set default reasoning effort for models that support it - logging.Info("setting default reasoning effort for model that supports reasoning", + } else { + // Check if reasoning effort is valid (low, medium, high) + effort := strings.ToLower(agent.ReasoningEffort) + if effort != "low" && effort != "medium" && effort != "high" { + logging.Warn("invalid reasoning effort, setting to medium", "agent", name, - "model", agent.Model) + "model", agent.Model, + "reasoning_effort", agent.ReasoningEffort) - // Update the agent with default reasoning effort + // Update the agent with valid reasoning effort updatedAgent := cfg.Agents[name] updatedAgent.ReasoningEffort = "medium" cfg.Agents[name] = updatedAgent - } else { - // Check if reasoning effort is valid (low, medium, high) - effort := strings.ToLower(agent.ReasoningEffort) - if effort != "low" && effort != "medium" && effort != "high" { - logging.Warn("invalid reasoning effort, setting to medium", - "agent", name, - "model", agent.Model, - "reasoning_effort", agent.ReasoningEffort) - - // Update the agent with valid reasoning effort - updatedAgent := cfg.Agents[name] - updatedAgent.ReasoningEffort = "medium" - cfg.Agents[name] = updatedAgent - } } - } else if !model.CanReason && agent.ReasoningEffort != "" { - // Model doesn't support reasoning but reasoning effort is set - logging.Warn("model doesn't support reasoning but reasoning effort is set, ignoring", - "agent", name, - "model", agent.Model, - "reasoning_effort", agent.ReasoningEffort) + } + } else if !model.CanReason && agent.ReasoningEffort != "" { + // Model doesn't support reasoning but reasoning effort is set + logging.Warn("model doesn't support reasoning but reasoning effort is set, ignoring", + "agent", name, + "model", agent.Model, + "reasoning_effort", agent.ReasoningEffort) - // Update the agent to remove reasoning effort - updatedAgent := cfg.Agents[name] - updatedAgent.ReasoningEffort = "" - cfg.Agents[name] = updatedAgent + // Update the agent to remove reasoning effort + updatedAgent := cfg.Agents[name] + updatedAgent.ReasoningEffort = "" + cfg.Agents[name] = updatedAgent + } + + return nil +} + +// Validate checks if the configuration is valid and applies defaults where needed. +func Validate() error { + if cfg == nil { + return fmt.Errorf("config not loaded") + } + + // Validate agent models + for name, agent := range cfg.Agents { + if err := validateAgent(cfg, name, agent); err != nil { + return err } } @@ -629,3 +639,36 @@ func WorkingDirectory() string { } return cfg.WorkingDir } + +func UpdateAgentModel(agentName AgentName, modelID models.ModelID) error { + if cfg == nil { + panic("config not loaded") + } + + existingAgentCfg := cfg.Agents[agentName] + + model, ok := models.SupportedModels[modelID] + if !ok { + return fmt.Errorf("model %s not supported", modelID) + } + + maxTokens := existingAgentCfg.MaxTokens + if model.DefaultMaxTokens > 0 { + maxTokens = model.DefaultMaxTokens + } + + newAgentCfg := Agent{ + Model: modelID, + MaxTokens: maxTokens, + ReasoningEffort: existingAgentCfg.ReasoningEffort, + } + cfg.Agents[agentName] = newAgentCfg + + if err := validateAgent(cfg, agentName, newAgentCfg); err != nil { + // revert config update on failure + cfg.Agents[agentName] = existingAgentCfg + return fmt.Errorf("failed to update agent model: %w", err) + } + + return nil +} |
