From adfa8befbbef36b1ec6f1008ba539719e274f28f Mon Sep 17 00:00:00 2001 From: wjiuxing Date: Sat, 9 May 2026 22:12:39 +0800 Subject: [PATCH 1/4] feat: add Chinese error patterns to RETRYABLE_ERROR_PATTERNS --- src/hooks/runtime-fallback/constants.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/hooks/runtime-fallback/constants.ts b/src/hooks/runtime-fallback/constants.ts index 206762961..cb968ef91 100644 --- a/src/hooks/runtime-fallback/constants.ts +++ b/src/hooks/runtime-fallback/constants.ts @@ -41,6 +41,14 @@ export const RETRYABLE_ERROR_PATTERNS = [ /(?:^|\s)429(?:\s|$)/, /(?:^|\s)503(?:\s|$)/, /(?:^|\s)529(?:\s|$)/, + + // Chinese rate-limit / quota patterns (Zhipu, etc.) + /使用上限/, // "usage limit" — Zhipu: "已达到 5 小时的使用上限" + /频率限制/, // "rate limit" — generic Chinese rate-limit + /请求过于频繁/, // "too many requests" — common Chinese 429 message + /暂时不可用/, // "temporarily unavailable" + /服务不可用/, // "service unavailable" + /请稍后重试/, // "please try again later" ] /** From 149a83d703aeb85b9437d6c8503702278a640f96 Mon Sep 17 00:00:00 2001 From: wjiuxing Date: Sat, 9 May 2026 22:12:58 +0800 Subject: [PATCH 2/4] feat: add Chinese quota patterns to classifyErrorType --- src/hooks/runtime-fallback/error-classifier.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/hooks/runtime-fallback/error-classifier.ts b/src/hooks/runtime-fallback/error-classifier.ts index 0901a4e05..33b17ccf3 100644 --- a/src/hooks/runtime-fallback/error-classifier.ts +++ b/src/hooks/runtime-fallback/error-classifier.ts @@ -143,6 +143,11 @@ export function classifyErrorType(error: unknown): string | undefined { /payment.?required/i.test(message) || /usage\s+limit/i.test(message) || /credit\s+balance.*too\s+low/i.test(message) || + /使用上限/.test(message) || + /达到.*限制/.test(message) || + /额度.*不足/.test(message) || + /余额.*不足/.test(message) || + /已耗尽/.test(message) || isLocalizedQuotaExhaustionMessage(message) ) { return "quota_exceeded" From c206b16865c1edbd915bcc0ff6c4310d1cee8d45 Mon Sep 17 00:00:00 2001 From: wjiuxing Date: Sat, 9 May 2026 22:13:56 +0800 Subject: [PATCH 3/4] feat: add Chinese error patterns to model-error-classifier --- src/shared/model-error-classifier.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/shared/model-error-classifier.ts b/src/shared/model-error-classifier.ts index fb3fdf480..a5d70ac62 100644 --- a/src/shared/model-error-classifier.ts +++ b/src/shared/model-error-classifier.ts @@ -74,6 +74,11 @@ const RETRYABLE_MESSAGE_PATTERNS = [ "529", "selected provider is forbidden", "provider is forbidden", + // Chinese retryable patterns (Zhipu, etc.) + "频率限制", // "rate limit" + "请求过于频繁", // "too many requests" + "暂时不可用", // "temporarily unavailable" + "服务不可用", // "service unavailable" ] /** @@ -106,6 +111,10 @@ const STOP_MESSAGE_PATTERNS = [ "in arrears", "fair use policy", "recharge and try", + "使用上限", + "额度不足", + "余额不足", + "已耗尽", ] const AUTO_RETRY_GATE_PATTERNS = [ From 3e9b125fd51f280b957c0d74f3b10510bc514560 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 15 May 2026 22:25:20 +0900 Subject: [PATCH 4/4] test(runtime-fallback): cover localized provider errors --- src/hooks/runtime-fallback/constants.ts | 14 +++---- .../runtime-fallback/error-classifier.test.ts | 38 +++++++++++++++++++ src/shared/model-error-classifier.test.ts | 31 +++++++++++++++ src/shared/model-error-classifier.ts | 1 + 4 files changed, 76 insertions(+), 8 deletions(-) diff --git a/src/hooks/runtime-fallback/constants.ts b/src/hooks/runtime-fallback/constants.ts index cb968ef91..43df0f936 100644 --- a/src/hooks/runtime-fallback/constants.ts +++ b/src/hooks/runtime-fallback/constants.ts @@ -41,14 +41,12 @@ export const RETRYABLE_ERROR_PATTERNS = [ /(?:^|\s)429(?:\s|$)/, /(?:^|\s)503(?:\s|$)/, /(?:^|\s)529(?:\s|$)/, - - // Chinese rate-limit / quota patterns (Zhipu, etc.) - /使用上限/, // "usage limit" — Zhipu: "已达到 5 小时的使用上限" - /频率限制/, // "rate limit" — generic Chinese rate-limit - /请求过于频繁/, // "too many requests" — common Chinese 429 message - /暂时不可用/, // "temporarily unavailable" - /服务不可用/, // "service unavailable" - /请稍后重试/, // "please try again later" + /使用上限/, + /频率限制/, + /请求过于频繁/, + /暂时不可用/, + /服务不可用/, + /请稍后重试/, ] /** diff --git a/src/hooks/runtime-fallback/error-classifier.test.ts b/src/hooks/runtime-fallback/error-classifier.test.ts index c9eef6e06..a7f43210a 100644 --- a/src/hooks/runtime-fallback/error-classifier.test.ts +++ b/src/hooks/runtime-fallback/error-classifier.test.ts @@ -59,6 +59,44 @@ describe("runtime-fallback error classifier", () => { expect(retryable).toBe(true) }) + test("treats localized transient provider messages as retryable", () => { + //#given + const errors = [ + { message: "请求过于频繁,请稍后重试" }, + { message: "服务暂时不可用" }, + { message: "触发频率限制" }, + ] + + //#when + const retryable = errors.map((error) => isRetryableError(error, [429, 503, 529])) + + //#then + expect(retryable).toEqual([true, true, true]) + }) + + test("classifies localized quota exhaustion messages as quota_exceeded", () => { + //#given + const errors = [ + { message: "已达到 5 小时的使用上限" }, + { message: "已达到每日调用限制" }, + { message: "额度不足" }, + { message: "账户余额不足" }, + { message: "免费额度已耗尽" }, + ] + + //#when + const classifications = errors.map((error) => classifyErrorType(error)) + + //#then + expect(classifications).toEqual([ + "quota_exceeded", + "quota_exceeded", + "quota_exceeded", + "quota_exceeded", + "quota_exceeded", + ]) + }) + test("classifies ProviderModelNotFoundError as model_not_found", () => { //#given const error = { diff --git a/src/shared/model-error-classifier.test.ts b/src/shared/model-error-classifier.test.ts index 33fd3f4c1..0ab40e8eb 100644 --- a/src/shared/model-error-classifier.test.ts +++ b/src/shared/model-error-classifier.test.ts @@ -216,6 +216,21 @@ describe("model-error-classifier", () => { expect(result).toBe(true) }) + test("treats localized transient provider messages as retryable", () => { + //#given + const errors = [ + { message: "请求过于频繁,请稍后重试" }, + { message: "服务暂时不可用" }, + { message: "触发频率限制" }, + ] + + //#when + const results = errors.map((error) => shouldRetryError(error)) + + //#then + expect(results).toEqual([true, true, true]) + }) + test("treats subscription quota message as non-retryable", () => { //#given const error = { message: "Subscription quota exceeded. You can continue using free models." } @@ -227,6 +242,22 @@ describe("model-error-classifier", () => { expect(result).toBe(false) }) + test("treats localized quota exhaustion messages as non-retryable stop errors", () => { + //#given + const errors = [ + { message: "已达到 5 小时的使用上限" }, + { message: "额度不足" }, + { message: "账户余额不足" }, + { message: "免费额度已耗尽" }, + ] + + //#when + const results = errors.map((error) => shouldRetryError(error)) + + //#then + expect(results).toEqual([false, false, false, false]) + }) + test("treats HTTP 429 rate limit message as retryable", () => { //#given const error = { message: "429 Too Many Requests: rate limit reached" } diff --git a/src/shared/model-error-classifier.ts b/src/shared/model-error-classifier.ts index a5d70ac62..787f4825a 100644 --- a/src/shared/model-error-classifier.ts +++ b/src/shared/model-error-classifier.ts @@ -67,6 +67,7 @@ const RETRYABLE_MESSAGE_PATTERNS = [ "balance", "temporarily unavailable", "try again", + "请稍后重试", "503", "502", "504",