1use std::sync::Mutex;
4use std::time::Duration;
5use thiserror::Error;
6
7pub const INTL_CLOUD: &str = "https://gateway.ariacompute.com";
8pub const INTL_SITE: &str = "https://ariacompute.com";
9pub const INTL_UPGRADE: &str = "https://github.com/ariacompute";
10pub const CN_CLOUD: &str = "https://gateway.ariacompute.cn";
11pub const CN_SITE: &str = "https://ariacompute.cn";
12pub const CN_UPGRADE: &str = "https://gitee.com/ariacompute";
13
14#[derive(Debug, Error, Clone, PartialEq, Eq)]
15pub enum AuthError {
16 #[error("invalid hybrid_mode: {0}")]
17 InvalidHybridMode(String),
18 #[error("invalid hybrid_execution: {0}")]
19 InvalidHybridExecution(String),
20 #[error("invalid compute: {0}")]
21 InvalidCompute(String),
22 #[error("hybrid_semantic_timeout_ms / cache_size must be positive integers")]
23 InvalidTimeoutOrCache,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct AuthConfig {
28 pub cloud_api_key: String,
29 pub cloud_url: String,
30 pub site_url: String,
31 pub upgrade_url: String,
32 pub hybrid_mode: String,
33 pub hybrid_execution: String,
34 pub hybrid_semantic: bool,
35 pub hybrid_semantic_timeout_ms: i32,
36 pub hybrid_semantic_cache_size: i32,
37 pub compute: String,
38 pub hf_token: String,
39 pub modelscope_api_token: String,
40}
41
42impl Default for AuthConfig {
43 fn default() -> Self {
44 Self {
45 cloud_api_key: String::new(),
46 cloud_url: String::new(),
47 site_url: String::new(),
48 upgrade_url: String::new(),
49 hybrid_mode: "balance".into(),
50 hybrid_execution: "hybrid".into(),
51 hybrid_semantic: true,
52 hybrid_semantic_timeout_ms: 800,
53 hybrid_semantic_cache_size: 512,
54 compute: "auto".into(),
55 hf_token: String::new(),
56 modelscope_api_token: String::new(),
57 }
58 }
59}
60
61#[derive(Debug, Clone, Default)]
63pub struct AuthUpdates {
64 pub cloud_api_key: Option<String>,
65 pub cloud_url: Option<String>,
66 pub site_url: Option<String>,
67 pub upgrade_url: Option<String>,
68 pub hybrid_mode: Option<String>,
69 pub hybrid_execution: Option<String>,
70 pub hybrid_semantic: Option<bool>,
71 pub hybrid_semantic_timeout_ms: Option<i32>,
72 pub hybrid_semantic_cache_size: Option<i32>,
73 pub compute: Option<String>,
74 pub hf_token: Option<String>,
75 pub modelscope_api_token: Option<String>,
76}
77
78fn gateway_region(url: &str) -> Option<&'static str> {
79 let lower = url.to_ascii_lowercase();
80 if lower.contains("ariacompute.cn") || lower.contains("gitee.com/ariacompute") {
81 Some("cn")
82 } else if lower.contains("ariacompute.com") || lower.contains("github.com/ariacompute") {
83 Some("intl")
84 } else {
85 None
86 }
87}
88
89fn pair_urls(region: &str) -> (&'static str, &'static str, &'static str) {
90 if region == "cn" {
91 (CN_CLOUD, CN_SITE, CN_UPGRADE)
92 } else {
93 (INTL_CLOUD, INTL_SITE, INTL_UPGRADE)
94 }
95}
96
97pub fn fill_auth_urls(mut cfg: AuthConfig) -> AuthConfig {
99 let region = gateway_region(&cfg.site_url)
100 .or_else(|| gateway_region(&cfg.cloud_url))
101 .or_else(|| gateway_region(&cfg.upgrade_url));
102 let Some(region) = region else {
103 return cfg;
104 };
105 let (cloud, site, upgrade) = pair_urls(region);
106 if cfg.cloud_url.is_empty() {
107 cfg.cloud_url = cloud.into();
108 }
109 if cfg.site_url.is_empty() {
110 cfg.site_url = site.into();
111 }
112 if cfg.upgrade_url.is_empty() {
113 cfg.upgrade_url = upgrade.into();
114 }
115 cfg
116}
117
118fn locale_prefers_cn() -> bool {
119 let lang = format!(
120 "{}{}",
121 std::env::var("LANG").unwrap_or_default(),
122 std::env::var("LC_ALL").unwrap_or_default()
123 )
124 .to_ascii_lowercase();
125 lang.contains("zh") || lang.contains(".cn") || lang.starts_with("cn")
126}
127
128type ProbeFn = fn(&str, &str) -> bool;
129
130fn default_probe_dashboard(site_url: &str, api_key: &str) -> bool {
131 let url = format!(
132 "{}/api/dashboard/models",
133 site_url.trim_end_matches('/')
134 );
135 let resp = ureq::get(&url)
136 .set("User-Agent", "aria-engine-sdk/0.1.0")
137 .set("Authorization", &format!("Bearer {api_key}"))
138 .timeout(Duration::from_secs(10))
139 .call();
140 match resp {
141 Ok(r) => (200..300).contains(&r.status()),
142 Err(ureq::Error::Status(code, _)) => (200..300).contains(&code),
143 Err(_) => false,
144 }
145}
146
147static PROBE_DASHBOARD: Mutex<ProbeFn> = Mutex::new(default_probe_dashboard);
148
149#[cfg(test)]
150pub fn set_probe_dashboard(f: ProbeFn) {
151 *PROBE_DASHBOARD.lock().unwrap() = f;
152}
153
154#[cfg(test)]
155pub fn reset_probe_dashboard() {
156 *PROBE_DASHBOARD.lock().unwrap() = default_probe_dashboard;
157}
158
159fn probe_dashboard(site_url: &str, api_key: &str) -> bool {
160 let f = *PROBE_DASHBOARD.lock().unwrap();
161 f(site_url, api_key)
162}
163
164pub fn detect_gateway_pair(api_key: &str) -> (&'static str, &'static str, &'static str) {
166 let key = api_key.trim();
167 let (first, second) = if locale_prefers_cn() {
168 ("cn", "intl")
169 } else {
170 ("intl", "cn")
171 };
172 for region in [first, second] {
173 let (cloud, site, upgrade) = pair_urls(region);
174 if !key.is_empty() && probe_dashboard(site, key) {
175 return (cloud, site, upgrade);
176 }
177 }
178 pair_urls(first)
179}
180
181pub fn apply_auth(existing: &AuthConfig, updates: &AuthUpdates) -> Result<AuthConfig, AuthError> {
183 let mut out = existing.clone();
184 if let Some(v) = &updates.cloud_api_key {
185 out.cloud_api_key = v.clone();
186 }
187 if let Some(v) = &updates.cloud_url {
188 out.cloud_url = v.clone();
189 }
190 if let Some(v) = &updates.site_url {
191 out.site_url = v.clone();
192 }
193 if let Some(v) = &updates.upgrade_url {
194 out.upgrade_url = v.clone();
195 }
196 if let Some(v) = &updates.hybrid_mode {
197 out.hybrid_mode = v.clone();
198 }
199 if let Some(v) = &updates.hybrid_execution {
200 out.hybrid_execution = v.clone();
201 }
202 if let Some(v) = updates.hybrid_semantic {
203 out.hybrid_semantic = v;
204 }
205 if let Some(v) = updates.hybrid_semantic_timeout_ms {
206 out.hybrid_semantic_timeout_ms = v;
207 }
208 if let Some(v) = updates.hybrid_semantic_cache_size {
209 out.hybrid_semantic_cache_size = v;
210 }
211 if let Some(v) = &updates.compute {
212 out.compute = v.clone();
213 }
214 if let Some(v) = &updates.hf_token {
215 out.hf_token = v.clone();
216 }
217 if let Some(v) = &updates.modelscope_api_token {
218 out.modelscope_api_token = v.clone();
219 }
220 match out.hybrid_mode.as_str() {
221 "cost" | "balance" | "intelligence" => {}
222 other => return Err(AuthError::InvalidHybridMode(other.into())),
223 }
224 match out.hybrid_execution.as_str() {
225 "hybrid" | "device" | "cloud" => {}
226 other => return Err(AuthError::InvalidHybridExecution(other.into())),
227 }
228 match out.compute.as_str() {
229 "auto" | "cpu" | "cuda" => {}
230 other => return Err(AuthError::InvalidCompute(other.into())),
231 }
232 if out.hybrid_semantic_timeout_ms <= 0 || out.hybrid_semantic_cache_size <= 0 {
233 return Err(AuthError::InvalidTimeoutOrCache);
234 }
235 out = fill_auth_urls(out);
236 if !out.cloud_api_key.is_empty()
237 && (out.cloud_url.is_empty() || out.site_url.is_empty() || out.upgrade_url.is_empty())
238 {
239 let (cloud, site, upgrade) = detect_gateway_pair(&out.cloud_api_key);
240 if out.cloud_url.is_empty() {
241 out.cloud_url = cloud.into();
242 }
243 if out.site_url.is_empty() {
244 out.site_url = site.into();
245 }
246 if out.upgrade_url.is_empty() {
247 out.upgrade_url = upgrade.into();
248 }
249 }
250 Ok(out)
251}