103 lines · 3.3 KB · TypeScript
RawDownload
import type { NamedError } from "@synsci/util/error"import { MessageV2 } from "./message-v2"import { iife } from "@/util/iife" export namespace SessionRetry {  export const RETRY_INITIAL_DELAY = 2000  export const RETRY_BACKOFF_FACTOR = 2  export const RETRY_MAX_DELAY_NO_HEADERS = 30_000 // 30 seconds  export const RETRY_MAX_DELAY = 2_147_483_647 // max 32-bit signed integer for setTimeout   export async function sleep(ms: number, signal: AbortSignal): Promise<void> {    return new Promise((resolve, reject) => {      const abortHandler = () => {        clearTimeout(timeout)        reject(new DOMException("Aborted", "AbortError"))      }      const timeout = setTimeout(        () => {          signal.removeEventListener("abort", abortHandler)          resolve()        },        Math.min(ms, RETRY_MAX_DELAY),      )      signal.addEventListener("abort", abortHandler, { once: true })    })  }   export function delay(attempt: number, error?: MessageV2.APIError) {    if (error) {      const headers = error.data.responseHeaders      if (headers) {        const retryAfterMs = headers["retry-after-ms"]        if (retryAfterMs) {          const parsedMs = Number.parseFloat(retryAfterMs)          if (!Number.isNaN(parsedMs)) {            return parsedMs          }        }         const retryAfter = headers["retry-after"]        if (retryAfter) {          const parsedSeconds = Number.parseFloat(retryAfter)          if (!Number.isNaN(parsedSeconds)) {            // convert seconds to milliseconds            return Math.ceil(parsedSeconds * 1000)          }          // Try parsing as HTTP date format          const parsed = Date.parse(retryAfter) - Date.now()          if (!Number.isNaN(parsed) && parsed > 0) {            return Math.ceil(parsed)          }        }         return RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1)      }    }     return Math.min(RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1), RETRY_MAX_DELAY_NO_HEADERS)  }   export function retryable(error: ReturnType<NamedError["toObject"]>) {    if (MessageV2.APIError.isInstance(error)) {      if (!error.data.isRetryable) return undefined      return error.data.message.includes("Overloaded") ? "Provider is overloaded" : error.data.message    }     const json = iife(() => {      try {        if (typeof error.data?.message === "string") {          const parsed = JSON.parse(error.data.message)          return parsed        }         return JSON.parse(error.data.message)      } catch {        return undefined      }    })    try {      if (!json || typeof json !== "object") return undefined      const code = typeof json.code === "string" ? json.code : ""       if (json.type === "error" && json.error?.type === "too_many_requests") {        return "Too Many Requests"      }      if (code.includes("exhausted") || code.includes("unavailable")) {        return "Provider is overloaded"      }      if (json.type === "error" && json.error?.code?.includes("rate_limit")) {        return "Rate Limited"      }      if (        json.error?.message?.includes("no_kv_space") ||        (json.type === "error" && json.error?.type === "server_error") ||        !!json.error      ) {        return "Provider Server Error"      }    } catch {      return undefined    }  }}