2026-09-13 07:25:02 +00:00
|
|
|
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<Self, String> {
|
|
|
|
|
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());
|
2026-09-13 07:42:59 +00:00
|
|
|
let model = env::var("GROKBOY_MODEL").unwrap_or_else(|_| "grok-4.6".into());
|
2026-09-13 07:25:02 +00:00
|
|
|
Ok(Self {
|
|
|
|
|
api_key,
|
|
|
|
|
base_url: base_url.trim_end_matches('/').to_string(),
|
|
|
|
|
model,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
2026-09-13 07:42:59 +00:00
|
|
|
#[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");
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-13 07:25:02 +00:00
|
|
|
#[test]
|
|
|
|
|
fn rejects_missing_key() {
|
|
|
|
|
let _ = Config {
|
|
|
|
|
api_key: "x".into(),
|
|
|
|
|
base_url: "https://api.x.ai/v1".into(),
|
2026-09-13 07:42:59 +00:00
|
|
|
model: "grok-4.6".into(),
|
2026-09-13 07:25:02 +00:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|