use uuid::Uuid;
const OPENCODE_SESSION_HEADER: &str = "X-Opencode-Session";
pub fn user_agent() -> String {
format!("OpenCrabs/{}", crate::VERSION)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Vendor {
OpenCode,
Zai,
ModelScope,
Kimi,
DeepSeek,
}
fn host_of(base_url: &str) -> String {
let rest = base_url
.split_once("://")
.map_or(base_url, |(_, rest)| rest);
let host = rest.split('/').next().unwrap_or(rest);
let host = host.split('@').next_back().unwrap_or(host);
let host = host.split(':').next().unwrap_or(host);
host.to_ascii_lowercase()
}
fn is_host(host: &str, domain: &str) -> bool {
host == domain || host.ends_with(&format!(".{domain}"))
}
impl Vendor {
pub fn from_base_url(base_url: &str) -> Option<Self> {
let host = host_of(base_url);
if is_host(&host, "opencode.ai") {
return Some(Self::OpenCode);
}
if is_host(&host, "z.ai") || is_host(&host, "bigmodel.cn") {
return Some(Self::Zai);
}
if is_host(&host, "modelscope.cn") || is_host(&host, "modelscope.ai") {
return Some(Self::ModelScope);
}
if is_host(&host, "kimi.com") || is_host(&host, "moonshot.ai") {
return Some(Self::Kimi);
}
if is_host(&host, "deepseek.com") {
return Some(Self::DeepSeek);
}
None
}
}
fn process_session_id() -> &'static str {
use std::sync::OnceLock;
static SESSION: OnceLock<String> = OnceLock::new();
SESSION.get_or_init(|| Uuid::new_v4().to_string())
}
pub fn headers_for(base_url: &str, session: Option<Uuid>) -> Vec<(String, String)> {
let Some(vendor) = Vendor::from_base_url(base_url) else {
return Vec::new();
};
let mut headers = vec![("User-Agent".to_string(), user_agent())];
match vendor {
Vendor::OpenCode => {
let session = session
.map(|s| s.to_string())
.unwrap_or_else(|| process_session_id().to_string());
headers.push((OPENCODE_SESSION_HEADER.to_string(), session));
headers.push(("X-Title".to_string(), "OpenCrabs".to_string()));
headers.push((
"HTTP-Referer".to_string(),
"https://opencrabs.com".to_string(),
));
}
Vendor::Zai => headers.push(("Accept-Language".to_string(), "en-US,en".to_string())),
Vendor::ModelScope | Vendor::Kimi | Vendor::DeepSeek => {}
}
headers
}