use crate::backend::http::HttpConfig;
use crate::persona::Persona;
#[derive(Debug, Clone)]
pub struct Provider {
name: String,
base_url: String,
model: String,
key_env_var: String,
}
impl Provider {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
base_url: String::new(),
model: String::new(),
key_env_var: String::new(),
}
}
#[must_use]
pub fn with_base_url(mut self, base_url: impl Into<String>) -> Self {
self.base_url = base_url.into();
self
}
#[must_use]
pub fn with_model(mut self, model: impl Into<String>) -> Self {
self.model = model.into();
self
}
#[must_use]
pub fn with_key_env_var(mut self, key_env_var: impl Into<String>) -> Self {
self.key_env_var = key_env_var.into();
self
}
pub fn name(&self) -> &str {
&self.name
}
pub fn base_url(&self) -> &str {
&self.base_url
}
pub fn model(&self) -> &str {
&self.model
}
pub fn key_env_var(&self) -> &str {
&self.key_env_var
}
pub fn config_from_env(&self) -> Option<HttpConfig> {
let api_key = std::env::var(&self.key_env_var).ok()?;
Some(HttpConfig {
base_url: self.base_url.clone(),
model: self.model.clone(),
api_key,
})
}
pub fn registry() -> &'static [Provider] {
static REGISTRY: std::sync::OnceLock<Vec<Provider>> = std::sync::OnceLock::new();
REGISTRY.get_or_init(|| {
vec![
Provider::new("deepseek")
.with_base_url("https://api.deepseek.com/v1")
.with_model("deepseek-chat")
.with_key_env_var("DEEPSEEK_API_KEY"),
Provider::new("openai")
.with_base_url("https://api.openai.com/v1")
.with_model("gpt-4o")
.with_key_env_var("OPENAI_API_KEY"),
Provider::new("moonshot")
.with_base_url("https://api.moonshot.cn/v1")
.with_model("moonshot-v1-auto")
.with_key_env_var("MOONSHOT_API_KEY"),
Provider::new("alibaba")
.with_base_url("https://dashscope.aliyuncs.com/compatible-mode/v1")
.with_model("qwen-plus")
.with_key_env_var("DASHSCOPE_API_KEY"),
Provider::new("zai")
.with_base_url("https://api.z.ai/api/coding/paas/v4")
.with_model("glm-5.2")
.with_key_env_var("ZAI_API_KEY"),
Provider::new("google")
.with_base_url("https://generativelanguage.googleapis.com/v1beta/openai")
.with_model("gemini-1.5-pro")
.with_key_env_var("GOOGLE_API_KEY"),
]
})
}
}
pub fn random_roster(
personas: &[Persona],
configs: &[HttpConfig],
rng: &mut impl rand::Rng,
) -> Vec<(Persona, HttpConfig)> {
if configs.is_empty() || personas.is_empty() {
return Vec::new();
}
personas
.iter()
.map(|persona| {
let idx = rng.random_range(0..configs.len());
(persona.clone(), configs[idx].clone())
})
.collect()
}
pub fn roster_from_env(
personas: &[Persona],
providers: &[Provider],
seed: u64,
) -> Result<Vec<(Persona, HttpConfig)>, crate::ProserpinaError> {
use rand::SeedableRng;
let configs: Vec<HttpConfig> = providers
.iter()
.filter_map(|p| p.config_from_env())
.collect();
if configs.is_empty() {
return Err(crate::ProserpinaError::no_authed_providers(
providers.iter().map(|p| p.name().to_owned()).collect(),
));
}
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
Ok(random_roster(personas, &configs, &mut rng))
}