blob: c51bd43916885c97ee26085aba551174f116f966 [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"`
Gerrit GerritConfig `yaml:"gerrit"`
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"`
}
// GerritConfig represents Gerrit integration configuration
type GerritConfig struct {
Username string `yaml:"username"`
Password string `yaml:"password"` // HTTP password or API token
BaseURL string `yaml:"base_url"` // Gerrit server URL (e.g. https://gerrit.example.com)
Project string `yaml:"project"` // Gerrit project name
}
// 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 {
RepoPath string `yaml:"repo_path"`
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
}
if gerritUsername := os.Getenv("GERRIT_USERNAME"); gerritUsername != "" {
config.Gerrit.Username = gerritUsername
}
if gerritPassword := os.Getenv("GERRIT_PASSWORD"); gerritPassword != "" {
config.Gerrit.Password = gerritPassword
}
if gerritBaseURL := os.Getenv("GERRIT_BASE_URL"); gerritBaseURL != "" {
config.Gerrit.BaseURL = gerritBaseURL
}
if gerritProject := os.Getenv("GERRIT_PROJECT"); gerritProject != "" {
config.Gerrit.Project = gerritProject
}
// 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.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 that at least one Git provider is configured
hasGitHub := config.GitHub.Token != "" && config.GitHub.Owner != "" && config.GitHub.Repo != ""
hasGerrit := config.Gerrit.Username != "" && config.Gerrit.Password != "" && config.Gerrit.BaseURL != "" && config.Gerrit.Project != ""
if !hasGitHub && !hasGerrit {
return fmt.Errorf("either GitHub or Gerrit configuration is required")
}
// Validate GitHub config if provided
if config.GitHub.Token != "" {
if config.GitHub.Owner == "" {
return fmt.Errorf("github.owner is required when github.token is provided")
}
if config.GitHub.Repo == "" {
return fmt.Errorf("github.repo is required when github.token is provided")
}
}
// Validate Gerrit config if provided
if config.Gerrit.Username != "" {
if config.Gerrit.Password == "" {
return fmt.Errorf("gerrit.password is required when gerrit.username is provided")
}
if config.Gerrit.BaseURL == "" {
return fmt.Errorf("gerrit.base_url is required when gerrit.username is provided")
}
if config.Gerrit.Project == "" {
return fmt.Errorf("gerrit.project is required when gerrit.username is provided")
}
}
// 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
}
// HasGitHubConfig returns true if GitHub is properly configured
func (c *Config) HasGitHubConfig() bool {
return c.GitHub.Token != "" && c.GitHub.Owner != "" && c.GitHub.Repo != ""
}
// HasGerritConfig returns true if Gerrit is properly configured
func (c *Config) HasGerritConfig() bool {
return c.Gerrit.Username != "" && c.Gerrit.Password != "" && c.Gerrit.BaseURL != "" && c.Gerrit.Project != ""
}
// GetPrimaryGitProvider returns the primary git provider type ("github" or "gerrit")
// If both are configured, GitHub takes precedence for backward compatibility
func (c *Config) GetPrimaryGitProvider() string {
if c.HasGitHubConfig() {
return "github"
}
if c.HasGerritConfig() {
return "gerrit"
}
return ""
}