@@ -120,13 +120,11 @@ func Load(workingDir string, debug bool) (*Config, error) {
122 applyDefaultValues()
122 applyDefaultValues()
124 defaultLevel := slog.LevelInfo
123 defaultLevel := slog.LevelInfo
125 if cfg.Debug {
124 if cfg.Debug {
126 defaultLevel = slog.LevelDebug
125 defaultLevel = slog.LevelDebug
128− // if we are in debug mode make the writer a file
127+ if os.Getenv("OPENCODE_DEV_DEBUG") == "true" {
130 loggingFile := fmt.Sprintf("%s/%s", cfg.Data.Directory, "debug.log")
128 loggingFile := fmt.Sprintf("%s/%s", cfg.Data.Directory, "debug.log")
132 // if file does not exist create it
130 // if file does not exist create it
@@ -156,6 +154,11 @@ func Load(workingDir string, debug bool) (*Config, error) {
156 slog.SetDefault(logger)
154 slog.SetDefault(logger)
157+ // Validate configuration
158+ if err := Validate(); err != nil {
159+ return cfg, fmt.Errorf("config validation failed: %w", err)
159 if cfg.Agents == nil {
162 if cfg.Agents == nil {
160 cfg.Agents = make(map[AgentName]Agent)
163 cfg.Agents = make(map[AgentName]Agent)
@@ -302,6 +305,273 @@ func applyDefaultValues() {
308+// Validate checks if the configuration is valid and applies defaults where needed.
309+// It validates model IDs and providers, ensuring they are supported.
310+func Validate() error {
312+ return fmt.Errorf("config not loaded")
315+ // Validate agent models
316+ for name, agent := range cfg.Agents {
317+ // Check if model exists
318+ model, modelExists := models.SupportedModels[agent.Model]
320+ logging.Warn("unsupported model configured, reverting to default",
322+ "configured_model", agent.Model)
324+ // Set default model based on available providers
325+ if setDefaultModelForAgent(name) {
326+ logging.Info("set default model for agent", "agent", name, "model", cfg.Agents[name].Model)
328+ return fmt.Errorf("no valid provider available for agent %s", name)
333+ // Check if provider for the model is configured
334+ provider := model.Provider
335+ providerCfg, providerExists := cfg.Providers[provider]
337+ if !providerExists {
338+ // Provider not configured, check if we have environment variables
339+ apiKey := getProviderAPIKey(provider)
341+ logging.Warn("provider not configured for model, reverting to default",
343+ "model", agent.Model,
344+ "provider", provider)
346+ // Set default model based on available providers
347+ if setDefaultModelForAgent(name) {
348+ logging.Info("set default model for agent", "agent", name, "model", cfg.Agents[name].Model)
350+ return fmt.Errorf("no valid provider available for agent %s", name)
353+ // Add provider with API key from environment
354+ cfg.Providers[provider] = Provider{
357+ logging.Info("added provider from environment", "provider", provider)
359+ } else if providerCfg.Disabled || providerCfg.APIKey == "" {
360+ // Provider is disabled or has no API key
361+ logging.Warn("provider is disabled or has no API key, reverting to default",
363+ "model", agent.Model,
364+ "provider", provider)
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)
370+ return fmt.Errorf("no valid provider available for agent %s", name)
374+ // Validate max tokens
375+ if agent.MaxTokens <= 0 {
376+ logging.Warn("invalid max tokens, setting to default",
378+ "model", agent.Model,
379+ "max_tokens", agent.MaxTokens)
381+ // Update the agent with default max tokens
382+ updatedAgent := cfg.Agents[name]
383+ if model.DefaultMaxTokens > 0 {
384+ updatedAgent.MaxTokens = model.DefaultMaxTokens
386+ updatedAgent.MaxTokens = 4096 // Fallback default
388+ cfg.Agents[name] = updatedAgent
389+ } else if model.ContextWindow > 0 && agent.MaxTokens > model.ContextWindow/2 {
390+ // Ensure max tokens doesn't exceed half the context window (reasonable limit)
391+ logging.Warn("max tokens exceeds half the context window, adjusting",
393+ "model", agent.Model,
394+ "max_tokens", agent.MaxTokens,
395+ "context_window", model.ContextWindow)
397+ // Update the agent with adjusted max tokens
398+ updatedAgent := cfg.Agents[name]
399+ updatedAgent.MaxTokens = model.ContextWindow / 2
400+ cfg.Agents[name] = updatedAgent
403+ // Validate reasoning effort for models that support reasoning
404+ if model.CanReason && provider == models.ProviderOpenAI {
405+ if agent.ReasoningEffort == "" {
406+ // Set default reasoning effort for models that support it
407+ logging.Info("setting default reasoning effort for model that supports reasoning",
409+ "model", agent.Model)
411+ // Update the agent with default reasoning effort
412+ updatedAgent := cfg.Agents[name]
413+ updatedAgent.ReasoningEffort = "medium"
414+ cfg.Agents[name] = updatedAgent
416+ // Check if reasoning effort is valid (low, medium, high)
417+ effort := strings.ToLower(agent.ReasoningEffort)
418+ if effort != "low" && effort != "medium" && effort != "high" {
419+ logging.Warn("invalid reasoning effort, setting to medium",
421+ "model", agent.Model,
422+ "reasoning_effort", agent.ReasoningEffort)
424+ // Update the agent with valid reasoning effort
425+ updatedAgent := cfg.Agents[name]
426+ updatedAgent.ReasoningEffort = "medium"
427+ cfg.Agents[name] = updatedAgent
430+ } else if !model.CanReason && agent.ReasoningEffort != "" {
431+ // Model doesn't support reasoning but reasoning effort is set
432+ logging.Warn("model doesn't support reasoning but reasoning effort is set, ignoring",
434+ "model", agent.Model,
435+ "reasoning_effort", agent.ReasoningEffort)
437+ // Update the agent to remove reasoning effort
438+ updatedAgent := cfg.Agents[name]
439+ updatedAgent.ReasoningEffort = ""
440+ cfg.Agents[name] = updatedAgent
444+ // Validate providers
445+ for provider, providerCfg := range cfg.Providers {
446+ if providerCfg.APIKey == "" && !providerCfg.Disabled {
447+ logging.Warn("provider has no API key, marking as disabled", "provider", provider)
448+ providerCfg.Disabled = true
449+ cfg.Providers[provider] = providerCfg
453+ // Validate LSP configurations
454+ for language, lspConfig := range cfg.LSP {
455+ if lspConfig.Command == "" && !lspConfig.Disabled {
456+ logging.Warn("LSP configuration has no command, marking as disabled", "language", language)
457+ lspConfig.Disabled = true
458+ cfg.LSP[language] = lspConfig
465+// getProviderAPIKey gets the API key for a provider from environment variables
466+func getProviderAPIKey(provider models.ModelProvider) string {
468+ case models.ProviderAnthropic:
469+ return os.Getenv("ANTHROPIC_API_KEY")
470+ case models.ProviderOpenAI:
471+ return os.Getenv("OPENAI_API_KEY")
472+ case models.ProviderGemini:
473+ return os.Getenv("GEMINI_API_KEY")
474+ case models.ProviderGROQ:
475+ return os.Getenv("GROQ_API_KEY")
476+ case models.ProviderBedrock:
477+ if hasAWSCredentials() {
478+ return "aws-credentials-available"
484+// setDefaultModelForAgent sets a default model for an agent based on available providers
485+func setDefaultModelForAgent(agent AgentName) bool {
486+ // Check providers in order of preference
487+ if apiKey := os.Getenv("ANTHROPIC_API_KEY"); apiKey != "" {
488+ maxTokens := int64(5000)
489+ if agent == AgentTitle {
492+ cfg.Agents[agent] = Agent{
493+ Model: models.Claude37Sonnet,
494+ MaxTokens: maxTokens,
499+ if apiKey := os.Getenv("OPENAI_API_KEY"); apiKey != "" {
500+ var model models.ModelID
501+ maxTokens := int64(5000)
502+ reasoningEffort := ""
506+ model = models.GPT41Mini
509+ model = models.GPT41Mini
511+ model = models.GPT41
514+ // Check if model supports reasoning
515+ if modelInfo, ok := models.SupportedModels[model]; ok && modelInfo.CanReason {
516+ reasoningEffort = "medium"
519+ cfg.Agents[agent] = Agent{
521+ MaxTokens: maxTokens,
522+ ReasoningEffort: reasoningEffort,
527+ if apiKey := os.Getenv("GEMINI_API_KEY"); apiKey != "" {
528+ var model models.ModelID
529+ maxTokens := int64(5000)
531+ if agent == AgentTitle {
532+ model = models.Gemini25Flash
535+ model = models.Gemini25
538+ cfg.Agents[agent] = Agent{
540+ MaxTokens: maxTokens,
545+ if apiKey := os.Getenv("GROQ_API_KEY"); apiKey != "" {
546+ maxTokens := int64(5000)
547+ if agent == AgentTitle {
551+ cfg.Agents[agent] = Agent{
552+ Model: models.QWENQwq,
553+ MaxTokens: maxTokens,
558+ if hasAWSCredentials() {
559+ maxTokens := int64(5000)
560+ if agent == AgentTitle {
564+ cfg.Agents[agent] = Agent{
565+ Model: models.BedrockClaude37Sonnet,
566+ MaxTokens: maxTokens,
567+ ReasoningEffort: "medium", // Claude models support reasoning
305// Get returns the current configuration.
575// Get returns the current configuration.
306// It's safe to call this function multiple times.
576// It's safe to call this function multiple times.
307func Get() *Config {
577func Get() *Config {