use std::env; #[derive(Debug, Clone)] pub struct Config { pub api_key: String, pub base_url: String, pub model: String, } impl Config { /// Resolve from environment. Prefers GROKBOY_*, then XAI_*, then OPENAI_*. pub fn from_env() -> Result { let api_key = env::var("GROKBOY_API_KEY") .or_else(|_| env::var("XAI_API_KEY")) .or_else(|_| env::var("OPENAI_API_KEY")) .map_err(|_| { "missing API key: set GROKBOY_API_KEY (or XAI_API_KEY / OPENAI_API_KEY)".to_string() })?; if api_key.trim().is_empty() { return Err("API key is empty".into()); } let base_url = env::var("GROKBOY_BASE_URL") .or_else(|_| env::var("OPENAI_BASE_URL")) .unwrap_or_else(|_| "https://api.x.ai/v1".into()); let model = env::var("GROKBOY_MODEL").unwrap_or_else(|_| "grok-4.6".into()); Ok(Self { api_key, base_url: base_url.trim_end_matches('/').to_string(), model, }) } } #[cfg(test)] mod tests { use super::*; #[test] fn default_model_is_grok_4_6() { let cfg = Config { api_key: "x".into(), base_url: "https://api.x.ai/v1".into(), model: "grok-4.6".into(), }; assert_eq!(cfg.model, "grok-4.6"); } #[test] fn rejects_missing_key() { let _ = Config { api_key: "x".into(), base_url: "https://api.x.ai/v1".into(), model: "grok-4.6".into(), }; } }