package sidekick import ( "os" "strings" "time" "github.com/BurntSushi/toml" ) // ProjectConfig represents the root of the TOML configuration type ProjectConfig struct { ToolResponseThreshold int `toml:"tool_response_threshold"` MCPListener MCPListenerConfig `toml:"mcp_listener"` Models map[string]ModelConfig `toml:"models"` MCPServers map[string]MCPServerConfig `toml:"mcp_servers"` Agents map[string]AgentConfig `toml:"agents"` } // MCPListenerConfig configures the MCP server exposed by Sidekick type MCPListenerConfig struct { Transport string `toml:"transport"` // "stdio" or "http" Port int `toml:"port"` // used if transport is "http" } // ModelConfig holds configuration for the LLM type ModelConfig struct { ModelID string `toml:"model_id"` Endpoint string `toml:"endpoint"` APIKey string `toml:"api_key"` Temperature float32 `toml:"temperature"` MaxTokens int `toml:"max_tokens"` TimeoutSecs int `toml:"timeout_secs"` Timeout time.Duration `toml:"-"` } // MCPServerConfig configures a single MCP server connection type MCPServerConfig struct { Transport string `toml:"transport"` // "stdio", "sse", "http", "websocket" Command string `toml:"command"` // for stdio Args []string `toml:"args"` // for stdio Env []string `toml:"env"` // for stdio, format "KEY=VALUE" URL string `toml:"url"` // for HTTP/SSE/WebSocket AuthToken string `toml:"auth_token"` // for HTTP Bearer authentication Headers map[string]string `toml:"headers"` // for custom headers } // AgentConfig holds configuration for a specific agent instance type AgentConfig struct { ID string `toml:"-"` Role Role `toml:"role"` ModelID string `toml:"model_id"` Goals []string `toml:"goals"` SystemPrompt string `toml:"system_prompt"` Description string `toml:"description"` MaxIterations int `toml:"max_iterations"` Toolsets []string `toml:"toolsets"` // Allowed tool namespaces (MCP servers) Subagents []string `toml:"subagents"` // Allowed subagent IDs EnableShellExec bool `toml:"enable_shell_exec"` ToolResponseThreshold int `toml:"-"` } // resolveEnv checks if a string starts with "env:" and resolves it func resolveEnv(val string) string { if strings.HasPrefix(val, "env:") { envVar := strings.TrimPrefix(val, "env:") return os.Getenv(envVar) } return val } func (s *MCPServerConfig) Resolve() { s.URL = resolveEnv(s.URL) s.AuthToken = resolveEnv(s.AuthToken) for k, v := range s.Headers { s.Headers[k] = resolveEnv(v) } } // Resolve applies environment variable resolution to relevant fields func (m *ModelConfig) Resolve() { m.ModelID = resolveEnv(m.ModelID) m.Endpoint = resolveEnv(m.Endpoint) m.APIKey = resolveEnv(m.APIKey) if m.TimeoutSecs > 0 { m.Timeout = time.Duration(m.TimeoutSecs) * time.Second } if m.Timeout == 0 { m.Timeout = 120 * time.Second } } // LoadConfig parses a TOML configuration file into ProjectConfig func LoadConfig(path string) (*ProjectConfig, error) { var cfg ProjectConfig if _, err := toml.DecodeFile(path, &cfg); err != nil { return nil, err } if cfg.ToolResponseThreshold <= 0 { cfg.ToolResponseThreshold = 10000 } for k, m := range cfg.Models { m.Resolve() cfg.Models[k] = m } for k, s := range cfg.MCPServers { s.Resolve() cfg.MCPServers[k] = s } for k, a := range cfg.Agents { a.ID = k a.ToolResponseThreshold = cfg.ToolResponseThreshold cfg.Agents[k] = a } return &cfg, nil }