blob: 421e861ffe91b9bc57f0c7b33589780b847b4651 [file] [log] [blame]
package config
import (
"fmt"
"os"
"time"
"gopkg.in/yaml.v3"
)
// Config represents the Staff MVP configuration
type Config struct {
OpenAI OpenAIConfig `yaml:"openai"`
GitHub GitHubConfig `yaml:"github"`
Agents []AgentConfig `yaml:"agents"`
Tasks TasksConfig `yaml:"tasks"`
Git GitConfig `yaml:"git"`
}
// OpenAIConfig represents OpenAI provider configuration
type OpenAIConfig struct {
APIKey string `yaml:"api_key"`
Model string `yaml:"model"`
BaseURL string `yaml:"base_url"`
Timeout time.Duration `yaml:"timeout"`
MaxRetries int `yaml:"max_retries"`
}
// GitHubConfig represents GitHub integration configuration
type GitHubConfig struct {
Token string `yaml:"token"`
Owner string `yaml:"owner"`
Repo string `yaml:"repo"`
}
// AgentConfig represents individual agent configuration
type AgentConfig struct {
Name string `yaml:"name"`
Role string `yaml:"role"`
Model string `yaml:"model"`
SystemPromptFile string `yaml:"system_prompt_file"`
Capabilities []string `yaml:"capabilities"` // For auto-assignment
TaskTypes []string `yaml:"task_types"` // Types of tasks this agent handles
MaxTokens *int `yaml:"max_tokens"` // Model-specific token limits
Temperature *float64 `yaml:"temperature"` // Model creativity setting
}
// TasksConfig represents task management configuration
type TasksConfig struct {
StoragePath string `yaml:"storage_path"`
CompletedPath string `yaml:"completed_path"`
}
// GitConfig represents Git operation configuration
type GitConfig struct {
BranchPrefix string `yaml:"branch_prefix"`
CommitMessageTemplate string `yaml:"commit_message_template"`
PRTemplate string `yaml:"pr_template"`
}
// LoadConfig loads configuration from a YAML file
func LoadConfig(configPath string) (*Config, error) {
// Read the config file
data, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}
// Parse YAML
var config Config
if err := yaml.Unmarshal(data, &config); err != nil {
return nil, fmt.Errorf("failed to parse config YAML: %w", err)
}
// Apply defaults
config = applyDefaults(config)
// Validate configuration
if err := validateConfig(config); err != nil {
return nil, fmt.Errorf("invalid configuration: %w", err)
}
return &config, nil
}
// LoadConfigWithEnvOverrides loads config with environment variable overrides
func LoadConfigWithEnvOverrides(configPath string) (*Config, error) {
config, err := LoadConfig(configPath)
if err != nil {
return nil, err
}
// Override with environment variables if present
if apiKey := os.Getenv("OPENAI_API_KEY"); apiKey != "" {
config.OpenAI.APIKey = apiKey
}
if githubToken := os.Getenv("GITHUB_TOKEN"); githubToken != "" {
config.GitHub.Token = githubToken
}
if owner := os.Getenv("GITHUB_OWNER"); owner != "" {
config.GitHub.Owner = owner
}
if repo := os.Getenv("GITHUB_REPO"); repo != "" {
config.GitHub.Repo = repo
}
// Re-validate after env overrides
if err := validateConfig(*config); err != nil {
return nil, fmt.Errorf("invalid configuration after env overrides: %w", err)
}
return config, nil
}
// applyDefaults applies default values to configuration
func applyDefaults(config Config) Config {
// OpenAI defaults
if config.OpenAI.Model == "" {
config.OpenAI.Model = "gpt-4"
}
if config.OpenAI.BaseURL == "" {
config.OpenAI.BaseURL = "https://api.openai.com/v1"
}
if config.OpenAI.Timeout == 0 {
config.OpenAI.Timeout = 30 * time.Second
}
if config.OpenAI.MaxRetries == 0 {
config.OpenAI.MaxRetries = 3
}
// Tasks defaults
if config.Tasks.StoragePath == "" {
config.Tasks.StoragePath = "../operations/"
}
if config.Tasks.CompletedPath == "" {
config.Tasks.CompletedPath = "../operations/completed/"
}
// Git defaults
if config.Git.BranchPrefix == "" {
config.Git.BranchPrefix = "task/"
}
if config.Git.CommitMessageTemplate == "" {
config.Git.CommitMessageTemplate = "Task {task_id}: {task_title}\n\n{solution}\n\nGenerated by Staff AI Agent: {agent_name}"
}
if config.Git.PRTemplate == "" {
config.Git.PRTemplate = `## Task: {task_title}
**Task ID:** {task_id}
**Agent:** {agent_name}
**Priority:** {priority}
### Description
{task_description}
### Solution
{solution}
### Files Changed
{files_changed}
---
*Generated by Staff AI Multi-Agent System*`
}
// Agent defaults
for i := range config.Agents {
if config.Agents[i].Model == "" {
config.Agents[i].Model = config.OpenAI.Model
}
}
return config
}
// validateConfig validates the configuration
func validateConfig(config Config) error {
// Validate OpenAI config
if config.OpenAI.APIKey == "" {
return fmt.Errorf("openai.api_key is required")
}
if config.OpenAI.Model == "" {
return fmt.Errorf("openai.model is required")
}
// Validate GitHub config
if config.GitHub.Token == "" {
return fmt.Errorf("github.token is required")
}
if config.GitHub.Owner == "" {
return fmt.Errorf("github.owner is required")
}
if config.GitHub.Repo == "" {
return fmt.Errorf("github.repo is required")
}
// Validate agents
if len(config.Agents) == 0 {
return fmt.Errorf("at least one agent must be configured")
}
for i, agent := range config.Agents {
if agent.Name == "" {
return fmt.Errorf("agent[%d].name is required", i)
}
if agent.Role == "" {
return fmt.Errorf("agent[%d].role is required", i)
}
if agent.SystemPromptFile == "" {
return fmt.Errorf("agent[%d].system_prompt_file is required", i)
}
}
return nil
}
// GetAgentByName returns an agent config by name
func (c *Config) GetAgentByName(name string) (*AgentConfig, error) {
for _, agent := range c.Agents {
if agent.Name == name {
return &agent, nil
}
}
return nil, fmt.Errorf("agent not found: %s", name)
}
// ListAgentNames returns a list of all configured agent names
func (c *Config) ListAgentNames() []string {
names := make([]string, len(c.Agents))
for i, agent := range c.Agents {
names[i] = agent.Name
}
return names
}