package config import ( "os" "strconv" "time" ) const ( ProviderOllama = "ollama" ProviderDeepSeek = "deepseek" ) type Config struct { Provider string `json:"provider"` Ollama OllamaConfig `json:"ollama"` DeepSeek DeepSeekConfig `json:"deepseek"` Sandbox SandboxConfig `json:"sandbox"` Session SessionConfig `json:"session"` } type OllamaConfig struct { BaseURL string `json:"base_url"` Model string `json:"model"` Timeout time.Duration `json:"timeout"` } type DeepSeekConfig struct { BaseURL string `json:"base_url"` Model string `json:"model"` APIKey string `json:"api_key"` Timeout time.Duration `json:"timeout"` } type SandboxConfig struct { Timeout time.Duration `json:"timeout"` MaxMemory int64 `json:"max_memory"` WorkingDir string `json:"working_dir"` } type SessionConfig struct { StorageDir string `json:"storage_dir"` MaxHistory int `json:"max_history"` } func DefaultConfig() *Config { return &Config{ Provider: ProviderDeepSeek, Ollama: OllamaConfig{ BaseURL: "http://localhost:11434", Model: "gemma4:e4b", Timeout: 120 * time.Second, }, DeepSeek: DeepSeekConfig{ BaseURL: "https://api.deepseek.com/v1", Model: "deepseek-v4-flash", APIKey: "sk-2f1049148e06492dbc304ba49c81c321", Timeout: 120 * time.Second, }, Sandbox: SandboxConfig{ Timeout: 30 * time.Second, MaxMemory: 512 * 1024 * 1024, WorkingDir: "/tmp/orca/sandbox", }, Session: SessionConfig{ StorageDir: func() string { home, _ := os.UserHomeDir() return home + "/.orca/sessions" }(), MaxHistory: 100, }, } } func LoadConfigFromEnv() *Config { cfg := DefaultConfig() if v := os.Getenv("ORCA_PROVIDER"); v != "" { cfg.Provider = v } if v := os.Getenv("ORCA_OLLAMA_BASE_URL"); v != "" { cfg.Ollama.BaseURL = v } if v := os.Getenv("ORCA_OLLAMA_MODEL"); v != "" { cfg.Ollama.Model = v } if v := os.Getenv("ORCA_OLLAMA_TIMEOUT"); v != "" { if d, err := time.ParseDuration(v); err == nil { cfg.Ollama.Timeout = d } } if v := os.Getenv("ORCA_DEEPSEEK_BASE_URL"); v != "" { cfg.DeepSeek.BaseURL = v } if v := os.Getenv("ORCA_DEEPSEEK_MODEL"); v != "" { cfg.DeepSeek.Model = v } if v := os.Getenv("ORCA_DEEPSEEK_API_KEY"); v != "" { cfg.DeepSeek.APIKey = v } if v := os.Getenv("ORCA_DEEPSEEK_TIMEOUT"); v != "" { if d, err := time.ParseDuration(v); err == nil { cfg.DeepSeek.Timeout = d } } if v := os.Getenv("ORCA_SANDBOX_TIMEOUT"); v != "" { if d, err := time.ParseDuration(v); err == nil { cfg.Sandbox.Timeout = d } } if v := os.Getenv("ORCA_SANDBOX_MAX_MEMORY"); v != "" { if n, err := strconv.ParseInt(v, 10, 64); err == nil { cfg.Sandbox.MaxMemory = n } } if v := os.Getenv("ORCA_SANDBOX_WORKING_DIR"); v != "" { cfg.Sandbox.WorkingDir = v } if v := os.Getenv("ORCA_SESSION_STORAGE_DIR"); v != "" { cfg.Session.StorageDir = v } if v := os.Getenv("ORCA_SESSION_MAX_HISTORY"); v != "" { if n, err := strconv.Atoi(v); err == nil { cfg.Session.MaxHistory = n } } return cfg } func (c *Config) IsValid() error { if c.Provider != ProviderOllama && c.Provider != ProviderDeepSeek { return errConfig("provider must be 'ollama' or 'deepseek'") } if c.Provider == ProviderOllama { if c.Ollama.BaseURL == "" { return errConfig("ollama.base_url must not be empty") } if c.Ollama.Model == "" { return errConfig("ollama.model must not be empty") } if c.Ollama.Timeout <= 0 { return errConfig("ollama.timeout must be positive") } } if c.Provider == ProviderDeepSeek { if c.DeepSeek.BaseURL == "" { return errConfig("deepseek.base_url must not be empty") } if c.DeepSeek.Model == "" { return errConfig("deepseek.model must not be empty") } if c.DeepSeek.APIKey == "" { return errConfig("deepseek.api_key must not be empty") } if c.DeepSeek.Timeout <= 0 { return errConfig("deepseek.timeout must be positive") } } if c.Sandbox.Timeout <= 0 { return errConfig("sandbox.timeout must be positive") } if c.Sandbox.MaxMemory <= 0 { return errConfig("sandbox.max_memory must be positive") } if c.Session.MaxHistory <= 0 { return errConfig("session.max_history must be positive") } return nil } func errConfig(msg string) error { return &ConfigError{Message: msg} } type ConfigError struct { Message string } func (e *ConfigError) Error() string { return "config: " + e.Message }