mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
fix(usage): improve usage-query resilience and error surfacing
- useUsageQuery: retry once + keep-last-good — show the last successful
result for up to 10min when a query fails transiently (network/timeout/
HTTP 5xx), so a single blip no longer flips the card to red. Deterministic
failures (auth, empty key, unknown provider, 4xx) surface immediately and
clear the snapshot so a stale quota can't resurface after credentials change.
- bump native balance/coding-plan/subscription request timeouts 10s -> 15s
for slow cross-border endpoints.
- coding_plan: return explicit errors ("API key is empty" / "Unknown coding
plan provider") instead of a blank failure, mirroring balance.
- add unit tests for keep-last-good and transient/deterministic classification.
This commit is contained in:
@@ -72,7 +72,7 @@ async fn query_deepseek(api_key: &str) -> UsageResult {
|
||||
.get("https://api.deepseek.com/user/balance")
|
||||
.header("Authorization", format!("Bearer {api_key}"))
|
||||
.header("Accept", "application/json")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.timeout(Duration::from_secs(15))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
@@ -144,7 +144,7 @@ async fn query_stepfun(api_key: &str) -> UsageResult {
|
||||
.get("https://api.stepfun.com/v1/accounts")
|
||||
.header("Authorization", format!("Bearer {api_key}"))
|
||||
.header("Accept", "application/json")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.timeout(Duration::from_secs(15))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
@@ -203,7 +203,7 @@ async fn query_siliconflow(api_key: &str, is_cn: bool) -> UsageResult {
|
||||
.get(&url)
|
||||
.header("Authorization", format!("Bearer {api_key}"))
|
||||
.header("Accept", "application/json")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.timeout(Duration::from_secs(15))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
@@ -267,7 +267,7 @@ async fn query_openrouter(api_key: &str) -> UsageResult {
|
||||
.get("https://openrouter.ai/api/v1/credits")
|
||||
.header("Authorization", format!("Bearer {api_key}"))
|
||||
.header("Accept", "application/json")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.timeout(Duration::from_secs(15))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
@@ -327,7 +327,7 @@ async fn query_novita(api_key: &str) -> UsageResult {
|
||||
.get("https://api.novita.ai/v3/user/balance")
|
||||
.header("Authorization", format!("Bearer {api_key}"))
|
||||
.header("Accept", "application/json")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.timeout(Duration::from_secs(15))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ async fn query_kimi(api_key: &str) -> SubscriptionQuota {
|
||||
.get("https://api.kimi.com/coding/v1/usages")
|
||||
.header("Authorization", format!("Bearer {api_key}"))
|
||||
.header("Accept", "application/json")
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.timeout(std::time::Duration::from_secs(15))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
@@ -308,7 +308,7 @@ async fn query_zhipu(base_url: &str, api_key: &str) -> SubscriptionQuota {
|
||||
.header("Authorization", api_key) // 注意:智谱不加 Bearer 前缀
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept-Language", "en-US,en")
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.timeout(std::time::Duration::from_secs(15))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
@@ -391,7 +391,7 @@ async fn query_minimax(api_key: &str, is_cn: bool) -> SubscriptionQuota {
|
||||
.get(&url)
|
||||
.header("Authorization", format!("Bearer {api_key}"))
|
||||
.header("Content-Type", "application/json")
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.timeout(std::time::Duration::from_secs(15))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
@@ -463,7 +463,7 @@ async fn query_zenmux(base_url: &str, api_key: &str) -> SubscriptionQuota {
|
||||
.get(base_url)
|
||||
.header("Authorization", format!("Bearer {api_key}"))
|
||||
.header("Accept", "application/json")
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.timeout(std::time::Duration::from_secs(15))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
@@ -666,7 +666,8 @@ pub async fn get_coding_plan_quota(
|
||||
success: false,
|
||||
tiers: vec![],
|
||||
extra_usage: None,
|
||||
error: None,
|
||||
// 与 balance::get_balance 一致:给出明确错误,避免 footer 显示无信息的失败
|
||||
error: Some("API key is empty".to_string()),
|
||||
queried_at: None,
|
||||
});
|
||||
}
|
||||
@@ -681,9 +682,10 @@ pub async fn get_coding_plan_quota(
|
||||
success: false,
|
||||
tiers: vec![],
|
||||
extra_usage: None,
|
||||
error: None,
|
||||
// 域名未命中已知套餐供应商(如第三方中转站):给出明确错误而非静默失败
|
||||
error: Some("Unknown coding plan provider".to_string()),
|
||||
queried_at: None,
|
||||
})
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -328,7 +328,7 @@ async fn query_claude_quota(access_token: &str) -> SubscriptionQuota {
|
||||
.header("Authorization", format!("Bearer {access_token}"))
|
||||
.header("anthropic-beta", "oauth-2025-04-20")
|
||||
.header("Accept", "application/json")
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.timeout(std::time::Duration::from_secs(15))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
@@ -668,7 +668,7 @@ pub(crate) async fn query_codex_quota(
|
||||
req = req.header("ChatGPT-Account-Id", id);
|
||||
}
|
||||
|
||||
let resp = match req.timeout(std::time::Duration::from_secs(10)).send().await {
|
||||
let resp = match req.timeout(std::time::Duration::from_secs(15)).send().await {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
return SubscriptionQuota::error(
|
||||
@@ -964,7 +964,7 @@ async fn refresh_gemini_token(refresh_token: &str) -> Option<String> {
|
||||
("refresh_token", refresh_token),
|
||||
("grant_type", "refresh_token"),
|
||||
])
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.timeout(std::time::Duration::from_secs(15))
|
||||
.send()
|
||||
.await
|
||||
.ok()?;
|
||||
@@ -1048,7 +1048,7 @@ async fn query_gemini_quota(access_token: &str) -> SubscriptionQuota {
|
||||
"pluginType": "GEMINI"
|
||||
}
|
||||
}))
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.timeout(std::time::Duration::from_secs(15))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
@@ -1109,7 +1109,7 @@ async fn query_gemini_quota(access_token: &str) -> SubscriptionQuota {
|
||||
.header("Authorization", format!("Bearer {access_token}"))
|
||||
.header("Content-Type", "application/json")
|
||||
.json("a_body)
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.timeout(std::time::Duration::from_secs(15))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user