From db66348ff8716cca317541dbedc744b756ca504f Mon Sep 17 00:00:00 2001 From: jimmy Date: Fri, 1 May 2026 17:35:18 +0800 Subject: [PATCH] feat(providers): add Baidu Qianfan Coding Plan for Claude Code (#2322) * feat(providers): add baidu qianfan coding plan presets * refactor(providers): align qianfan presets with existing format * chore(providers): narrow qianfan coding plan scope --- src-tauri/src/services/stream_check.rs | 25 +++++++++++++++++++++++++ src/config/claudeProviderPresets.ts | 20 ++++++++++++++++++++ src/hooks/useStreamCheck.ts | 14 ++++++++++++++ src/i18n/locales/en.json | 2 ++ src/i18n/locales/ja.json | 2 ++ src/i18n/locales/zh.json | 2 ++ 6 files changed, 65 insertions(+) diff --git a/src-tauri/src/services/stream_check.rs b/src-tauri/src/services/stream_check.rs index 4befbefe..25859a46 100644 --- a/src-tauri/src/services/stream_check.rs +++ b/src-tauri/src/services/stream_check.rs @@ -789,6 +789,15 @@ impl StreamCheckService { return None; } let lower = body.to_lowercase(); + let qianfan_quota_indicators = [ + "coding_plan_hour_quota_exceeded", + "coding_plan_week_quota_exceeded", + "coding_plan_month_quota_exceeded", + ]; + if qianfan_quota_indicators.iter().any(|s| lower.contains(s)) { + return Some("quotaExceeded"); + } + // 必须提到 "model",避免通用 404 / 400 被误判 if !lower.contains("model") { return None; @@ -1733,6 +1742,22 @@ mod tests { ); } + #[test] + fn test_detect_qianfan_coding_plan_quota_errors() { + let cases = [ + r#"{"error":{"code":"coding_plan_hour_quota_exceeded","message":"hour quota exceeded"}}"#, + r#"{"error":{"code":"coding_plan_week_quota_exceeded","message":"week quota exceeded"}}"#, + r#"{"error":{"code":"coding_plan_month_quota_exceeded","message":"month quota exceeded"}}"#, + ]; + + for body in cases { + assert_eq!( + StreamCheckService::detect_error_category(429, body), + Some("quotaExceeded") + ); + } + } + #[test] fn test_get_os_name() { let os_name = StreamCheckService::get_os_name(); diff --git a/src/config/claudeProviderPresets.ts b/src/config/claudeProviderPresets.ts index b9ba96c5..6618e54d 100644 --- a/src/config/claudeProviderPresets.ts +++ b/src/config/claudeProviderPresets.ts @@ -181,6 +181,26 @@ export const providerPresets: ProviderPreset[] = [ icon: "zhipu", iconColor: "#0F62FE", }, + { + name: "Baidu Qianfan Coding Plan", + websiteUrl: "https://cloud.baidu.com/product/qianfan_modelbuilder", + apiKeyUrl: + "https://console.bce.baidu.com/qianfan/ais/console/applicationConsole/application", + settingsConfig: { + env: { + ANTHROPIC_BASE_URL: "https://qianfan.baidubce.com/anthropic/coding", + ANTHROPIC_AUTH_TOKEN: "", + ANTHROPIC_MODEL: "qianfan-code-latest", + ANTHROPIC_DEFAULT_HAIKU_MODEL: "qianfan-code-latest", + ANTHROPIC_DEFAULT_SONNET_MODEL: "qianfan-code-latest", + ANTHROPIC_DEFAULT_OPUS_MODEL: "qianfan-code-latest", + }, + }, + category: "cn_official", + endpointCandidates: ["https://qianfan.baidubce.com/anthropic/coding"], + icon: "baidu", + iconColor: "#2932E1", + }, { name: "Bailian", websiteUrl: "https://bailian.console.aliyun.com", diff --git a/src/hooks/useStreamCheck.ts b/src/hooks/useStreamCheck.ts index 196e65e9..54786560 100644 --- a/src/hooks/useStreamCheck.ts +++ b/src/hooks/useStreamCheck.ts @@ -62,6 +62,20 @@ export function useStreamCheck(appId: AppId) { closeButton: true, }, ); + } else if (result.errorCategory === "quotaExceeded") { + toast.warning( + t("streamCheck.quotaExceeded", { + providerName: providerName, + defaultValue: `${providerName} Coding Plan quota has been exceeded`, + }), + { + description: t("streamCheck.quotaExceededHint", { + defaultValue: "", + }), + duration: 10000, + closeButton: true, + }, + ); } else { const httpStatus = result.httpStatus; const hintKey = httpStatus diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 42939438..a05e508b 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -2240,6 +2240,8 @@ "error": "{{providerName}} check error: {{error}}", "modelNotFound": "{{providerName}} test model {{model}} does not exist or has been deprecated", "modelNotFoundHint": "This model may have been retired by the provider. Update the default test model in \"Model Test Config\".", + "quotaExceeded": "{{providerName}} Coding Plan quota has been exceeded", + "quotaExceededHint": "Baidu Qianfan returned a Coding Plan quota-limit error. Wait for the 5-hour, weekly, or monthly quota refresh, or adjust the plan in the Qianfan console.", "httpHint": { "400": "Provider rejected request format. Health check probe may differ from actual usage.", "401": "API key may be invalid, or provider uses OAuth auth. Check failure doesn't mean it's unusable.", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 5c5a110c..2c2cfa33 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -2240,6 +2240,8 @@ "error": "{{providerName}} のチェックでエラーが発生しました: {{error}}", "modelNotFound": "{{providerName}} のテストモデル {{model}} は存在しないか廃止されています", "modelNotFoundHint": "このモデルはプロバイダーにより廃止された可能性があります。「モデルテスト設定」でデフォルトのテストモデルを更新してください。", + "quotaExceeded": "{{providerName}} の Coding Plan 利用枠を超過しました", + "quotaExceededHint": "Baidu Qianfan が Coding Plan の利用枠超過エラーを返しました。5時間・週次・月次の利用枠更新を待つか、Qianfan コンソールでプランを確認してください。", "httpHint": { "400": "リクエスト形式が拒否されました。ヘルスチェックの形式は実際の使用と異なる場合があります。", "401": "APIキーが無効か、OAuthなどの認証方式を使用しています。チェック失敗は実際に使えないことを意味しません。", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 5add807a..38ed5a9c 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -2240,6 +2240,8 @@ "error": "{{providerName}} 检查出错: {{error}}", "modelNotFound": "{{providerName}} 测试模型 {{model}} 不存在或已下架", "modelNotFoundHint": "该模型可能已被供应商弃用。请在\"模型测试配置\"中更新默认测试模型。", + "quotaExceeded": "{{providerName}} Coding Plan 额度已用尽", + "quotaExceededHint": "百度千帆返回了 Coding Plan 超额错误。请等待 5 小时/每周/每月额度刷新,或在千帆控制台切换套餐。", "httpHint": { "400": "供应商拒绝了请求格式。健康检查的探测格式可能与实际使用不同。", "401": "API Key 可能无效,或供应商使用 OAuth 等认证方式。检查失败不代表实际不可用。",