byokey_provider/
versions.rs1use byokey_types::ProviderId;
8use serde::Deserialize;
9use std::collections::HashMap;
10use std::sync::Arc;
11
12const BASE_URL: &str = "https://assets.byokey.io/versions";
13
14#[derive(Debug, Clone, Deserialize)]
16pub struct ProviderVersions {
17 #[serde(default)]
19 pub cli_version: Option<String>,
20 #[serde(default)]
22 pub user_agent: Option<String>,
23 #[serde(default)]
25 pub stainless_package_version: Option<String>,
26 #[serde(default)]
28 pub stainless_runtime_version: Option<String>,
29 #[serde(default)]
31 pub editor_version: Option<String>,
32 #[serde(default)]
34 pub plugin_version: Option<String>,
35 #[serde(default)]
37 pub github_api_version: Option<String>,
38}
39
40#[derive(Debug, Clone)]
42pub struct VersionStore(Arc<HashMap<ProviderId, ProviderVersions>>);
43
44impl VersionStore {
45 pub async fn fetch(http: &rquest::Client) -> Self {
50 let providers = [
51 (ProviderId::Claude, "claude"),
52 (ProviderId::Codex, "codex"),
53 (ProviderId::Copilot, "copilot"),
54 (ProviderId::Antigravity, "antigravity"),
55 (ProviderId::Kimi, "kimi"),
56 (ProviderId::Qwen, "qwen"),
57 (ProviderId::IFlow, "iflow"),
58 ];
59
60 let mut map = HashMap::new();
61 for (id, name) in providers {
62 match fetch_one(http, name).await {
63 Ok(v) => {
64 map.insert(id, v);
65 }
66 Err(e) => {
67 tracing::debug!(provider = name, %e, "failed to fetch version info, using defaults");
68 }
69 }
70 }
71
72 Self(Arc::new(map))
73 }
74
75 #[must_use]
77 pub fn empty() -> Self {
78 Self(Arc::new(HashMap::new()))
79 }
80
81 #[must_use]
83 pub fn get(&self, provider: &ProviderId) -> Option<&ProviderVersions> {
84 self.0.get(provider)
85 }
86
87 #[must_use]
89 pub fn user_agent(&self, provider: &ProviderId, default: &str) -> String {
90 self.get(provider)
91 .and_then(|v| v.user_agent.as_deref())
92 .unwrap_or(default)
93 .to_string()
94 }
95
96 #[must_use]
98 pub fn cli_version(&self, provider: &ProviderId, default: &str) -> String {
99 self.get(provider)
100 .and_then(|v| v.cli_version.as_deref())
101 .unwrap_or(default)
102 .to_string()
103 }
104
105 #[must_use]
107 pub fn stainless_runtime(&self, provider: &ProviderId, default: &str) -> String {
108 self.get(provider)
109 .and_then(|v| v.stainless_runtime_version.as_deref())
110 .unwrap_or(default)
111 .to_string()
112 }
113
114 #[must_use]
116 pub fn stainless_package(&self, provider: &ProviderId, default: &str) -> String {
117 self.get(provider)
118 .and_then(|v| v.stainless_package_version.as_deref())
119 .unwrap_or(default)
120 .to_string()
121 }
122}
123
124async fn fetch_one(http: &rquest::Client, provider_name: &str) -> Result<ProviderVersions, String> {
125 let url = format!("{BASE_URL}/{provider_name}.json");
126 let resp = http
127 .get(&url)
128 .send()
129 .await
130 .map_err(|e| format!("fetch failed: {e}"))?;
131 if !resp.status().is_success() {
132 return Err(format!("HTTP {}", resp.status()));
133 }
134 resp.json::<ProviderVersions>()
135 .await
136 .map_err(|e| format!("parse failed: {e}"))
137}