diff --git a/apps/web/utils/error.ts b/apps/web/utils/error.ts index b72e11ee27..14b5ea5ba7 100644 --- a/apps/web/utils/error.ts +++ b/apps/web/utils/error.ts @@ -3,7 +3,6 @@ import { setUser, } from "@sentry/nextjs"; import { APICallError, RetryError } from "ai"; -import type { z } from "zod"; import { createScopedLogger } from "@/utils/logger"; const logger = createScopedLogger("error"); @@ -117,6 +116,19 @@ export function isAWSThrottlingError(error: unknown): error is Error { ); } +export function isOutlookThrottlingError(error: unknown): boolean { + const err = error as Record; + const code = err?.code as string | undefined; + const statusCode = err?.statusCode as number | undefined; + const message = err?.message as string | undefined; + return ( + statusCode === 429 || + code === "ApplicationThrottled" || + code === "TooManyRequests" || + (typeof message === "string" && /MailboxConcurrency/i.test(message)) + ); +} + export function isAICallError(error: unknown): error is APICallError { return APICallError.isInstance(error); } @@ -131,6 +143,7 @@ export function isKnownApiError(error: unknown): boolean { isGmailInsufficientPermissionsError(error) || isGmailRateLimitExceededError(error) || isGmailQuotaExceededError(error) || + isOutlookThrottlingError(error) || (APICallError.isInstance(error) && (isIncorrectOpenAIAPIKeyError(error) || isInvalidOpenAIModelError(error) || @@ -174,6 +187,16 @@ export function checkCommonErrors( }; } + if (isOutlookThrottlingError(error)) { + logger.warn("Outlook throttling error for url", { url }); + return { + type: "Outlook Rate Limit", + message: + "Microsoft is temporarily limiting requests. Please try again shortly.", + code: 429, + }; + } + if (RetryError.isInstance(error) && isOpenAIRetryError(error)) { logger.warn("OpenAI quota exceeded for url", { url }); return { diff --git a/apps/web/utils/outlook/retry.test.ts b/apps/web/utils/outlook/retry.test.ts index 053a95e7d7..066106a326 100644 --- a/apps/web/utils/outlook/retry.test.ts +++ b/apps/web/utils/outlook/retry.test.ts @@ -68,6 +68,26 @@ describe("isRetryableError", () => { expect(result.retryable).toBe(true); }); + it("identifies ApplicationThrottled code as rate limit", () => { + const errorInfo = { + code: "ApplicationThrottled", + errorMessage: "Application is over its MailboxConcurrency limit.", + }; + const result = isRetryableError(errorInfo); + expect(result.isRateLimit).toBe(true); + expect(result.retryable).toBe(true); + }); + + it("identifies MailboxConcurrency message as rate limit", () => { + const errorInfo = { + status: 429, + errorMessage: "MailboxConcurrency limit exceeded", + }; + const result = isRetryableError(errorInfo); + expect(result.isRateLimit).toBe(true); + expect(result.retryable).toBe(true); + }); + it("identifies server errors", () => { for (const status of [502, 503, 504]) { const errorInfo = { status, errorMessage: "Server error" }; diff --git a/apps/web/utils/outlook/retry.ts b/apps/web/utils/outlook/retry.ts index 9429f405fd..60f3c49b8e 100644 --- a/apps/web/utils/outlook/retry.ts +++ b/apps/web/utils/outlook/retry.ts @@ -13,7 +13,7 @@ interface ErrorInfo { /** * Retries a Microsoft Graph API operation when rate limits or temporary server errors are encountered - * - Rate limits: 429, "TooManyRequests" code + * - Rate limits: 429, "TooManyRequests", "ApplicationThrottled", "MailboxConcurrency" * - Server errors: 502, 503, 504, "ServiceNotAvailable", "ServerBusy" */ export async function withOutlookRetry( @@ -121,12 +121,13 @@ export function isRetryableError(errorInfo: ErrorInfo): { } { const { status, code, errorMessage } = errorInfo; - // Rate limit detection: 429 status or "TooManyRequests" code + // Rate limit detection: 429 status, throttling codes, or rate limit messages const isRateLimit = status === 429 || code === "TooManyRequests" || + code === "ApplicationThrottled" || /rate limit/i.test(errorMessage) || - /quota exceeded/i.test(errorMessage); + /MailboxConcurrency/i.test(errorMessage); // Temporary server errors that should be retried (502, 503, 504) const isServerError = diff --git a/version.txt b/version.txt index ec89d50dfe..50b5571c28 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v2.23.3 +v2.23.4