diff --git a/src/agents/embedded-agent-helpers.formatassistanterrortext.test.ts b/src/agents/embedded-agent-helpers.formatassistanterrortext.test.ts index cd41433f004..602b9cd8239 100644 --- a/src/agents/embedded-agent-helpers.formatassistanterrortext.test.ts +++ b/src/agents/embedded-agent-helpers.formatassistanterrortext.test.ts @@ -71,6 +71,14 @@ describe("formatAssistantErrorText", () => { "The AI service is temporarily overloaded. Please try again in a moment.", ); }); + it("preserves overload wording for Z.AI rate-limit errors", () => { + const msg = makeAssistantError( + '429 status code (exceeded limit)\n{"code":1305,"message":"The service may be temporarily overloaded, please try again later."}', + ); + expect(formatAssistantErrorText(msg)).toBe( + "The AI service is temporarily overloaded. Please try again in a moment.", + ); + }); it("rewrites generic provider internal errors without support request ids", () => { const msg = makeAssistantError( "An error occurred while processing your request. You can retry your request, or contact us through our help center at help.openai.com if the error persists. Please include the request ID synthetic-provider-request-001 in your message.", diff --git a/src/agents/embedded-agent-helpers/failover-matches.test.ts b/src/agents/embedded-agent-helpers/failover-matches.test.ts index ed8d7b6f6ce..106af7e5163 100644 --- a/src/agents/embedded-agent-helpers/failover-matches.test.ts +++ b/src/agents/embedded-agent-helpers/failover-matches.test.ts @@ -1,6 +1,6 @@ // Covers provider-specific failover matcher regressions. import { describe, expect, it } from "vitest"; -import { classifyFailoverReason } from "./errors.js"; +import { classifyFailoverReason, classifyFailoverReasonFromHttpStatus } from "./errors.js"; import { isAuthErrorMessage, isBillingErrorMessage, @@ -9,6 +9,7 @@ import { isServerErrorMessage, isTimeoutErrorMessage, } from "./failover-matches.js"; +import { formatRateLimitOrOverloadedErrorCopy } from "./sanitize-user-facing-text.js"; describe("Z.ai vendor error codes (#48988)", () => { describe("error 1311 — model not included in subscription plan", () => { @@ -210,3 +211,24 @@ describe("generic assistant error text classification (#93931)", () => { ).toBe(false); }); }); + +describe("HTTP 429 overload wording (#98101)", () => { + it("keeps Z.AI code 1305 in rate-limit backoff while preserving overload copy", () => { + const message = + "429 status code (exceeded limit)\n" + + '{"code":1305,"message":"The service may be temporarily overloaded, please try again later."}'; + expect(classifyFailoverReason(message)).toBe("rate_limit"); + expect(classifyFailoverReasonFromHttpStatus(429, message)).toBe("rate_limit"); + expect(formatRateLimitOrOverloadedErrorCopy(message)).toBe( + "The AI service is temporarily overloaded. Please try again in a moment.", + ); + }); + + it("preserves actionable retry details when a rate limit also mentions overload", () => { + expect( + formatRateLimitOrOverloadedErrorCopy( + "429 rate limit: service overloaded, try again in 30 seconds", + ), + ).toBe("⚠️ rate limit: service overloaded, try again in 30 seconds"); + }); +}); diff --git a/src/agents/embedded-agent-helpers/sanitize-user-facing-text.ts b/src/agents/embedded-agent-helpers/sanitize-user-facing-text.ts index 4c586667ded..d2eaae344fb 100644 --- a/src/agents/embedded-agent-helpers/sanitize-user-facing-text.ts +++ b/src/agents/embedded-agent-helpers/sanitize-user-facing-text.ts @@ -127,15 +127,24 @@ function extractProviderRateLimitMessage(raw: string): string | undefined { } export function formatRateLimitOrOverloadedErrorCopy(raw: string): string | undefined { - if (isRateLimitErrorMessage(raw)) { - return extractProviderRateLimitMessage(raw) ?? RATE_LIMIT_ERROR_USER_MESSAGE; - } if (MODEL_CAPACITY_ERROR_RE.test(raw)) { return MODEL_CAPACITY_ERROR_USER_MESSAGE; } + const isRateLimit = isRateLimitErrorMessage(raw); + if (isRateLimit) { + const providerMessage = extractProviderRateLimitMessage(raw); + if (providerMessage) { + return providerMessage; + } + } + // Retry classification still owns 429 backoff; user copy can preserve the provider's + // overload wording when there is no more actionable retry/reset detail. if (isOverloadedErrorMessage(raw)) { return OVERLOADED_ERROR_USER_MESSAGE; } + if (isRateLimit) { + return RATE_LIMIT_ERROR_USER_MESSAGE; + } return undefined; }