|
@@ -11,62 +11,13 @@ import (
|
|
|
"strings"
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/spf13/viper"
|
|
"github.com/spf13/viper"
|
|
|
- "github.com/sst/opencode/internal/llm/models"
|
|
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
-// MCPType defines the type of MCP (Model Control Protocol) server.
|
|
|
|
|
-type MCPType string
|
|
|
|
|
-
|
|
|
|
|
-// Supported MCP types
|
|
|
|
|
-const (
|
|
|
|
|
- MCPStdio MCPType = "stdio"
|
|
|
|
|
- MCPSse MCPType = "sse"
|
|
|
|
|
-)
|
|
|
|
|
-
|
|
|
|
|
-// MCPServer defines the configuration for a Model Control Protocol server.
|
|
|
|
|
-type MCPServer struct {
|
|
|
|
|
- Command string `json:"command"`
|
|
|
|
|
- Env []string `json:"env"`
|
|
|
|
|
- Args []string `json:"args"`
|
|
|
|
|
- Type MCPType `json:"type"`
|
|
|
|
|
- URL string `json:"url"`
|
|
|
|
|
- Headers map[string]string `json:"headers"`
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-type AgentName string
|
|
|
|
|
-
|
|
|
|
|
-const (
|
|
|
|
|
- AgentPrimary AgentName = "primary"
|
|
|
|
|
- AgentTask AgentName = "task"
|
|
|
|
|
- AgentTitle AgentName = "title"
|
|
|
|
|
-)
|
|
|
|
|
-
|
|
|
|
|
-// Agent defines configuration for different LLM models and their token limits.
|
|
|
|
|
-type Agent struct {
|
|
|
|
|
- Model models.ModelID `json:"model"`
|
|
|
|
|
- MaxTokens int64 `json:"maxTokens"`
|
|
|
|
|
- ReasoningEffort string `json:"reasoningEffort"` // For openai models low,medium,heigh
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// Provider defines configuration for an LLM provider.
|
|
|
|
|
-type Provider struct {
|
|
|
|
|
- APIKey string `json:"apiKey"`
|
|
|
|
|
- Disabled bool `json:"disabled"`
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
// Data defines storage configuration.
|
|
// Data defines storage configuration.
|
|
|
type Data struct {
|
|
type Data struct {
|
|
|
Directory string `json:"directory,omitempty"`
|
|
Directory string `json:"directory,omitempty"`
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// LSPConfig defines configuration for Language Server Protocol integration.
|
|
|
|
|
-type LSPConfig struct {
|
|
|
|
|
- Disabled bool `json:"enabled"`
|
|
|
|
|
- Command string `json:"command"`
|
|
|
|
|
- Args []string `json:"args"`
|
|
|
|
|
- Options any `json:"options"`
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
// TUIConfig defines the configuration for the Terminal User Interface.
|
|
// TUIConfig defines the configuration for the Terminal User Interface.
|
|
|
type TUIConfig struct {
|
|
type TUIConfig struct {
|
|
|
Theme string `json:"theme,omitempty"`
|
|
Theme string `json:"theme,omitempty"`
|
|
@@ -81,17 +32,13 @@ type ShellConfig struct {
|
|
|
|
|
|
|
|
// Config is the main configuration structure for the application.
|
|
// Config is the main configuration structure for the application.
|
|
|
type Config struct {
|
|
type Config struct {
|
|
|
- Data Data `json:"data"`
|
|
|
|
|
- WorkingDir string `json:"wd,omitempty"`
|
|
|
|
|
- MCPServers map[string]MCPServer `json:"mcpServers,omitempty"`
|
|
|
|
|
- Providers map[models.ModelProvider]Provider `json:"providers,omitempty"`
|
|
|
|
|
- LSP map[string]LSPConfig `json:"lsp,omitempty"`
|
|
|
|
|
- Agents map[AgentName]Agent `json:"agents,omitempty"`
|
|
|
|
|
- Debug bool `json:"debug,omitempty"`
|
|
|
|
|
- DebugLSP bool `json:"debugLSP,omitempty"`
|
|
|
|
|
- ContextPaths []string `json:"contextPaths,omitempty"`
|
|
|
|
|
- TUI TUIConfig `json:"tui"`
|
|
|
|
|
- Shell ShellConfig `json:"shell,omitempty"`
|
|
|
|
|
|
|
+ Data Data `json:"data"`
|
|
|
|
|
+ WorkingDir string `json:"wd,omitempty"`
|
|
|
|
|
+ Debug bool `json:"debug,omitempty"`
|
|
|
|
|
+ DebugLSP bool `json:"debugLSP,omitempty"`
|
|
|
|
|
+ ContextPaths []string `json:"contextPaths,omitempty"`
|
|
|
|
|
+ TUI TUIConfig `json:"tui"`
|
|
|
|
|
+ Shell ShellConfig `json:"shell,omitempty"`
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Application constants
|
|
// Application constants
|
|
@@ -132,9 +79,6 @@ func Load(workingDir string, debug bool) (*Config, error) {
|
|
|
|
|
|
|
|
cfg = &Config{
|
|
cfg = &Config{
|
|
|
WorkingDir: workingDir,
|
|
WorkingDir: workingDir,
|
|
|
- MCPServers: make(map[string]MCPServer),
|
|
|
|
|
- Providers: make(map[models.ModelProvider]Provider),
|
|
|
|
|
- LSP: make(map[string]LSPConfig),
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
configureViper()
|
|
configureViper()
|
|
@@ -148,15 +92,11 @@ func Load(workingDir string, debug bool) (*Config, error) {
|
|
|
// Load and merge local config
|
|
// Load and merge local config
|
|
|
mergeLocalConfig(workingDir)
|
|
mergeLocalConfig(workingDir)
|
|
|
|
|
|
|
|
- setProviderDefaults()
|
|
|
|
|
-
|
|
|
|
|
// Apply configuration to the struct
|
|
// Apply configuration to the struct
|
|
|
if err := viper.Unmarshal(cfg); err != nil {
|
|
if err := viper.Unmarshal(cfg); err != nil {
|
|
|
return cfg, fmt.Errorf("failed to unmarshal config: %w", err)
|
|
return cfg, fmt.Errorf("failed to unmarshal config: %w", err)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- applyDefaultValues()
|
|
|
|
|
-
|
|
|
|
|
defaultLevel := slog.LevelInfo
|
|
defaultLevel := slog.LevelInfo
|
|
|
if cfg.Debug {
|
|
if cfg.Debug {
|
|
|
defaultLevel = slog.LevelDebug
|
|
defaultLevel = slog.LevelDebug
|
|
@@ -167,16 +107,6 @@ func Load(workingDir string, debug bool) (*Config, error) {
|
|
|
if err := Validate(); err != nil {
|
|
if err := Validate(); err != nil {
|
|
|
return cfg, fmt.Errorf("config validation failed: %w", err)
|
|
return cfg, fmt.Errorf("config validation failed: %w", err)
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- if cfg.Agents == nil {
|
|
|
|
|
- cfg.Agents = make(map[AgentName]Agent)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Override the max tokens for title agent
|
|
|
|
|
- cfg.Agents[AgentTitle] = Agent{
|
|
|
|
|
- Model: cfg.Agents[AgentTitle].Model,
|
|
|
|
|
- MaxTokens: 80,
|
|
|
|
|
- }
|
|
|
|
|
return cfg, nil
|
|
return cfg, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -206,156 +136,6 @@ func setDefaults(debug bool) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// setProviderDefaults configures LLM provider defaults based on provider provided by
|
|
|
|
|
-// environment variables and configuration file.
|
|
|
|
|
-func setProviderDefaults() {
|
|
|
|
|
- // Set all API keys we can find in the environment
|
|
|
|
|
- if apiKey := os.Getenv("ANTHROPIC_API_KEY"); apiKey != "" {
|
|
|
|
|
- viper.SetDefault("providers.anthropic.apiKey", apiKey)
|
|
|
|
|
- }
|
|
|
|
|
- if apiKey := os.Getenv("OPENAI_API_KEY"); apiKey != "" {
|
|
|
|
|
- viper.SetDefault("providers.openai.apiKey", apiKey)
|
|
|
|
|
- }
|
|
|
|
|
- if apiKey := os.Getenv("GEMINI_API_KEY"); apiKey != "" {
|
|
|
|
|
- viper.SetDefault("providers.gemini.apiKey", apiKey)
|
|
|
|
|
- }
|
|
|
|
|
- if apiKey := os.Getenv("GROQ_API_KEY"); apiKey != "" {
|
|
|
|
|
- viper.SetDefault("providers.groq.apiKey", apiKey)
|
|
|
|
|
- }
|
|
|
|
|
- if apiKey := os.Getenv("OPENROUTER_API_KEY"); apiKey != "" {
|
|
|
|
|
- viper.SetDefault("providers.openrouter.apiKey", apiKey)
|
|
|
|
|
- }
|
|
|
|
|
- if apiKey := os.Getenv("XAI_API_KEY"); apiKey != "" {
|
|
|
|
|
- viper.SetDefault("providers.xai.apiKey", apiKey)
|
|
|
|
|
- }
|
|
|
|
|
- if apiKey := os.Getenv("AZURE_OPENAI_ENDPOINT"); apiKey != "" {
|
|
|
|
|
- // api-key may be empty when using Entra ID credentials – that's okay
|
|
|
|
|
- viper.SetDefault("providers.azure.apiKey", os.Getenv("AZURE_OPENAI_API_KEY"))
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Use this order to set the default models
|
|
|
|
|
- // 1. Anthropic
|
|
|
|
|
- // 2. OpenAI
|
|
|
|
|
- // 3. Google Gemini
|
|
|
|
|
- // 4. Groq
|
|
|
|
|
- // 5. OpenRouter
|
|
|
|
|
- // 6. AWS Bedrock
|
|
|
|
|
- // 7. Azure
|
|
|
|
|
- // 8. Google Cloud VertexAI
|
|
|
|
|
-
|
|
|
|
|
- // Anthropic configuration
|
|
|
|
|
- if key := viper.GetString("providers.anthropic.apiKey"); strings.TrimSpace(key) != "" {
|
|
|
|
|
- viper.SetDefault("agents.primary.model", models.Claude4Sonnet)
|
|
|
|
|
- viper.SetDefault("agents.task.model", models.Claude4Sonnet)
|
|
|
|
|
- viper.SetDefault("agents.title.model", models.Claude4Sonnet)
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // OpenAI configuration
|
|
|
|
|
- if key := viper.GetString("providers.openai.apiKey"); strings.TrimSpace(key) != "" {
|
|
|
|
|
- viper.SetDefault("agents.primary.model", models.GPT41)
|
|
|
|
|
- viper.SetDefault("agents.task.model", models.GPT41Mini)
|
|
|
|
|
- viper.SetDefault("agents.title.model", models.GPT41Mini)
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Google Gemini configuration
|
|
|
|
|
- if key := viper.GetString("providers.gemini.apiKey"); strings.TrimSpace(key) != "" {
|
|
|
|
|
- viper.SetDefault("agents.primary.model", models.Gemini25)
|
|
|
|
|
- viper.SetDefault("agents.task.model", models.Gemini25Flash)
|
|
|
|
|
- viper.SetDefault("agents.title.model", models.Gemini25Flash)
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Groq configuration
|
|
|
|
|
- if key := viper.GetString("providers.groq.apiKey"); strings.TrimSpace(key) != "" {
|
|
|
|
|
- viper.SetDefault("agents.primary.model", models.QWENQwq)
|
|
|
|
|
- viper.SetDefault("agents.task.model", models.QWENQwq)
|
|
|
|
|
- viper.SetDefault("agents.title.model", models.QWENQwq)
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // OpenRouter configuration
|
|
|
|
|
- if key := viper.GetString("providers.openrouter.apiKey"); strings.TrimSpace(key) != "" {
|
|
|
|
|
- viper.SetDefault("agents.primary.model", models.OpenRouterClaude37Sonnet)
|
|
|
|
|
- viper.SetDefault("agents.task.model", models.OpenRouterClaude37Sonnet)
|
|
|
|
|
- viper.SetDefault("agents.title.model", models.OpenRouterClaude35Haiku)
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // XAI configuration
|
|
|
|
|
- if key := viper.GetString("providers.xai.apiKey"); strings.TrimSpace(key) != "" {
|
|
|
|
|
- viper.SetDefault("agents.primary.model", models.XAIGrok3Beta)
|
|
|
|
|
- viper.SetDefault("agents.task.model", models.XAIGrok3Beta)
|
|
|
|
|
- viper.SetDefault("agents.title.model", models.XAiGrok3MiniFastBeta)
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // AWS Bedrock configuration
|
|
|
|
|
- if hasAWSCredentials() {
|
|
|
|
|
- viper.SetDefault("agents.primary.model", models.BedrockClaude37Sonnet)
|
|
|
|
|
- viper.SetDefault("agents.task.model", models.BedrockClaude37Sonnet)
|
|
|
|
|
- viper.SetDefault("agents.title.model", models.BedrockClaude37Sonnet)
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Azure OpenAI configuration
|
|
|
|
|
- if os.Getenv("AZURE_OPENAI_ENDPOINT") != "" {
|
|
|
|
|
- viper.SetDefault("agents.primary.model", models.AzureGPT41)
|
|
|
|
|
- viper.SetDefault("agents.task.model", models.AzureGPT41Mini)
|
|
|
|
|
- viper.SetDefault("agents.title.model", models.AzureGPT41Mini)
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Google Cloud VertexAI configuration
|
|
|
|
|
- if hasVertexAICredentials() {
|
|
|
|
|
- viper.SetDefault("agents.coder.model", models.VertexAIGemini25)
|
|
|
|
|
- viper.SetDefault("agents.summarizer.model", models.VertexAIGemini25)
|
|
|
|
|
- viper.SetDefault("agents.task.model", models.VertexAIGemini25Flash)
|
|
|
|
|
- viper.SetDefault("agents.title.model", models.VertexAIGemini25Flash)
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// hasAWSCredentials checks if AWS credentials are available in the environment.
|
|
|
|
|
-func hasAWSCredentials() bool {
|
|
|
|
|
- // Check for explicit AWS credentials
|
|
|
|
|
- if os.Getenv("AWS_ACCESS_KEY_ID") != "" && os.Getenv("AWS_SECRET_ACCESS_KEY") != "" {
|
|
|
|
|
- return true
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Check for AWS profile
|
|
|
|
|
- if os.Getenv("AWS_PROFILE") != "" || os.Getenv("AWS_DEFAULT_PROFILE") != "" {
|
|
|
|
|
- return true
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Check for AWS region
|
|
|
|
|
- if os.Getenv("AWS_REGION") != "" || os.Getenv("AWS_DEFAULT_REGION") != "" {
|
|
|
|
|
- return true
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Check if running on EC2 with instance profile
|
|
|
|
|
- if os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") != "" ||
|
|
|
|
|
- os.Getenv("AWS_CONTAINER_CREDENTIALS_FULL_URI") != "" {
|
|
|
|
|
- return true
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return false
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// hasVertexAICredentials checks if VertexAI credentials are available in the environment.
|
|
|
|
|
-func hasVertexAICredentials() bool {
|
|
|
|
|
- // Check for explicit VertexAI parameters
|
|
|
|
|
- if os.Getenv("VERTEXAI_PROJECT") != "" && os.Getenv("VERTEXAI_LOCATION") != "" {
|
|
|
|
|
- return true
|
|
|
|
|
- }
|
|
|
|
|
- // Check for Google Cloud project and location
|
|
|
|
|
- if os.Getenv("GOOGLE_CLOUD_PROJECT") != "" && (os.Getenv("GOOGLE_CLOUD_REGION") != "" || os.Getenv("GOOGLE_CLOUD_LOCATION") != "") {
|
|
|
|
|
- return true
|
|
|
|
|
- }
|
|
|
|
|
- return false
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
// readConfig handles the result of reading a configuration file.
|
|
// readConfig handles the result of reading a configuration file.
|
|
|
func readConfig(err error) error {
|
|
func readConfig(err error) error {
|
|
|
if err == nil {
|
|
if err == nil {
|
|
@@ -383,346 +163,15 @@ func mergeLocalConfig(workingDir string) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// applyDefaultValues sets default values for configuration fields that need processing.
|
|
|
|
|
-func applyDefaultValues() {
|
|
|
|
|
- // Set default MCP type if not specified
|
|
|
|
|
- for k, v := range cfg.MCPServers {
|
|
|
|
|
- if v.Type == "" {
|
|
|
|
|
- v.Type = MCPStdio
|
|
|
|
|
- cfg.MCPServers[k] = v
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// It validates model IDs and providers, ensuring they are supported.
|
|
|
|
|
-func validateAgent(cfg *Config, name AgentName, agent Agent) error {
|
|
|
|
|
- // Check if model exists
|
|
|
|
|
- model, modelExists := models.SupportedModels[agent.Model]
|
|
|
|
|
- if !modelExists {
|
|
|
|
|
- slog.Warn("unsupported model configured, reverting to default",
|
|
|
|
|
- "agent", name,
|
|
|
|
|
- "configured_model", agent.Model)
|
|
|
|
|
-
|
|
|
|
|
- // Set default model based on available providers
|
|
|
|
|
- if setDefaultModelForAgent(name) {
|
|
|
|
|
- slog.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]
|
|
|
|
|
-
|
|
|
|
|
- if !providerExists {
|
|
|
|
|
- // Provider not configured, check if we have environment variables
|
|
|
|
|
- apiKey := getProviderAPIKey(provider)
|
|
|
|
|
- if apiKey == "" {
|
|
|
|
|
- slog.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) {
|
|
|
|
|
- slog.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,
|
|
|
|
|
- }
|
|
|
|
|
- slog.Info("added provider from environment", "provider", provider)
|
|
|
|
|
- }
|
|
|
|
|
- } else if providerCfg.Disabled || providerCfg.APIKey == "" {
|
|
|
|
|
- // Provider is disabled or has no API key
|
|
|
|
|
- slog.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) {
|
|
|
|
|
- slog.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 {
|
|
|
|
|
- slog.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 = 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)
|
|
|
|
|
- slog.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
|
|
|
|
|
- slog.Info("setting default reasoning effort for model that supports reasoning",
|
|
|
|
|
- "agent", name,
|
|
|
|
|
- "model", agent.Model)
|
|
|
|
|
-
|
|
|
|
|
- // Update the agent with default 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" {
|
|
|
|
|
- slog.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
|
|
|
|
|
- slog.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
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return nil
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
// Validate checks if the configuration is valid and applies defaults where needed.
|
|
// Validate checks if the configuration is valid and applies defaults where needed.
|
|
|
func Validate() error {
|
|
func Validate() error {
|
|
|
if cfg == nil {
|
|
if cfg == nil {
|
|
|
return fmt.Errorf("config not loaded")
|
|
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
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Validate providers
|
|
|
|
|
- for provider, providerCfg := range cfg.Providers {
|
|
|
|
|
- if providerCfg.APIKey == "" && !providerCfg.Disabled {
|
|
|
|
|
- slog.Warn("provider has no API key, marking as disabled", "provider", provider)
|
|
|
|
|
- providerCfg.Disabled = true
|
|
|
|
|
- cfg.Providers[provider] = providerCfg
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Validate LSP configurations
|
|
|
|
|
- for language, lspConfig := range cfg.LSP {
|
|
|
|
|
- if lspConfig.Command == "" && !lspConfig.Disabled {
|
|
|
|
|
- slog.Warn("LSP configuration has no command, marking as disabled", "language", language)
|
|
|
|
|
- lspConfig.Disabled = true
|
|
|
|
|
- cfg.LSP[language] = lspConfig
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// getProviderAPIKey gets the API key for a provider from environment variables
|
|
|
|
|
-func getProviderAPIKey(provider models.ModelProvider) string {
|
|
|
|
|
- switch provider {
|
|
|
|
|
- case models.ProviderAnthropic:
|
|
|
|
|
- return os.Getenv("ANTHROPIC_API_KEY")
|
|
|
|
|
- case models.ProviderOpenAI:
|
|
|
|
|
- return os.Getenv("OPENAI_API_KEY")
|
|
|
|
|
- case models.ProviderGemini:
|
|
|
|
|
- return os.Getenv("GEMINI_API_KEY")
|
|
|
|
|
- case models.ProviderGROQ:
|
|
|
|
|
- return os.Getenv("GROQ_API_KEY")
|
|
|
|
|
- case models.ProviderAzure:
|
|
|
|
|
- return os.Getenv("AZURE_OPENAI_API_KEY")
|
|
|
|
|
- case models.ProviderOpenRouter:
|
|
|
|
|
- return os.Getenv("OPENROUTER_API_KEY")
|
|
|
|
|
- case models.ProviderBedrock:
|
|
|
|
|
- if hasAWSCredentials() {
|
|
|
|
|
- return "aws-credentials-available"
|
|
|
|
|
- }
|
|
|
|
|
- case models.ProviderVertexAI:
|
|
|
|
|
- if hasVertexAICredentials() {
|
|
|
|
|
- return "vertex-ai-credentials-available"
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return ""
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// setDefaultModelForAgent sets a default model for an agent based on available providers
|
|
|
|
|
-func setDefaultModelForAgent(agent AgentName) bool {
|
|
|
|
|
- // Check providers in order of preference
|
|
|
|
|
- if apiKey := os.Getenv("ANTHROPIC_API_KEY"); apiKey != "" {
|
|
|
|
|
- maxTokens := int64(5000)
|
|
|
|
|
- if agent == AgentTitle {
|
|
|
|
|
- maxTokens = 80
|
|
|
|
|
- }
|
|
|
|
|
- cfg.Agents[agent] = Agent{
|
|
|
|
|
- Model: models.Claude4Sonnet,
|
|
|
|
|
- MaxTokens: maxTokens,
|
|
|
|
|
- }
|
|
|
|
|
- return true
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if apiKey := os.Getenv("OPENAI_API_KEY"); apiKey != "" {
|
|
|
|
|
- var model models.ModelID
|
|
|
|
|
- maxTokens := int64(5000)
|
|
|
|
|
- reasoningEffort := ""
|
|
|
|
|
-
|
|
|
|
|
- switch agent {
|
|
|
|
|
- case AgentTitle:
|
|
|
|
|
- model = models.GPT41Mini
|
|
|
|
|
- maxTokens = 80
|
|
|
|
|
- case AgentTask:
|
|
|
|
|
- model = models.GPT41Mini
|
|
|
|
|
- default:
|
|
|
|
|
- model = models.GPT41
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Check if model supports reasoning
|
|
|
|
|
- if modelInfo, ok := models.SupportedModels[model]; ok && modelInfo.CanReason {
|
|
|
|
|
- reasoningEffort = "medium"
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- cfg.Agents[agent] = Agent{
|
|
|
|
|
- Model: model,
|
|
|
|
|
- MaxTokens: maxTokens,
|
|
|
|
|
- ReasoningEffort: reasoningEffort,
|
|
|
|
|
- }
|
|
|
|
|
- return true
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if apiKey := os.Getenv("OPENROUTER_API_KEY"); apiKey != "" {
|
|
|
|
|
- var model models.ModelID
|
|
|
|
|
- maxTokens := int64(5000)
|
|
|
|
|
- reasoningEffort := ""
|
|
|
|
|
-
|
|
|
|
|
- switch agent {
|
|
|
|
|
- case AgentTitle:
|
|
|
|
|
- model = models.OpenRouterClaude35Haiku
|
|
|
|
|
- maxTokens = 80
|
|
|
|
|
- case AgentTask:
|
|
|
|
|
- model = models.OpenRouterClaude37Sonnet
|
|
|
|
|
- default:
|
|
|
|
|
- model = models.OpenRouterClaude37Sonnet
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Check if model supports reasoning
|
|
|
|
|
- if modelInfo, ok := models.SupportedModels[model]; ok && modelInfo.CanReason {
|
|
|
|
|
- reasoningEffort = "medium"
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- cfg.Agents[agent] = Agent{
|
|
|
|
|
- Model: model,
|
|
|
|
|
- MaxTokens: maxTokens,
|
|
|
|
|
- ReasoningEffort: reasoningEffort,
|
|
|
|
|
- }
|
|
|
|
|
- return true
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if apiKey := os.Getenv("GEMINI_API_KEY"); apiKey != "" {
|
|
|
|
|
- var model models.ModelID
|
|
|
|
|
- maxTokens := int64(5000)
|
|
|
|
|
-
|
|
|
|
|
- if agent == AgentTitle {
|
|
|
|
|
- model = models.Gemini25Flash
|
|
|
|
|
- maxTokens = 80
|
|
|
|
|
- } else {
|
|
|
|
|
- model = models.Gemini25
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- cfg.Agents[agent] = Agent{
|
|
|
|
|
- Model: model,
|
|
|
|
|
- MaxTokens: maxTokens,
|
|
|
|
|
- }
|
|
|
|
|
- return true
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if apiKey := os.Getenv("GROQ_API_KEY"); apiKey != "" {
|
|
|
|
|
- maxTokens := int64(5000)
|
|
|
|
|
- if agent == AgentTitle {
|
|
|
|
|
- maxTokens = 80
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- cfg.Agents[agent] = Agent{
|
|
|
|
|
- Model: models.QWENQwq,
|
|
|
|
|
- MaxTokens: maxTokens,
|
|
|
|
|
- }
|
|
|
|
|
- return true
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if hasAWSCredentials() {
|
|
|
|
|
- maxTokens := int64(5000)
|
|
|
|
|
- if agent == AgentTitle {
|
|
|
|
|
- maxTokens = 80
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- cfg.Agents[agent] = Agent{
|
|
|
|
|
- Model: models.BedrockClaude37Sonnet,
|
|
|
|
|
- MaxTokens: maxTokens,
|
|
|
|
|
- ReasoningEffort: "medium", // Claude models support reasoning
|
|
|
|
|
- }
|
|
|
|
|
- return true
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if hasVertexAICredentials() {
|
|
|
|
|
- var model models.ModelID
|
|
|
|
|
- maxTokens := int64(5000)
|
|
|
|
|
-
|
|
|
|
|
- if agent == AgentTitle {
|
|
|
|
|
- model = models.VertexAIGemini25Flash
|
|
|
|
|
- maxTokens = 80
|
|
|
|
|
- } else {
|
|
|
|
|
- model = models.VertexAIGemini25
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- cfg.Agents[agent] = Agent{
|
|
|
|
|
- Model: model,
|
|
|
|
|
- MaxTokens: maxTokens,
|
|
|
|
|
- }
|
|
|
|
|
- return true
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return false
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
// Get returns the current configuration.
|
|
// Get returns the current configuration.
|
|
|
// It's safe to call this function multiple times.
|
|
// It's safe to call this function multiple times.
|
|
|
func Get() *Config {
|
|
func Get() *Config {
|
|
@@ -801,44 +250,6 @@ func updateCfgFile(updateCfg func(config *Config)) error {
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-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 updateCfgFile(func(config *Config) {
|
|
|
|
|
- if config.Agents == nil {
|
|
|
|
|
- config.Agents = make(map[AgentName]Agent)
|
|
|
|
|
- }
|
|
|
|
|
- config.Agents[agentName] = newAgentCfg
|
|
|
|
|
- })
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
// UpdateTheme updates the theme in the configuration and writes it to the config file.
|
|
// UpdateTheme updates the theme in the configuration and writes it to the config file.
|
|
|
func UpdateTheme(themeName string) error {
|
|
func UpdateTheme(themeName string) error {
|
|
|
if cfg == nil {
|
|
if cfg == nil {
|