use byokey_types::ProviderId;
use serde::Deserialize;
use std::collections::HashMap;
use std::sync::Arc;
const BASE_URL: &str = "https://assets.byokey.io/versions";
#[derive(Debug, Clone, Deserialize)]
pub struct ProviderVersions {
#[serde(default)]
pub cli_version: Option<String>,
#[serde(default)]
pub user_agent: Option<String>,
#[serde(default)]
pub stainless_package_version: Option<String>,
#[serde(default)]
pub stainless_runtime_version: Option<String>,
#[serde(default)]
pub editor_version: Option<String>,
#[serde(default)]
pub plugin_version: Option<String>,
#[serde(default)]
pub github_api_version: Option<String>,
}
#[derive(Debug, Clone)]
pub struct VersionStore(Arc<HashMap<ProviderId, ProviderVersions>>);
impl VersionStore {
pub async fn fetch(http: &rquest::Client) -> Self {
let providers = [
(ProviderId::Claude, "claude"),
(ProviderId::Codex, "codex"),
(ProviderId::Copilot, "copilot"),
(ProviderId::Antigravity, "antigravity"),
(ProviderId::Kimi, "kimi"),
(ProviderId::Qwen, "qwen"),
(ProviderId::IFlow, "iflow"),
];
let mut map = HashMap::new();
for (id, name) in providers {
match fetch_one(http, name).await {
Ok(v) => {
map.insert(id, v);
}
Err(e) => {
tracing::debug!(provider = name, %e, "failed to fetch version info, using defaults");
}
}
}
Self(Arc::new(map))
}
#[must_use]
pub fn empty() -> Self {
Self(Arc::new(HashMap::new()))
}
#[must_use]
pub fn get(&self, provider: &ProviderId) -> Option<&ProviderVersions> {
self.0.get(provider)
}
#[must_use]
pub fn user_agent(&self, provider: &ProviderId, default: &str) -> String {
self.get(provider)
.and_then(|v| v.user_agent.as_deref())
.unwrap_or(default)
.to_string()
}
#[must_use]
pub fn cli_version(&self, provider: &ProviderId, default: &str) -> String {
self.get(provider)
.and_then(|v| v.cli_version.as_deref())
.unwrap_or(default)
.to_string()
}
#[must_use]
pub fn stainless_runtime(&self, provider: &ProviderId, default: &str) -> String {
self.get(provider)
.and_then(|v| v.stainless_runtime_version.as_deref())
.unwrap_or(default)
.to_string()
}
#[must_use]
pub fn stainless_package(&self, provider: &ProviderId, default: &str) -> String {
self.get(provider)
.and_then(|v| v.stainless_package_version.as_deref())
.unwrap_or(default)
.to_string()
}
}
async fn fetch_one(http: &rquest::Client, provider_name: &str) -> Result<ProviderVersions, String> {
let url = format!("{BASE_URL}/{provider_name}.json");
let resp = http
.get(&url)
.send()
.await
.map_err(|e| format!("fetch failed: {e}"))?;
if !resp.status().is_success() {
return Err(format!("HTTP {}", resp.status()));
}
resp.json::<ProviderVersions>()
.await
.map_err(|e| format!("parse failed: {e}"))
}