@langchain/core 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,11 @@ const require_index = require("./p-retry/index.cjs");
5
5
  let p_queue = require("p-queue");
6
6
  p_queue = require_runtime.__toESM(p_queue, 1);
7
7
  //#region src/utils/async_caller.ts
8
- var async_caller_exports = /* @__PURE__ */ require_runtime.__exportAll({ AsyncCaller: () => AsyncCaller });
8
+ var async_caller_exports = /* @__PURE__ */ require_runtime.__exportAll({
9
+ AsyncCaller: () => AsyncCaller,
10
+ classifyRateLimitError: () => classifyRateLimitError,
11
+ parseRetryAfterMs: () => parseRetryAfterMs
12
+ });
9
13
  const STATUS_NO_RETRY = [
10
14
  400,
11
15
  401,
@@ -17,6 +21,110 @@ const STATUS_NO_RETRY = [
17
21
  407,
18
22
  409
19
23
  ];
24
+ const RETRY_AFTER_AUTO_RETRY_THRESHOLD_MS = 6e4;
25
+ const QUOTA_EXHAUSTED_MESSAGE_PATTERNS = [
26
+ /insufficient[_ -]?quota/i,
27
+ /exceeded (?:your|the current|the available).+quota/i,
28
+ /usage quota/i,
29
+ /quota (?:has been )?exhausted/i,
30
+ /billing/i,
31
+ /credit balance/i,
32
+ /out of credits/i,
33
+ /will reset at/i
34
+ ];
35
+ const RETRY_AFTER_MESSAGE_PATTERN = /(?:try again in|retry after)\s+(\d+(?:\.\d+)?)\s*(milliseconds?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h)\b/i;
36
+ function getResponseStatus(error) {
37
+ return typeof error === "object" && error !== null && "response" in error && typeof error.response === "object" && error.response !== null && "status" in error.response && typeof error.response.status === "number" ? error.response.status : void 0;
38
+ }
39
+ function getDirectStatus(error) {
40
+ if (typeof error !== "object" || error === null) return;
41
+ if ("status" in error && typeof error.status === "number") return error.status;
42
+ if ("statusCode" in error && typeof error.statusCode === "number") return error.statusCode;
43
+ }
44
+ function getErrorMessage(error) {
45
+ return typeof error === "object" && error !== null && "message" in error && typeof error.message === "string" ? error.message : void 0;
46
+ }
47
+ function getErrorCode(error) {
48
+ if (typeof error !== "object" || error === null) return;
49
+ if ("code" in error && typeof error.code === "string") return error.code;
50
+ return "error" in error && typeof error.error === "object" && error.error !== null && "code" in error.error && typeof error.error.code === "string" ? error.error.code : void 0;
51
+ }
52
+ function _getRetryAfterHeader(error) {
53
+ if (error?.headers) {
54
+ if (typeof error.headers.get === "function") return error.headers.get("retry-after");
55
+ return error.headers["retry-after"] ?? error.headers["Retry-After"];
56
+ }
57
+ if (error?.response?.headers) {
58
+ if (typeof error.response.headers.get === "function") return error.response.headers.get("retry-after");
59
+ return error.response.headers["retry-after"] ?? error.response.headers["Retry-After"];
60
+ }
61
+ }
62
+ function parseRetryAfterFromMessageMs(message) {
63
+ if (message == null) return;
64
+ const match = RETRY_AFTER_MESSAGE_PATTERN.exec(message);
65
+ if (!match) return;
66
+ const rawValue = Number(match[1]);
67
+ const unit = match[2]?.toLowerCase();
68
+ if (Number.isNaN(rawValue) || !unit) return;
69
+ if (unit === "ms" || unit.startsWith("millisecond")) return rawValue;
70
+ if (unit === "m" || unit.startsWith("min")) return rawValue * 6e4;
71
+ if (unit === "h" || unit.startsWith("hr") || unit.startsWith("hour")) return rawValue * 36e5;
72
+ return rawValue * 1e3;
73
+ }
74
+ function coerceError(error, fallbackMessage) {
75
+ if (error instanceof Error) return error;
76
+ const coerced = new Error(fallbackMessage);
77
+ if (typeof error === "object" && error !== null) Object.assign(coerced, error);
78
+ return coerced;
79
+ }
80
+ function setRateLimitMetadata(error, classification) {
81
+ if (typeof error !== "object" || error === null) return;
82
+ const mutableError = error;
83
+ mutableError.rateLimitType = classification.action;
84
+ mutableError.rateLimitReason = classification.reason;
85
+ if (classification.retryAfterMs !== void 0) mutableError.retryAfterMs = classification.retryAfterMs;
86
+ }
87
+ function parseRetryAfterMs(headerValue) {
88
+ if (headerValue == null) return;
89
+ const trimmed = headerValue.trim();
90
+ if (!trimmed) return;
91
+ const seconds = Number(trimmed);
92
+ if (!Number.isNaN(seconds) && seconds >= 0) return seconds * 1e3;
93
+ const date = Date.parse(trimmed);
94
+ if (!Number.isNaN(date)) {
95
+ const delayMs = date - Date.now();
96
+ return delayMs > 0 ? delayMs : 0;
97
+ }
98
+ }
99
+ function classifyRateLimitError(error) {
100
+ if ((getResponseStatus(error) ?? getDirectStatus(error)) !== 429) return;
101
+ if (getErrorCode(error) === "insufficient_quota") return {
102
+ action: "stop",
103
+ reason: "insufficient_quota"
104
+ };
105
+ const message = getErrorMessage(error);
106
+ if (message && QUOTA_EXHAUSTED_MESSAGE_PATTERNS.some((pattern) => pattern.test(message))) return {
107
+ action: "stop",
108
+ reason: "quota_message"
109
+ };
110
+ const retryAfterMs = parseRetryAfterMs(_getRetryAfterHeader(error)) ?? parseRetryAfterFromMessageMs(message);
111
+ if (retryAfterMs !== void 0) {
112
+ if (retryAfterMs <= RETRY_AFTER_AUTO_RETRY_THRESHOLD_MS) return {
113
+ action: "wait",
114
+ retryAfterMs,
115
+ reason: "retry_after_hint"
116
+ };
117
+ return {
118
+ action: "capacity",
119
+ retryAfterMs,
120
+ reason: "retry_after_too_large"
121
+ };
122
+ }
123
+ return {
124
+ action: "capacity",
125
+ reason: "headerless_429"
126
+ };
127
+ }
20
128
  /**
21
129
  * The default failed attempt handler for the AsyncCaller.
22
130
  * @param error - The error to handle.
@@ -26,13 +134,26 @@ const defaultFailedAttemptHandler = (error) => {
26
134
  if (typeof error !== "object" || error === null) return;
27
135
  if ("message" in error && typeof error.message === "string" && (error.message.startsWith("Cancel") || error.message.startsWith("AbortError")) || "name" in error && typeof error.name === "string" && error.name === "AbortError") throw error;
28
136
  if ("code" in error && typeof error.code === "string" && error.code === "ECONNABORTED") throw error;
29
- const responseStatus = "response" in error && typeof error.response === "object" && error.response !== null && "status" in error.response && typeof error.response.status === "number" ? error.response.status : void 0;
30
- const directStatus = "status" in error && typeof error.status === "number" ? error.status : void 0;
31
- const status = responseStatus ?? directStatus;
137
+ const status = getResponseStatus(error) ?? getDirectStatus(error);
32
138
  if (status && STATUS_NO_RETRY.includes(+status)) throw error;
33
- if (("error" in error && typeof error.error === "object" && error.error !== null && "code" in error.error && typeof error.error.code === "string" ? error.error.code : void 0) === "insufficient_quota") {
34
- const err = new Error("message" in error && typeof error.message === "string" ? error.message : "Insufficient quota");
139
+ if (getErrorCode(error) === "insufficient_quota") {
140
+ const err = coerceError(error, getErrorMessage(error) ?? "Insufficient quota");
35
141
  err.name = "InsufficientQuotaError";
142
+ setRateLimitMetadata(err, {
143
+ action: "stop",
144
+ reason: "insufficient_quota"
145
+ });
146
+ throw err;
147
+ }
148
+ const rateLimitClassification = classifyRateLimitError(error);
149
+ if (rateLimitClassification) {
150
+ if (rateLimitClassification.action === "wait") {
151
+ setRateLimitMetadata(error, rateLimitClassification);
152
+ return;
153
+ }
154
+ const err = coerceError(error, getErrorMessage(error) ?? "Rate limit exceeded");
155
+ if (err.name === "Error") err.name = rateLimitClassification.action === "stop" ? "RateLimitQuotaExhaustedError" : "RateLimitCapacityError";
156
+ setRateLimitMetadata(err, rateLimitClassification);
36
157
  throw err;
37
158
  }
38
159
  };
@@ -97,5 +218,7 @@ Object.defineProperty(exports, "async_caller_exports", {
97
218
  return async_caller_exports;
98
219
  }
99
220
  });
221
+ exports.classifyRateLimitError = classifyRateLimitError;
222
+ exports.parseRetryAfterMs = parseRetryAfterMs;
100
223
 
101
224
  //# sourceMappingURL=async_caller.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"async_caller.cjs","names":["PQueueMod","pRetry","getAbortSignalError"],"sources":["../../src/utils/async_caller.ts"],"sourcesContent":["import PQueueMod from \"p-queue\";\n\nimport { getAbortSignalError } from \"./signal.js\";\nimport pRetry from \"./p-retry/index.js\";\n\nconst STATUS_NO_RETRY = [\n 400, // Bad Request\n 401, // Unauthorized\n 402, // Payment Required\n 403, // Forbidden\n 404, // Not Found\n 405, // Method Not Allowed\n 406, // Not Acceptable\n 407, // Proxy Authentication Required\n 409, // Conflict\n];\n\n/**\n * The default failed attempt handler for the AsyncCaller.\n * @param error - The error to handle.\n * @returns void\n */\nconst defaultFailedAttemptHandler = (error: unknown) => {\n if (typeof error !== \"object\" || error === null) {\n return;\n }\n\n if (\n (\"message\" in error &&\n typeof error.message === \"string\" &&\n (error.message.startsWith(\"Cancel\") ||\n error.message.startsWith(\"AbortError\"))) ||\n (\"name\" in error &&\n typeof error.name === \"string\" &&\n error.name === \"AbortError\")\n ) {\n throw error;\n }\n if (\n \"code\" in error &&\n typeof error.code === \"string\" &&\n error.code === \"ECONNABORTED\"\n ) {\n throw error;\n }\n const responseStatus =\n \"response\" in error &&\n typeof error.response === \"object\" &&\n error.response !== null &&\n \"status\" in error.response &&\n typeof error.response.status === \"number\"\n ? error.response.status\n : undefined;\n\n // OpenAI SDK errors expose status directly on the error object\n const directStatus =\n \"status\" in error && typeof error.status === \"number\"\n ? error.status\n : undefined;\n\n const status = responseStatus ?? directStatus;\n if (status && STATUS_NO_RETRY.includes(+status)) {\n throw error;\n }\n\n const code =\n \"error\" in error &&\n typeof error.error === \"object\" &&\n error.error !== null &&\n \"code\" in error.error &&\n typeof error.error.code === \"string\"\n ? error.error.code\n : undefined;\n if (code === \"insufficient_quota\") {\n const err = new Error(\n \"message\" in error && typeof error.message === \"string\"\n ? error.message\n : \"Insufficient quota\"\n );\n err.name = \"InsufficientQuotaError\";\n throw err;\n }\n};\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nexport type FailedAttemptHandler = (error: any) => any;\n\nexport interface AsyncCallerParams {\n /**\n * The maximum number of concurrent calls that can be made.\n * Defaults to `Infinity`, which means no limit.\n */\n maxConcurrency?: number;\n /**\n * The maximum number of retries that can be made for a single call,\n * with an exponential backoff between each attempt. Defaults to 6.\n */\n maxRetries?: number;\n /**\n * Custom handler to handle failed attempts. Takes the originally thrown\n * error object as input, and should itself throw an error if the input\n * error is not retryable.\n */\n onFailedAttempt?: FailedAttemptHandler;\n}\n\nexport interface AsyncCallerCallOptions {\n signal?: AbortSignal;\n}\n\n/**\n * A class that can be used to make async calls with concurrency and retry logic.\n *\n * This is useful for making calls to any kind of \"expensive\" external resource,\n * be it because it's rate-limited, subject to network issues, etc.\n *\n * Concurrent calls are limited by the `maxConcurrency` parameter, which defaults\n * to `Infinity`. This means that by default, all calls will be made in parallel.\n *\n * Retries are limited by the `maxRetries` parameter, which defaults to 6. This\n * means that by default, each call will be retried up to 6 times, with an\n * exponential backoff between each attempt.\n */\nexport class AsyncCaller {\n protected maxConcurrency: AsyncCallerParams[\"maxConcurrency\"];\n\n protected maxRetries: AsyncCallerParams[\"maxRetries\"];\n\n protected onFailedAttempt: AsyncCallerParams[\"onFailedAttempt\"];\n\n private queue: (typeof import(\"p-queue\"))[\"default\"][\"prototype\"];\n\n constructor(params: AsyncCallerParams) {\n this.maxConcurrency = params.maxConcurrency ?? Infinity;\n this.maxRetries = params.maxRetries ?? 6;\n this.onFailedAttempt =\n params.onFailedAttempt ?? defaultFailedAttemptHandler;\n\n const PQueue = (\n \"default\" in PQueueMod ? PQueueMod.default : PQueueMod\n ) as typeof PQueueMod;\n this.queue = new PQueue({ concurrency: this.maxConcurrency });\n }\n\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n async call<A extends any[], T extends (...args: A) => Promise<any>>(\n callable: T,\n ...args: Parameters<T>\n ): Promise<Awaited<ReturnType<T>>> {\n return this.queue.add(\n () =>\n pRetry(\n () =>\n callable(...args).catch((error) => {\n // oxlint-disable-next-line no-instanceof/no-instanceof\n if (error instanceof Error) {\n throw error;\n } else {\n throw new Error(error);\n }\n }),\n {\n onFailedAttempt: ({ error }) => this.onFailedAttempt?.(error),\n retries: this.maxRetries,\n randomize: true,\n // If needed we can change some of the defaults here,\n // but they're quite sensible.\n }\n ),\n { throwOnTimeout: true }\n );\n }\n\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n callWithOptions<A extends any[], T extends (...args: A) => Promise<any>>(\n options: AsyncCallerCallOptions,\n callable: T,\n ...args: Parameters<T>\n ): Promise<Awaited<ReturnType<T>>> {\n // Note this doesn't cancel the underlying request,\n // when available prefer to use the signal option of the underlying call\n if (options.signal) {\n let listener: (() => void) | undefined;\n return Promise.race([\n this.call<A, T>(callable, ...args),\n new Promise<never>((_, reject) => {\n listener = () => {\n reject(getAbortSignalError(options.signal));\n };\n options.signal?.addEventListener(\"abort\", listener, { once: true });\n }),\n ]).finally(() => {\n if (options.signal && listener) {\n options.signal.removeEventListener(\"abort\", listener);\n }\n });\n }\n return this.call<A, T>(callable, ...args);\n }\n\n fetch(...args: Parameters<typeof fetch>): ReturnType<typeof fetch> {\n return this.call(() =>\n fetch(...args).then((res) => (res.ok ? res : Promise.reject(res)))\n );\n }\n}\n"],"mappings":";;;;;;;;AAKA,MAAM,kBAAkB;CACtB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;AAOD,MAAM,+BAA+B,UAAmB;AACtD,KAAI,OAAO,UAAU,YAAY,UAAU,KACzC;AAGF,KACG,aAAa,SACZ,OAAO,MAAM,YAAY,aACxB,MAAM,QAAQ,WAAW,SAAS,IACjC,MAAM,QAAQ,WAAW,aAAa,KACzC,UAAU,SACT,OAAO,MAAM,SAAS,YACtB,MAAM,SAAS,aAEjB,OAAM;AAER,KACE,UAAU,SACV,OAAO,MAAM,SAAS,YACtB,MAAM,SAAS,eAEf,OAAM;CAER,MAAM,iBACJ,cAAc,SACd,OAAO,MAAM,aAAa,YAC1B,MAAM,aAAa,QACnB,YAAY,MAAM,YAClB,OAAO,MAAM,SAAS,WAAW,WAC7B,MAAM,SAAS,SACf,KAAA;CAGN,MAAM,eACJ,YAAY,SAAS,OAAO,MAAM,WAAW,WACzC,MAAM,SACN,KAAA;CAEN,MAAM,SAAS,kBAAkB;AACjC,KAAI,UAAU,gBAAgB,SAAS,CAAC,OAAO,CAC7C,OAAM;AAWR,MAPE,WAAW,SACX,OAAO,MAAM,UAAU,YACvB,MAAM,UAAU,QAChB,UAAU,MAAM,SAChB,OAAO,MAAM,MAAM,SAAS,WACxB,MAAM,MAAM,OACZ,KAAA,OACO,sBAAsB;EACjC,MAAM,MAAM,IAAI,MACd,aAAa,SAAS,OAAO,MAAM,YAAY,WAC3C,MAAM,UACN,qBACL;AACD,MAAI,OAAO;AACX,QAAM;;;;;;;;;;;;;;;;AA2CV,IAAa,cAAb,MAAyB;CACvB;CAEA;CAEA;CAEA;CAEA,YAAY,QAA2B;AACrC,OAAK,iBAAiB,OAAO,kBAAkB;AAC/C,OAAK,aAAa,OAAO,cAAc;AACvC,OAAK,kBACH,OAAO,mBAAmB;EAE5B,MAAM,SACJ,aAAaA,QAAAA,UAAYA,QAAAA,QAAU,UAAUA,QAAAA;AAE/C,OAAK,QAAQ,IAAI,OAAO,EAAE,aAAa,KAAK,gBAAgB,CAAC;;CAI/D,MAAM,KACJ,UACA,GAAG,MAC8B;AACjC,SAAO,KAAK,MAAM,UAEdC,cAAAA,cAEI,SAAS,GAAG,KAAK,CAAC,OAAO,UAAU;AAEjC,OAAI,iBAAiB,MACnB,OAAM;OAEN,OAAM,IAAI,MAAM,MAAM;IAExB,EACJ;GACE,kBAAkB,EAAE,YAAY,KAAK,kBAAkB,MAAM;GAC7D,SAAS,KAAK;GACd,WAAW;GAGZ,CACF,EACH,EAAE,gBAAgB,MAAM,CACzB;;CAIH,gBACE,SACA,UACA,GAAG,MAC8B;AAGjC,MAAI,QAAQ,QAAQ;GAClB,IAAI;AACJ,UAAO,QAAQ,KAAK,CAClB,KAAK,KAAW,UAAU,GAAG,KAAK,EAClC,IAAI,SAAgB,GAAG,WAAW;AAChC,qBAAiB;AACf,YAAOC,eAAAA,oBAAoB,QAAQ,OAAO,CAAC;;AAE7C,YAAQ,QAAQ,iBAAiB,SAAS,UAAU,EAAE,MAAM,MAAM,CAAC;KACnE,CACH,CAAC,CAAC,cAAc;AACf,QAAI,QAAQ,UAAU,SACpB,SAAQ,OAAO,oBAAoB,SAAS,SAAS;KAEvD;;AAEJ,SAAO,KAAK,KAAW,UAAU,GAAG,KAAK;;CAG3C,MAAM,GAAG,MAA0D;AACjE,SAAO,KAAK,WACV,MAAM,GAAG,KAAK,CAAC,MAAM,QAAS,IAAI,KAAK,MAAM,QAAQ,OAAO,IAAI,CAAE,CACnE"}
1
+ {"version":3,"file":"async_caller.cjs","names":["PQueueMod","pRetry","getAbortSignalError"],"sources":["../../src/utils/async_caller.ts"],"sourcesContent":["import PQueueMod from \"p-queue\";\n\nimport { getAbortSignalError } from \"./signal.js\";\nimport pRetry from \"./p-retry/index.js\";\n\nconst STATUS_NO_RETRY = [\n 400, // Bad Request\n 401, // Unauthorized\n 402, // Payment Required\n 403, // Forbidden\n 404, // Not Found\n 405, // Method Not Allowed\n 406, // Not Acceptable\n 407, // Proxy Authentication Required\n 409, // Conflict\n];\n\nconst RETRY_AFTER_AUTO_RETRY_THRESHOLD_MS = 60_000;\n\nconst QUOTA_EXHAUSTED_MESSAGE_PATTERNS = [\n /insufficient[_ -]?quota/i,\n /exceeded (?:your|the current|the available).+quota/i,\n /usage quota/i,\n /quota (?:has been )?exhausted/i,\n /billing/i,\n /credit balance/i,\n /out of credits/i,\n /will reset at/i,\n];\n\nconst RETRY_AFTER_MESSAGE_PATTERN =\n /(?:try again in|retry after)\\s+(\\d+(?:\\.\\d+)?)\\s*(milliseconds?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h)\\b/i;\n\ntype RateLimitAction = \"wait\" | \"capacity\" | \"stop\";\n\ntype RateLimitClassification = {\n action: RateLimitAction;\n retryAfterMs?: number;\n reason: string;\n};\n\nfunction getResponseStatus(error: unknown): number | undefined {\n return typeof error === \"object\" &&\n error !== null &&\n \"response\" in error &&\n typeof error.response === \"object\" &&\n error.response !== null &&\n \"status\" in error.response &&\n typeof error.response.status === \"number\"\n ? error.response.status\n : undefined;\n}\n\nfunction getDirectStatus(error: unknown): number | undefined {\n if (typeof error !== \"object\" || error === null) {\n return undefined;\n }\n\n if (\"status\" in error && typeof error.status === \"number\") {\n return error.status;\n }\n\n if (\"statusCode\" in error && typeof error.statusCode === \"number\") {\n return error.statusCode;\n }\n\n return undefined;\n}\n\nfunction getErrorMessage(error: unknown): string | undefined {\n return typeof error === \"object\" &&\n error !== null &&\n \"message\" in error &&\n typeof error.message === \"string\"\n ? error.message\n : undefined;\n}\n\nfunction getErrorCode(error: unknown): string | undefined {\n if (typeof error !== \"object\" || error === null) {\n return undefined;\n }\n\n if (\"code\" in error && typeof error.code === \"string\") {\n return error.code;\n }\n\n return \"error\" in error &&\n typeof error.error === \"object\" &&\n error.error !== null &&\n \"code\" in error.error &&\n typeof error.error.code === \"string\"\n ? error.error.code\n : undefined;\n}\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction _getRetryAfterHeader(error: any): string | null | undefined {\n if (error?.headers) {\n if (typeof error.headers.get === \"function\") {\n return error.headers.get(\"retry-after\");\n }\n return error.headers[\"retry-after\"] ?? error.headers[\"Retry-After\"];\n }\n\n if (error?.response?.headers) {\n if (typeof error.response.headers.get === \"function\") {\n return error.response.headers.get(\"retry-after\");\n }\n return (\n error.response.headers[\"retry-after\"] ??\n error.response.headers[\"Retry-After\"]\n );\n }\n\n return undefined;\n}\n\nfunction parseRetryAfterFromMessageMs(\n message: string | undefined\n): number | undefined {\n if (message == null) {\n return undefined;\n }\n\n const match = RETRY_AFTER_MESSAGE_PATTERN.exec(message);\n if (!match) {\n return undefined;\n }\n\n const rawValue = Number(match[1]);\n const unit = match[2]?.toLowerCase();\n if (Number.isNaN(rawValue) || !unit) {\n return undefined;\n }\n\n if (unit === \"ms\" || unit.startsWith(\"millisecond\")) {\n return rawValue;\n }\n\n if (unit === \"m\" || unit.startsWith(\"min\")) {\n return rawValue * 60_000;\n }\n\n if (unit === \"h\" || unit.startsWith(\"hr\") || unit.startsWith(\"hour\")) {\n return rawValue * 3_600_000;\n }\n\n return rawValue * 1000;\n}\n\nfunction coerceError(error: unknown, fallbackMessage: string): Error {\n if (error instanceof Error) {\n return error;\n }\n\n const coerced = new Error(fallbackMessage);\n if (typeof error === \"object\" && error !== null) {\n Object.assign(coerced, error);\n }\n return coerced;\n}\n\nfunction setRateLimitMetadata(\n error: unknown,\n classification: RateLimitClassification\n) {\n if (typeof error !== \"object\" || error === null) {\n return;\n }\n\n const mutableError = error as Record<string, unknown>;\n mutableError.rateLimitType = classification.action;\n mutableError.rateLimitReason = classification.reason;\n\n if (classification.retryAfterMs !== undefined) {\n mutableError.retryAfterMs = classification.retryAfterMs;\n }\n}\n\nexport function parseRetryAfterMs(\n headerValue: string | null | undefined\n): number | undefined {\n if (headerValue == null) {\n return undefined;\n }\n\n const trimmed = headerValue.trim();\n if (!trimmed) {\n return undefined;\n }\n\n const seconds = Number(trimmed);\n if (!Number.isNaN(seconds) && seconds >= 0) {\n return seconds * 1000;\n }\n\n const date = Date.parse(trimmed);\n if (!Number.isNaN(date)) {\n const delayMs = date - Date.now();\n return delayMs > 0 ? delayMs : 0;\n }\n\n return undefined;\n}\n\nexport function classifyRateLimitError(\n error: unknown\n): RateLimitClassification | undefined {\n const status = getResponseStatus(error) ?? getDirectStatus(error);\n if (status !== 429) {\n return undefined;\n }\n\n const code = getErrorCode(error);\n if (code === \"insufficient_quota\") {\n return { action: \"stop\", reason: \"insufficient_quota\" };\n }\n\n const message = getErrorMessage(error);\n if (\n message &&\n QUOTA_EXHAUSTED_MESSAGE_PATTERNS.some((pattern) => pattern.test(message))\n ) {\n return { action: \"stop\", reason: \"quota_message\" };\n }\n\n const retryAfterMs =\n parseRetryAfterMs(_getRetryAfterHeader(error)) ??\n parseRetryAfterFromMessageMs(message);\n\n if (retryAfterMs !== undefined) {\n if (retryAfterMs <= RETRY_AFTER_AUTO_RETRY_THRESHOLD_MS) {\n return {\n action: \"wait\",\n retryAfterMs,\n reason: \"retry_after_hint\",\n };\n }\n\n return {\n action: \"capacity\",\n retryAfterMs,\n reason: \"retry_after_too_large\",\n };\n }\n\n return { action: \"capacity\", reason: \"headerless_429\" };\n}\n\n/**\n * The default failed attempt handler for the AsyncCaller.\n * @param error - The error to handle.\n * @returns void\n */\nconst defaultFailedAttemptHandler = (error: unknown) => {\n if (typeof error !== \"object\" || error === null) {\n return;\n }\n\n if (\n (\"message\" in error &&\n typeof error.message === \"string\" &&\n (error.message.startsWith(\"Cancel\") ||\n error.message.startsWith(\"AbortError\"))) ||\n (\"name\" in error &&\n typeof error.name === \"string\" &&\n error.name === \"AbortError\")\n ) {\n throw error;\n }\n if (\n \"code\" in error &&\n typeof error.code === \"string\" &&\n error.code === \"ECONNABORTED\"\n ) {\n throw error;\n }\n const status = getResponseStatus(error) ?? getDirectStatus(error);\n if (status && STATUS_NO_RETRY.includes(+status)) {\n throw error;\n }\n\n const code = getErrorCode(error);\n if (code === \"insufficient_quota\") {\n const err = coerceError(\n error,\n getErrorMessage(error) ?? \"Insufficient quota\"\n );\n err.name = \"InsufficientQuotaError\";\n setRateLimitMetadata(err, {\n action: \"stop\",\n reason: \"insufficient_quota\",\n });\n throw err;\n }\n\n const rateLimitClassification = classifyRateLimitError(error);\n if (rateLimitClassification) {\n if (rateLimitClassification.action === \"wait\") {\n setRateLimitMetadata(error, rateLimitClassification);\n return;\n }\n\n const err = coerceError(\n error,\n getErrorMessage(error) ?? \"Rate limit exceeded\"\n );\n if (err.name === \"Error\") {\n err.name =\n rateLimitClassification.action === \"stop\"\n ? \"RateLimitQuotaExhaustedError\"\n : \"RateLimitCapacityError\";\n }\n setRateLimitMetadata(err, rateLimitClassification);\n throw err;\n }\n};\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nexport type FailedAttemptHandler = (error: any) => any;\n\nexport interface AsyncCallerParams {\n /**\n * The maximum number of concurrent calls that can be made.\n * Defaults to `Infinity`, which means no limit.\n */\n maxConcurrency?: number;\n /**\n * The maximum number of retries that can be made for a single call,\n * with an exponential backoff between each attempt. Defaults to 6.\n */\n maxRetries?: number;\n /**\n * Custom handler to handle failed attempts. Takes the originally thrown\n * error object as input, and should itself throw an error if the input\n * error is not retryable.\n */\n onFailedAttempt?: FailedAttemptHandler;\n}\n\nexport interface AsyncCallerCallOptions {\n signal?: AbortSignal;\n}\n\n/**\n * A class that can be used to make async calls with concurrency and retry logic.\n *\n * This is useful for making calls to any kind of \"expensive\" external resource,\n * be it because it's rate-limited, subject to network issues, etc.\n *\n * Concurrent calls are limited by the `maxConcurrency` parameter, which defaults\n * to `Infinity`. This means that by default, all calls will be made in parallel.\n *\n * Retries are limited by the `maxRetries` parameter, which defaults to 6. This\n * means that by default, each call will be retried up to 6 times, with an\n * exponential backoff between each attempt.\n */\nexport class AsyncCaller {\n protected maxConcurrency: AsyncCallerParams[\"maxConcurrency\"];\n\n protected maxRetries: AsyncCallerParams[\"maxRetries\"];\n\n protected onFailedAttempt: AsyncCallerParams[\"onFailedAttempt\"];\n\n private queue: (typeof import(\"p-queue\"))[\"default\"][\"prototype\"];\n\n constructor(params: AsyncCallerParams) {\n this.maxConcurrency = params.maxConcurrency ?? Infinity;\n this.maxRetries = params.maxRetries ?? 6;\n this.onFailedAttempt =\n params.onFailedAttempt ?? defaultFailedAttemptHandler;\n\n const PQueue = (\n \"default\" in PQueueMod ? PQueueMod.default : PQueueMod\n ) as typeof PQueueMod;\n this.queue = new PQueue({ concurrency: this.maxConcurrency });\n }\n\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n async call<A extends any[], T extends (...args: A) => Promise<any>>(\n callable: T,\n ...args: Parameters<T>\n ): Promise<Awaited<ReturnType<T>>> {\n return this.queue.add(\n () =>\n pRetry(\n () =>\n callable(...args).catch((error) => {\n // oxlint-disable-next-line no-instanceof/no-instanceof\n if (error instanceof Error) {\n throw error;\n } else {\n throw new Error(error);\n }\n }),\n {\n onFailedAttempt: ({ error }) => this.onFailedAttempt?.(error),\n retries: this.maxRetries,\n randomize: true,\n // If needed we can change some of the defaults here,\n // but they're quite sensible.\n }\n ),\n { throwOnTimeout: true }\n );\n }\n\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n callWithOptions<A extends any[], T extends (...args: A) => Promise<any>>(\n options: AsyncCallerCallOptions,\n callable: T,\n ...args: Parameters<T>\n ): Promise<Awaited<ReturnType<T>>> {\n // Note this doesn't cancel the underlying request,\n // when available prefer to use the signal option of the underlying call\n if (options.signal) {\n let listener: (() => void) | undefined;\n return Promise.race([\n this.call<A, T>(callable, ...args),\n new Promise<never>((_, reject) => {\n listener = () => {\n reject(getAbortSignalError(options.signal));\n };\n options.signal?.addEventListener(\"abort\", listener, { once: true });\n }),\n ]).finally(() => {\n if (options.signal && listener) {\n options.signal.removeEventListener(\"abort\", listener);\n }\n });\n }\n return this.call<A, T>(callable, ...args);\n }\n\n fetch(...args: Parameters<typeof fetch>): ReturnType<typeof fetch> {\n return this.call(() =>\n fetch(...args).then((res) => (res.ok ? res : Promise.reject(res)))\n );\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,12 @@
1
1
  //#region src/utils/async_caller.d.ts
2
+ type RateLimitAction = "wait" | "capacity" | "stop";
3
+ type RateLimitClassification = {
4
+ action: RateLimitAction;
5
+ retryAfterMs?: number;
6
+ reason: string;
7
+ };
8
+ declare function parseRetryAfterMs(headerValue: string | null | undefined): number | undefined;
9
+ declare function classifyRateLimitError(error: unknown): RateLimitClassification | undefined;
2
10
  type FailedAttemptHandler = (error: any) => any;
3
11
  interface AsyncCallerParams {
4
12
  /**
@@ -45,5 +53,5 @@ declare class AsyncCaller {
45
53
  fetch(...args: Parameters<typeof fetch>): ReturnType<typeof fetch>;
46
54
  }
47
55
  //#endregion
48
- export { AsyncCaller, AsyncCallerCallOptions, AsyncCallerParams, FailedAttemptHandler };
56
+ export { AsyncCaller, AsyncCallerCallOptions, AsyncCallerParams, FailedAttemptHandler, classifyRateLimitError, parseRetryAfterMs };
49
57
  //# sourceMappingURL=async_caller.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"async_caller.d.cts","names":[],"sources":["../../src/utils/async_caller.ts"],"mappings":";KAqFY,oBAAA,IAAwB,KAAA;AAAA,UAEnB,iBAAA;EAFe;;;;EAO9B,cAAA;EALgC;;;;EAUhC,UAAA;EAMA;;;;AAGF;EAHE,eAAA,GAAkB,oBAAA;AAAA;AAAA,UAGH,sBAAA;EACf,MAAA,GAAS,WAAA;AAAA;;;;;;;;;;;;;;cAgBE,WAAA;EAAA,UACD,cAAA,EAAgB,iBAAA;EAAA,UAEhB,UAAA,EAAY,iBAAA;EAAA,UAEZ,eAAA,EAAiB,iBAAA;EAAA,QAEnB,KAAA;EAER,WAAA,CAAY,MAAA,EAAQ,iBAAA;EAad,IAAA,gCAAoC,IAAA,EAAM,CAAA,KAAM,OAAA,MAAA,CACpD,QAAA,EAAU,CAAA,KACP,IAAA,EAAM,UAAA,CAAW,CAAA,IACnB,OAAA,CAAQ,OAAA,CAAQ,UAAA,CAAW,CAAA;EA0B9B,eAAA,gCAA+C,IAAA,EAAM,CAAA,KAAM,OAAA,MAAA,CACzD,OAAA,EAAS,sBAAA,EACT,QAAA,EAAU,CAAA,KACP,IAAA,EAAM,UAAA,CAAW,CAAA,IACnB,OAAA,CAAQ,OAAA,CAAQ,UAAA,CAAW,CAAA;EAsB9B,KAAA,CAAA,GAAS,IAAA,EAAM,UAAA,QAAkB,KAAA,IAAS,UAAA,QAAkB,KAAA;AAAA"}
1
+ {"version":3,"file":"async_caller.d.cts","names":[],"sources":["../../src/utils/async_caller.ts"],"mappings":";KAiCK,eAAA;AAAA,KAEA,uBAAA;EACH,MAAA,EAAQ,eAAA;EACR,YAAA;EACA,MAAA;AAAA;AAAA,iBA8Ic,iBAAA,CACd,WAAA;AAAA,iBAyBc,sBAAA,CACd,KAAA,YACC,uBAAA;AAAA,KAgHS,oBAAA,IAAwB,KAAA;AAAA,UAEnB,iBAAA;EA9Rf;;;;EAmSA,cAAA;EAjSM;AA8IR;;;EAwJE,UAAA;EAvJsC;AAyBxC;;;;EAoIE,eAAA,GAAkB,oBAAA;AAAA;AAAA,UAGH,sBAAA;EACf,MAAA,GAAS,WAAA;AAAA;;AApBX;;;;;;;;;;AAmBA;;cAiBa,WAAA;EAAA,UACD,cAAA,EAAgB,iBAAA;EAAA,UAEhB,UAAA,EAAY,iBAAA;EAAA,UAEZ,eAAA,EAAiB,iBAAA;EAAA,QAEnB,KAAA;EAER,WAAA,CAAY,MAAA,EAAQ,iBAAA;EAad,IAAA,gCAAoC,IAAA,EAAM,CAAA,KAAM,OAAA,MAAA,CACpD,QAAA,EAAU,CAAA,KACP,IAAA,EAAM,UAAA,CAAW,CAAA,IACnB,OAAA,CAAQ,OAAA,CAAQ,UAAA,CAAW,CAAA;EA0B9B,eAAA,gCAA+C,IAAA,EAAM,CAAA,KAAM,OAAA,MAAA,CACzD,OAAA,EAAS,sBAAA,EACT,QAAA,EAAU,CAAA,KACP,IAAA,EAAM,UAAA,CAAW,CAAA,IACnB,OAAA,CAAQ,OAAA,CAAQ,UAAA,CAAW,CAAA;EAsB9B,KAAA,CAAA,GAAS,IAAA,EAAM,UAAA,QAAkB,KAAA,IAAS,UAAA,QAAkB,KAAA;AAAA"}
@@ -1,4 +1,12 @@
1
1
  //#region src/utils/async_caller.d.ts
2
+ type RateLimitAction = "wait" | "capacity" | "stop";
3
+ type RateLimitClassification = {
4
+ action: RateLimitAction;
5
+ retryAfterMs?: number;
6
+ reason: string;
7
+ };
8
+ declare function parseRetryAfterMs(headerValue: string | null | undefined): number | undefined;
9
+ declare function classifyRateLimitError(error: unknown): RateLimitClassification | undefined;
2
10
  type FailedAttemptHandler = (error: any) => any;
3
11
  interface AsyncCallerParams {
4
12
  /**
@@ -45,5 +53,5 @@ declare class AsyncCaller {
45
53
  fetch(...args: Parameters<typeof fetch>): ReturnType<typeof fetch>;
46
54
  }
47
55
  //#endregion
48
- export { AsyncCaller, AsyncCallerCallOptions, AsyncCallerParams, FailedAttemptHandler };
56
+ export { AsyncCaller, AsyncCallerCallOptions, AsyncCallerParams, FailedAttemptHandler, classifyRateLimitError, parseRetryAfterMs };
49
57
  //# sourceMappingURL=async_caller.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"async_caller.d.ts","names":[],"sources":["../../src/utils/async_caller.ts"],"mappings":";KAqFY,oBAAA,IAAwB,KAAA;AAAA,UAEnB,iBAAA;EAFe;;;;EAO9B,cAAA;EALgC;;;;EAUhC,UAAA;EAMA;;;;AAGF;EAHE,eAAA,GAAkB,oBAAA;AAAA;AAAA,UAGH,sBAAA;EACf,MAAA,GAAS,WAAA;AAAA;;;;;;;;;;;;;;cAgBE,WAAA;EAAA,UACD,cAAA,EAAgB,iBAAA;EAAA,UAEhB,UAAA,EAAY,iBAAA;EAAA,UAEZ,eAAA,EAAiB,iBAAA;EAAA,QAEnB,KAAA;EAER,WAAA,CAAY,MAAA,EAAQ,iBAAA;EAad,IAAA,gCAAoC,IAAA,EAAM,CAAA,KAAM,OAAA,MAAA,CACpD,QAAA,EAAU,CAAA,KACP,IAAA,EAAM,UAAA,CAAW,CAAA,IACnB,OAAA,CAAQ,OAAA,CAAQ,UAAA,CAAW,CAAA;EA0B9B,eAAA,gCAA+C,IAAA,EAAM,CAAA,KAAM,OAAA,MAAA,CACzD,OAAA,EAAS,sBAAA,EACT,QAAA,EAAU,CAAA,KACP,IAAA,EAAM,UAAA,CAAW,CAAA,IACnB,OAAA,CAAQ,OAAA,CAAQ,UAAA,CAAW,CAAA;EAsB9B,KAAA,CAAA,GAAS,IAAA,EAAM,UAAA,QAAkB,KAAA,IAAS,UAAA,QAAkB,KAAA;AAAA"}
1
+ {"version":3,"file":"async_caller.d.ts","names":[],"sources":["../../src/utils/async_caller.ts"],"mappings":";KAiCK,eAAA;AAAA,KAEA,uBAAA;EACH,MAAA,EAAQ,eAAA;EACR,YAAA;EACA,MAAA;AAAA;AAAA,iBA8Ic,iBAAA,CACd,WAAA;AAAA,iBAyBc,sBAAA,CACd,KAAA,YACC,uBAAA;AAAA,KAgHS,oBAAA,IAAwB,KAAA;AAAA,UAEnB,iBAAA;EA9Rf;;;;EAmSA,cAAA;EAjSM;AA8IR;;;EAwJE,UAAA;EAvJsC;AAyBxC;;;;EAoIE,eAAA,GAAkB,oBAAA;AAAA;AAAA,UAGH,sBAAA;EACf,MAAA,GAAS,WAAA;AAAA;;AApBX;;;;;;;;;;AAmBA;;cAiBa,WAAA;EAAA,UACD,cAAA,EAAgB,iBAAA;EAAA,UAEhB,UAAA,EAAY,iBAAA;EAAA,UAEZ,eAAA,EAAiB,iBAAA;EAAA,QAEnB,KAAA;EAER,WAAA,CAAY,MAAA,EAAQ,iBAAA;EAad,IAAA,gCAAoC,IAAA,EAAM,CAAA,KAAM,OAAA,MAAA,CACpD,QAAA,EAAU,CAAA,KACP,IAAA,EAAM,UAAA,CAAW,CAAA,IACnB,OAAA,CAAQ,OAAA,CAAQ,UAAA,CAAW,CAAA;EA0B9B,eAAA,gCAA+C,IAAA,EAAM,CAAA,KAAM,OAAA,MAAA,CACzD,OAAA,EAAS,sBAAA,EACT,QAAA,EAAU,CAAA,KACP,IAAA,EAAM,UAAA,CAAW,CAAA,IACnB,OAAA,CAAQ,OAAA,CAAQ,UAAA,CAAW,CAAA;EAsB9B,KAAA,CAAA,GAAS,IAAA,EAAM,UAAA,QAAkB,KAAA,IAAS,UAAA,QAAkB,KAAA;AAAA"}
@@ -3,7 +3,11 @@ import { getAbortSignalError } from "./signal.js";
3
3
  import pRetry from "./p-retry/index.js";
4
4
  import PQueueMod from "p-queue";
5
5
  //#region src/utils/async_caller.ts
6
- var async_caller_exports = /* @__PURE__ */ __exportAll({ AsyncCaller: () => AsyncCaller });
6
+ var async_caller_exports = /* @__PURE__ */ __exportAll({
7
+ AsyncCaller: () => AsyncCaller,
8
+ classifyRateLimitError: () => classifyRateLimitError,
9
+ parseRetryAfterMs: () => parseRetryAfterMs
10
+ });
7
11
  const STATUS_NO_RETRY = [
8
12
  400,
9
13
  401,
@@ -15,6 +19,110 @@ const STATUS_NO_RETRY = [
15
19
  407,
16
20
  409
17
21
  ];
22
+ const RETRY_AFTER_AUTO_RETRY_THRESHOLD_MS = 6e4;
23
+ const QUOTA_EXHAUSTED_MESSAGE_PATTERNS = [
24
+ /insufficient[_ -]?quota/i,
25
+ /exceeded (?:your|the current|the available).+quota/i,
26
+ /usage quota/i,
27
+ /quota (?:has been )?exhausted/i,
28
+ /billing/i,
29
+ /credit balance/i,
30
+ /out of credits/i,
31
+ /will reset at/i
32
+ ];
33
+ const RETRY_AFTER_MESSAGE_PATTERN = /(?:try again in|retry after)\s+(\d+(?:\.\d+)?)\s*(milliseconds?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h)\b/i;
34
+ function getResponseStatus(error) {
35
+ return typeof error === "object" && error !== null && "response" in error && typeof error.response === "object" && error.response !== null && "status" in error.response && typeof error.response.status === "number" ? error.response.status : void 0;
36
+ }
37
+ function getDirectStatus(error) {
38
+ if (typeof error !== "object" || error === null) return;
39
+ if ("status" in error && typeof error.status === "number") return error.status;
40
+ if ("statusCode" in error && typeof error.statusCode === "number") return error.statusCode;
41
+ }
42
+ function getErrorMessage(error) {
43
+ return typeof error === "object" && error !== null && "message" in error && typeof error.message === "string" ? error.message : void 0;
44
+ }
45
+ function getErrorCode(error) {
46
+ if (typeof error !== "object" || error === null) return;
47
+ if ("code" in error && typeof error.code === "string") return error.code;
48
+ return "error" in error && typeof error.error === "object" && error.error !== null && "code" in error.error && typeof error.error.code === "string" ? error.error.code : void 0;
49
+ }
50
+ function _getRetryAfterHeader(error) {
51
+ if (error?.headers) {
52
+ if (typeof error.headers.get === "function") return error.headers.get("retry-after");
53
+ return error.headers["retry-after"] ?? error.headers["Retry-After"];
54
+ }
55
+ if (error?.response?.headers) {
56
+ if (typeof error.response.headers.get === "function") return error.response.headers.get("retry-after");
57
+ return error.response.headers["retry-after"] ?? error.response.headers["Retry-After"];
58
+ }
59
+ }
60
+ function parseRetryAfterFromMessageMs(message) {
61
+ if (message == null) return;
62
+ const match = RETRY_AFTER_MESSAGE_PATTERN.exec(message);
63
+ if (!match) return;
64
+ const rawValue = Number(match[1]);
65
+ const unit = match[2]?.toLowerCase();
66
+ if (Number.isNaN(rawValue) || !unit) return;
67
+ if (unit === "ms" || unit.startsWith("millisecond")) return rawValue;
68
+ if (unit === "m" || unit.startsWith("min")) return rawValue * 6e4;
69
+ if (unit === "h" || unit.startsWith("hr") || unit.startsWith("hour")) return rawValue * 36e5;
70
+ return rawValue * 1e3;
71
+ }
72
+ function coerceError(error, fallbackMessage) {
73
+ if (error instanceof Error) return error;
74
+ const coerced = new Error(fallbackMessage);
75
+ if (typeof error === "object" && error !== null) Object.assign(coerced, error);
76
+ return coerced;
77
+ }
78
+ function setRateLimitMetadata(error, classification) {
79
+ if (typeof error !== "object" || error === null) return;
80
+ const mutableError = error;
81
+ mutableError.rateLimitType = classification.action;
82
+ mutableError.rateLimitReason = classification.reason;
83
+ if (classification.retryAfterMs !== void 0) mutableError.retryAfterMs = classification.retryAfterMs;
84
+ }
85
+ function parseRetryAfterMs(headerValue) {
86
+ if (headerValue == null) return;
87
+ const trimmed = headerValue.trim();
88
+ if (!trimmed) return;
89
+ const seconds = Number(trimmed);
90
+ if (!Number.isNaN(seconds) && seconds >= 0) return seconds * 1e3;
91
+ const date = Date.parse(trimmed);
92
+ if (!Number.isNaN(date)) {
93
+ const delayMs = date - Date.now();
94
+ return delayMs > 0 ? delayMs : 0;
95
+ }
96
+ }
97
+ function classifyRateLimitError(error) {
98
+ if ((getResponseStatus(error) ?? getDirectStatus(error)) !== 429) return;
99
+ if (getErrorCode(error) === "insufficient_quota") return {
100
+ action: "stop",
101
+ reason: "insufficient_quota"
102
+ };
103
+ const message = getErrorMessage(error);
104
+ if (message && QUOTA_EXHAUSTED_MESSAGE_PATTERNS.some((pattern) => pattern.test(message))) return {
105
+ action: "stop",
106
+ reason: "quota_message"
107
+ };
108
+ const retryAfterMs = parseRetryAfterMs(_getRetryAfterHeader(error)) ?? parseRetryAfterFromMessageMs(message);
109
+ if (retryAfterMs !== void 0) {
110
+ if (retryAfterMs <= RETRY_AFTER_AUTO_RETRY_THRESHOLD_MS) return {
111
+ action: "wait",
112
+ retryAfterMs,
113
+ reason: "retry_after_hint"
114
+ };
115
+ return {
116
+ action: "capacity",
117
+ retryAfterMs,
118
+ reason: "retry_after_too_large"
119
+ };
120
+ }
121
+ return {
122
+ action: "capacity",
123
+ reason: "headerless_429"
124
+ };
125
+ }
18
126
  /**
19
127
  * The default failed attempt handler for the AsyncCaller.
20
128
  * @param error - The error to handle.
@@ -24,13 +132,26 @@ const defaultFailedAttemptHandler = (error) => {
24
132
  if (typeof error !== "object" || error === null) return;
25
133
  if ("message" in error && typeof error.message === "string" && (error.message.startsWith("Cancel") || error.message.startsWith("AbortError")) || "name" in error && typeof error.name === "string" && error.name === "AbortError") throw error;
26
134
  if ("code" in error && typeof error.code === "string" && error.code === "ECONNABORTED") throw error;
27
- const responseStatus = "response" in error && typeof error.response === "object" && error.response !== null && "status" in error.response && typeof error.response.status === "number" ? error.response.status : void 0;
28
- const directStatus = "status" in error && typeof error.status === "number" ? error.status : void 0;
29
- const status = responseStatus ?? directStatus;
135
+ const status = getResponseStatus(error) ?? getDirectStatus(error);
30
136
  if (status && STATUS_NO_RETRY.includes(+status)) throw error;
31
- if (("error" in error && typeof error.error === "object" && error.error !== null && "code" in error.error && typeof error.error.code === "string" ? error.error.code : void 0) === "insufficient_quota") {
32
- const err = new Error("message" in error && typeof error.message === "string" ? error.message : "Insufficient quota");
137
+ if (getErrorCode(error) === "insufficient_quota") {
138
+ const err = coerceError(error, getErrorMessage(error) ?? "Insufficient quota");
33
139
  err.name = "InsufficientQuotaError";
140
+ setRateLimitMetadata(err, {
141
+ action: "stop",
142
+ reason: "insufficient_quota"
143
+ });
144
+ throw err;
145
+ }
146
+ const rateLimitClassification = classifyRateLimitError(error);
147
+ if (rateLimitClassification) {
148
+ if (rateLimitClassification.action === "wait") {
149
+ setRateLimitMetadata(error, rateLimitClassification);
150
+ return;
151
+ }
152
+ const err = coerceError(error, getErrorMessage(error) ?? "Rate limit exceeded");
153
+ if (err.name === "Error") err.name = rateLimitClassification.action === "stop" ? "RateLimitQuotaExhaustedError" : "RateLimitCapacityError";
154
+ setRateLimitMetadata(err, rateLimitClassification);
34
155
  throw err;
35
156
  }
36
157
  };
@@ -88,6 +209,6 @@ var AsyncCaller = class {
88
209
  }
89
210
  };
90
211
  //#endregion
91
- export { AsyncCaller, async_caller_exports };
212
+ export { AsyncCaller, async_caller_exports, classifyRateLimitError, parseRetryAfterMs };
92
213
 
93
214
  //# sourceMappingURL=async_caller.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"async_caller.js","names":[],"sources":["../../src/utils/async_caller.ts"],"sourcesContent":["import PQueueMod from \"p-queue\";\n\nimport { getAbortSignalError } from \"./signal.js\";\nimport pRetry from \"./p-retry/index.js\";\n\nconst STATUS_NO_RETRY = [\n 400, // Bad Request\n 401, // Unauthorized\n 402, // Payment Required\n 403, // Forbidden\n 404, // Not Found\n 405, // Method Not Allowed\n 406, // Not Acceptable\n 407, // Proxy Authentication Required\n 409, // Conflict\n];\n\n/**\n * The default failed attempt handler for the AsyncCaller.\n * @param error - The error to handle.\n * @returns void\n */\nconst defaultFailedAttemptHandler = (error: unknown) => {\n if (typeof error !== \"object\" || error === null) {\n return;\n }\n\n if (\n (\"message\" in error &&\n typeof error.message === \"string\" &&\n (error.message.startsWith(\"Cancel\") ||\n error.message.startsWith(\"AbortError\"))) ||\n (\"name\" in error &&\n typeof error.name === \"string\" &&\n error.name === \"AbortError\")\n ) {\n throw error;\n }\n if (\n \"code\" in error &&\n typeof error.code === \"string\" &&\n error.code === \"ECONNABORTED\"\n ) {\n throw error;\n }\n const responseStatus =\n \"response\" in error &&\n typeof error.response === \"object\" &&\n error.response !== null &&\n \"status\" in error.response &&\n typeof error.response.status === \"number\"\n ? error.response.status\n : undefined;\n\n // OpenAI SDK errors expose status directly on the error object\n const directStatus =\n \"status\" in error && typeof error.status === \"number\"\n ? error.status\n : undefined;\n\n const status = responseStatus ?? directStatus;\n if (status && STATUS_NO_RETRY.includes(+status)) {\n throw error;\n }\n\n const code =\n \"error\" in error &&\n typeof error.error === \"object\" &&\n error.error !== null &&\n \"code\" in error.error &&\n typeof error.error.code === \"string\"\n ? error.error.code\n : undefined;\n if (code === \"insufficient_quota\") {\n const err = new Error(\n \"message\" in error && typeof error.message === \"string\"\n ? error.message\n : \"Insufficient quota\"\n );\n err.name = \"InsufficientQuotaError\";\n throw err;\n }\n};\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nexport type FailedAttemptHandler = (error: any) => any;\n\nexport interface AsyncCallerParams {\n /**\n * The maximum number of concurrent calls that can be made.\n * Defaults to `Infinity`, which means no limit.\n */\n maxConcurrency?: number;\n /**\n * The maximum number of retries that can be made for a single call,\n * with an exponential backoff between each attempt. Defaults to 6.\n */\n maxRetries?: number;\n /**\n * Custom handler to handle failed attempts. Takes the originally thrown\n * error object as input, and should itself throw an error if the input\n * error is not retryable.\n */\n onFailedAttempt?: FailedAttemptHandler;\n}\n\nexport interface AsyncCallerCallOptions {\n signal?: AbortSignal;\n}\n\n/**\n * A class that can be used to make async calls with concurrency and retry logic.\n *\n * This is useful for making calls to any kind of \"expensive\" external resource,\n * be it because it's rate-limited, subject to network issues, etc.\n *\n * Concurrent calls are limited by the `maxConcurrency` parameter, which defaults\n * to `Infinity`. This means that by default, all calls will be made in parallel.\n *\n * Retries are limited by the `maxRetries` parameter, which defaults to 6. This\n * means that by default, each call will be retried up to 6 times, with an\n * exponential backoff between each attempt.\n */\nexport class AsyncCaller {\n protected maxConcurrency: AsyncCallerParams[\"maxConcurrency\"];\n\n protected maxRetries: AsyncCallerParams[\"maxRetries\"];\n\n protected onFailedAttempt: AsyncCallerParams[\"onFailedAttempt\"];\n\n private queue: (typeof import(\"p-queue\"))[\"default\"][\"prototype\"];\n\n constructor(params: AsyncCallerParams) {\n this.maxConcurrency = params.maxConcurrency ?? Infinity;\n this.maxRetries = params.maxRetries ?? 6;\n this.onFailedAttempt =\n params.onFailedAttempt ?? defaultFailedAttemptHandler;\n\n const PQueue = (\n \"default\" in PQueueMod ? PQueueMod.default : PQueueMod\n ) as typeof PQueueMod;\n this.queue = new PQueue({ concurrency: this.maxConcurrency });\n }\n\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n async call<A extends any[], T extends (...args: A) => Promise<any>>(\n callable: T,\n ...args: Parameters<T>\n ): Promise<Awaited<ReturnType<T>>> {\n return this.queue.add(\n () =>\n pRetry(\n () =>\n callable(...args).catch((error) => {\n // oxlint-disable-next-line no-instanceof/no-instanceof\n if (error instanceof Error) {\n throw error;\n } else {\n throw new Error(error);\n }\n }),\n {\n onFailedAttempt: ({ error }) => this.onFailedAttempt?.(error),\n retries: this.maxRetries,\n randomize: true,\n // If needed we can change some of the defaults here,\n // but they're quite sensible.\n }\n ),\n { throwOnTimeout: true }\n );\n }\n\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n callWithOptions<A extends any[], T extends (...args: A) => Promise<any>>(\n options: AsyncCallerCallOptions,\n callable: T,\n ...args: Parameters<T>\n ): Promise<Awaited<ReturnType<T>>> {\n // Note this doesn't cancel the underlying request,\n // when available prefer to use the signal option of the underlying call\n if (options.signal) {\n let listener: (() => void) | undefined;\n return Promise.race([\n this.call<A, T>(callable, ...args),\n new Promise<never>((_, reject) => {\n listener = () => {\n reject(getAbortSignalError(options.signal));\n };\n options.signal?.addEventListener(\"abort\", listener, { once: true });\n }),\n ]).finally(() => {\n if (options.signal && listener) {\n options.signal.removeEventListener(\"abort\", listener);\n }\n });\n }\n return this.call<A, T>(callable, ...args);\n }\n\n fetch(...args: Parameters<typeof fetch>): ReturnType<typeof fetch> {\n return this.call(() =>\n fetch(...args).then((res) => (res.ok ? res : Promise.reject(res)))\n );\n }\n}\n"],"mappings":";;;;;;AAKA,MAAM,kBAAkB;CACtB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;AAOD,MAAM,+BAA+B,UAAmB;AACtD,KAAI,OAAO,UAAU,YAAY,UAAU,KACzC;AAGF,KACG,aAAa,SACZ,OAAO,MAAM,YAAY,aACxB,MAAM,QAAQ,WAAW,SAAS,IACjC,MAAM,QAAQ,WAAW,aAAa,KACzC,UAAU,SACT,OAAO,MAAM,SAAS,YACtB,MAAM,SAAS,aAEjB,OAAM;AAER,KACE,UAAU,SACV,OAAO,MAAM,SAAS,YACtB,MAAM,SAAS,eAEf,OAAM;CAER,MAAM,iBACJ,cAAc,SACd,OAAO,MAAM,aAAa,YAC1B,MAAM,aAAa,QACnB,YAAY,MAAM,YAClB,OAAO,MAAM,SAAS,WAAW,WAC7B,MAAM,SAAS,SACf,KAAA;CAGN,MAAM,eACJ,YAAY,SAAS,OAAO,MAAM,WAAW,WACzC,MAAM,SACN,KAAA;CAEN,MAAM,SAAS,kBAAkB;AACjC,KAAI,UAAU,gBAAgB,SAAS,CAAC,OAAO,CAC7C,OAAM;AAWR,MAPE,WAAW,SACX,OAAO,MAAM,UAAU,YACvB,MAAM,UAAU,QAChB,UAAU,MAAM,SAChB,OAAO,MAAM,MAAM,SAAS,WACxB,MAAM,MAAM,OACZ,KAAA,OACO,sBAAsB;EACjC,MAAM,MAAM,IAAI,MACd,aAAa,SAAS,OAAO,MAAM,YAAY,WAC3C,MAAM,UACN,qBACL;AACD,MAAI,OAAO;AACX,QAAM;;;;;;;;;;;;;;;;AA2CV,IAAa,cAAb,MAAyB;CACvB;CAEA;CAEA;CAEA;CAEA,YAAY,QAA2B;AACrC,OAAK,iBAAiB,OAAO,kBAAkB;AAC/C,OAAK,aAAa,OAAO,cAAc;AACvC,OAAK,kBACH,OAAO,mBAAmB;EAE5B,MAAM,SACJ,aAAa,YAAY,UAAU,UAAU;AAE/C,OAAK,QAAQ,IAAI,OAAO,EAAE,aAAa,KAAK,gBAAgB,CAAC;;CAI/D,MAAM,KACJ,UACA,GAAG,MAC8B;AACjC,SAAO,KAAK,MAAM,UAEd,aAEI,SAAS,GAAG,KAAK,CAAC,OAAO,UAAU;AAEjC,OAAI,iBAAiB,MACnB,OAAM;OAEN,OAAM,IAAI,MAAM,MAAM;IAExB,EACJ;GACE,kBAAkB,EAAE,YAAY,KAAK,kBAAkB,MAAM;GAC7D,SAAS,KAAK;GACd,WAAW;GAGZ,CACF,EACH,EAAE,gBAAgB,MAAM,CACzB;;CAIH,gBACE,SACA,UACA,GAAG,MAC8B;AAGjC,MAAI,QAAQ,QAAQ;GAClB,IAAI;AACJ,UAAO,QAAQ,KAAK,CAClB,KAAK,KAAW,UAAU,GAAG,KAAK,EAClC,IAAI,SAAgB,GAAG,WAAW;AAChC,qBAAiB;AACf,YAAO,oBAAoB,QAAQ,OAAO,CAAC;;AAE7C,YAAQ,QAAQ,iBAAiB,SAAS,UAAU,EAAE,MAAM,MAAM,CAAC;KACnE,CACH,CAAC,CAAC,cAAc;AACf,QAAI,QAAQ,UAAU,SACpB,SAAQ,OAAO,oBAAoB,SAAS,SAAS;KAEvD;;AAEJ,SAAO,KAAK,KAAW,UAAU,GAAG,KAAK;;CAG3C,MAAM,GAAG,MAA0D;AACjE,SAAO,KAAK,WACV,MAAM,GAAG,KAAK,CAAC,MAAM,QAAS,IAAI,KAAK,MAAM,QAAQ,OAAO,IAAI,CAAE,CACnE"}
1
+ {"version":3,"file":"async_caller.js","names":[],"sources":["../../src/utils/async_caller.ts"],"sourcesContent":["import PQueueMod from \"p-queue\";\n\nimport { getAbortSignalError } from \"./signal.js\";\nimport pRetry from \"./p-retry/index.js\";\n\nconst STATUS_NO_RETRY = [\n 400, // Bad Request\n 401, // Unauthorized\n 402, // Payment Required\n 403, // Forbidden\n 404, // Not Found\n 405, // Method Not Allowed\n 406, // Not Acceptable\n 407, // Proxy Authentication Required\n 409, // Conflict\n];\n\nconst RETRY_AFTER_AUTO_RETRY_THRESHOLD_MS = 60_000;\n\nconst QUOTA_EXHAUSTED_MESSAGE_PATTERNS = [\n /insufficient[_ -]?quota/i,\n /exceeded (?:your|the current|the available).+quota/i,\n /usage quota/i,\n /quota (?:has been )?exhausted/i,\n /billing/i,\n /credit balance/i,\n /out of credits/i,\n /will reset at/i,\n];\n\nconst RETRY_AFTER_MESSAGE_PATTERN =\n /(?:try again in|retry after)\\s+(\\d+(?:\\.\\d+)?)\\s*(milliseconds?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h)\\b/i;\n\ntype RateLimitAction = \"wait\" | \"capacity\" | \"stop\";\n\ntype RateLimitClassification = {\n action: RateLimitAction;\n retryAfterMs?: number;\n reason: string;\n};\n\nfunction getResponseStatus(error: unknown): number | undefined {\n return typeof error === \"object\" &&\n error !== null &&\n \"response\" in error &&\n typeof error.response === \"object\" &&\n error.response !== null &&\n \"status\" in error.response &&\n typeof error.response.status === \"number\"\n ? error.response.status\n : undefined;\n}\n\nfunction getDirectStatus(error: unknown): number | undefined {\n if (typeof error !== \"object\" || error === null) {\n return undefined;\n }\n\n if (\"status\" in error && typeof error.status === \"number\") {\n return error.status;\n }\n\n if (\"statusCode\" in error && typeof error.statusCode === \"number\") {\n return error.statusCode;\n }\n\n return undefined;\n}\n\nfunction getErrorMessage(error: unknown): string | undefined {\n return typeof error === \"object\" &&\n error !== null &&\n \"message\" in error &&\n typeof error.message === \"string\"\n ? error.message\n : undefined;\n}\n\nfunction getErrorCode(error: unknown): string | undefined {\n if (typeof error !== \"object\" || error === null) {\n return undefined;\n }\n\n if (\"code\" in error && typeof error.code === \"string\") {\n return error.code;\n }\n\n return \"error\" in error &&\n typeof error.error === \"object\" &&\n error.error !== null &&\n \"code\" in error.error &&\n typeof error.error.code === \"string\"\n ? error.error.code\n : undefined;\n}\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction _getRetryAfterHeader(error: any): string | null | undefined {\n if (error?.headers) {\n if (typeof error.headers.get === \"function\") {\n return error.headers.get(\"retry-after\");\n }\n return error.headers[\"retry-after\"] ?? error.headers[\"Retry-After\"];\n }\n\n if (error?.response?.headers) {\n if (typeof error.response.headers.get === \"function\") {\n return error.response.headers.get(\"retry-after\");\n }\n return (\n error.response.headers[\"retry-after\"] ??\n error.response.headers[\"Retry-After\"]\n );\n }\n\n return undefined;\n}\n\nfunction parseRetryAfterFromMessageMs(\n message: string | undefined\n): number | undefined {\n if (message == null) {\n return undefined;\n }\n\n const match = RETRY_AFTER_MESSAGE_PATTERN.exec(message);\n if (!match) {\n return undefined;\n }\n\n const rawValue = Number(match[1]);\n const unit = match[2]?.toLowerCase();\n if (Number.isNaN(rawValue) || !unit) {\n return undefined;\n }\n\n if (unit === \"ms\" || unit.startsWith(\"millisecond\")) {\n return rawValue;\n }\n\n if (unit === \"m\" || unit.startsWith(\"min\")) {\n return rawValue * 60_000;\n }\n\n if (unit === \"h\" || unit.startsWith(\"hr\") || unit.startsWith(\"hour\")) {\n return rawValue * 3_600_000;\n }\n\n return rawValue * 1000;\n}\n\nfunction coerceError(error: unknown, fallbackMessage: string): Error {\n if (error instanceof Error) {\n return error;\n }\n\n const coerced = new Error(fallbackMessage);\n if (typeof error === \"object\" && error !== null) {\n Object.assign(coerced, error);\n }\n return coerced;\n}\n\nfunction setRateLimitMetadata(\n error: unknown,\n classification: RateLimitClassification\n) {\n if (typeof error !== \"object\" || error === null) {\n return;\n }\n\n const mutableError = error as Record<string, unknown>;\n mutableError.rateLimitType = classification.action;\n mutableError.rateLimitReason = classification.reason;\n\n if (classification.retryAfterMs !== undefined) {\n mutableError.retryAfterMs = classification.retryAfterMs;\n }\n}\n\nexport function parseRetryAfterMs(\n headerValue: string | null | undefined\n): number | undefined {\n if (headerValue == null) {\n return undefined;\n }\n\n const trimmed = headerValue.trim();\n if (!trimmed) {\n return undefined;\n }\n\n const seconds = Number(trimmed);\n if (!Number.isNaN(seconds) && seconds >= 0) {\n return seconds * 1000;\n }\n\n const date = Date.parse(trimmed);\n if (!Number.isNaN(date)) {\n const delayMs = date - Date.now();\n return delayMs > 0 ? delayMs : 0;\n }\n\n return undefined;\n}\n\nexport function classifyRateLimitError(\n error: unknown\n): RateLimitClassification | undefined {\n const status = getResponseStatus(error) ?? getDirectStatus(error);\n if (status !== 429) {\n return undefined;\n }\n\n const code = getErrorCode(error);\n if (code === \"insufficient_quota\") {\n return { action: \"stop\", reason: \"insufficient_quota\" };\n }\n\n const message = getErrorMessage(error);\n if (\n message &&\n QUOTA_EXHAUSTED_MESSAGE_PATTERNS.some((pattern) => pattern.test(message))\n ) {\n return { action: \"stop\", reason: \"quota_message\" };\n }\n\n const retryAfterMs =\n parseRetryAfterMs(_getRetryAfterHeader(error)) ??\n parseRetryAfterFromMessageMs(message);\n\n if (retryAfterMs !== undefined) {\n if (retryAfterMs <= RETRY_AFTER_AUTO_RETRY_THRESHOLD_MS) {\n return {\n action: \"wait\",\n retryAfterMs,\n reason: \"retry_after_hint\",\n };\n }\n\n return {\n action: \"capacity\",\n retryAfterMs,\n reason: \"retry_after_too_large\",\n };\n }\n\n return { action: \"capacity\", reason: \"headerless_429\" };\n}\n\n/**\n * The default failed attempt handler for the AsyncCaller.\n * @param error - The error to handle.\n * @returns void\n */\nconst defaultFailedAttemptHandler = (error: unknown) => {\n if (typeof error !== \"object\" || error === null) {\n return;\n }\n\n if (\n (\"message\" in error &&\n typeof error.message === \"string\" &&\n (error.message.startsWith(\"Cancel\") ||\n error.message.startsWith(\"AbortError\"))) ||\n (\"name\" in error &&\n typeof error.name === \"string\" &&\n error.name === \"AbortError\")\n ) {\n throw error;\n }\n if (\n \"code\" in error &&\n typeof error.code === \"string\" &&\n error.code === \"ECONNABORTED\"\n ) {\n throw error;\n }\n const status = getResponseStatus(error) ?? getDirectStatus(error);\n if (status && STATUS_NO_RETRY.includes(+status)) {\n throw error;\n }\n\n const code = getErrorCode(error);\n if (code === \"insufficient_quota\") {\n const err = coerceError(\n error,\n getErrorMessage(error) ?? \"Insufficient quota\"\n );\n err.name = \"InsufficientQuotaError\";\n setRateLimitMetadata(err, {\n action: \"stop\",\n reason: \"insufficient_quota\",\n });\n throw err;\n }\n\n const rateLimitClassification = classifyRateLimitError(error);\n if (rateLimitClassification) {\n if (rateLimitClassification.action === \"wait\") {\n setRateLimitMetadata(error, rateLimitClassification);\n return;\n }\n\n const err = coerceError(\n error,\n getErrorMessage(error) ?? \"Rate limit exceeded\"\n );\n if (err.name === \"Error\") {\n err.name =\n rateLimitClassification.action === \"stop\"\n ? \"RateLimitQuotaExhaustedError\"\n : \"RateLimitCapacityError\";\n }\n setRateLimitMetadata(err, rateLimitClassification);\n throw err;\n }\n};\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nexport type FailedAttemptHandler = (error: any) => any;\n\nexport interface AsyncCallerParams {\n /**\n * The maximum number of concurrent calls that can be made.\n * Defaults to `Infinity`, which means no limit.\n */\n maxConcurrency?: number;\n /**\n * The maximum number of retries that can be made for a single call,\n * with an exponential backoff between each attempt. Defaults to 6.\n */\n maxRetries?: number;\n /**\n * Custom handler to handle failed attempts. Takes the originally thrown\n * error object as input, and should itself throw an error if the input\n * error is not retryable.\n */\n onFailedAttempt?: FailedAttemptHandler;\n}\n\nexport interface AsyncCallerCallOptions {\n signal?: AbortSignal;\n}\n\n/**\n * A class that can be used to make async calls with concurrency and retry logic.\n *\n * This is useful for making calls to any kind of \"expensive\" external resource,\n * be it because it's rate-limited, subject to network issues, etc.\n *\n * Concurrent calls are limited by the `maxConcurrency` parameter, which defaults\n * to `Infinity`. This means that by default, all calls will be made in parallel.\n *\n * Retries are limited by the `maxRetries` parameter, which defaults to 6. This\n * means that by default, each call will be retried up to 6 times, with an\n * exponential backoff between each attempt.\n */\nexport class AsyncCaller {\n protected maxConcurrency: AsyncCallerParams[\"maxConcurrency\"];\n\n protected maxRetries: AsyncCallerParams[\"maxRetries\"];\n\n protected onFailedAttempt: AsyncCallerParams[\"onFailedAttempt\"];\n\n private queue: (typeof import(\"p-queue\"))[\"default\"][\"prototype\"];\n\n constructor(params: AsyncCallerParams) {\n this.maxConcurrency = params.maxConcurrency ?? Infinity;\n this.maxRetries = params.maxRetries ?? 6;\n this.onFailedAttempt =\n params.onFailedAttempt ?? defaultFailedAttemptHandler;\n\n const PQueue = (\n \"default\" in PQueueMod ? PQueueMod.default : PQueueMod\n ) as typeof PQueueMod;\n this.queue = new PQueue({ concurrency: this.maxConcurrency });\n }\n\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n async call<A extends any[], T extends (...args: A) => Promise<any>>(\n callable: T,\n ...args: Parameters<T>\n ): Promise<Awaited<ReturnType<T>>> {\n return this.queue.add(\n () =>\n pRetry(\n () =>\n callable(...args).catch((error) => {\n // oxlint-disable-next-line no-instanceof/no-instanceof\n if (error instanceof Error) {\n throw error;\n } else {\n throw new Error(error);\n }\n }),\n {\n onFailedAttempt: ({ error }) => this.onFailedAttempt?.(error),\n retries: this.maxRetries,\n randomize: true,\n // If needed we can change some of the defaults here,\n // but they're quite sensible.\n }\n ),\n { throwOnTimeout: true }\n );\n }\n\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n callWithOptions<A extends any[], T extends (...args: A) => Promise<any>>(\n options: AsyncCallerCallOptions,\n callable: T,\n ...args: Parameters<T>\n ): Promise<Awaited<ReturnType<T>>> {\n // Note this doesn't cancel the underlying request,\n // when available prefer to use the signal option of the underlying call\n if (options.signal) {\n let listener: (() => void) | undefined;\n return Promise.race([\n this.call<A, T>(callable, ...args),\n new Promise<never>((_, reject) => {\n listener = () => {\n reject(getAbortSignalError(options.signal));\n };\n options.signal?.addEventListener(\"abort\", listener, { once: true });\n }),\n ]).finally(() => {\n if (options.signal && listener) {\n options.signal.removeEventListener(\"abort\", listener);\n }\n });\n }\n return this.call<A, T>(callable, ...args);\n }\n\n fetch(...args: Parameters<typeof fetch>): ReturnType<typeof fetch> {\n return this.call(() =>\n fetch(...args).then((res) => (res.ok ? res : Promise.reject(res)))\n );\n }\n}\n"],"mappings":"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"}
@@ -63,7 +63,9 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
63
63
  options.signal?.throwIfAborted();
64
64
  return false;
65
65
  }
66
- const delayTime = calculateDelay(retriesConsumed, options);
66
+ let delayTime = calculateDelay(retriesConsumed, options);
67
+ const retryAfterMs = typeof normalizedError.retryAfterMs === "number" && normalizedError.retryAfterMs >= 0 ? normalizedError.retryAfterMs : void 0;
68
+ if (retryAfterMs !== void 0) delayTime = Math.max(delayTime, retryAfterMs);
67
69
  const finalDelay = Math.min(delayTime, remainingTime);
68
70
  if (finalDelay > 0) await new Promise((resolve, reject) => {
69
71
  const onAbort = () => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["isNetworkError"],"sources":["../../../src/utils/p-retry/index.js"],"sourcesContent":["/* oxlint-disable */\nimport isNetworkError from \"../is-network-error/index.js\";\n\nfunction validateRetries(retries) {\n if (typeof retries === \"number\") {\n if (retries < 0) {\n throw new TypeError(\"Expected `retries` to be a non-negative number.\");\n }\n\n if (Number.isNaN(retries)) {\n throw new TypeError(\n \"Expected `retries` to be a valid number or Infinity, got NaN.\"\n );\n }\n } else if (retries !== undefined) {\n throw new TypeError(\"Expected `retries` to be a number or Infinity.\");\n }\n}\n\nfunction validateNumberOption(\n name,\n value,\n { min = 0, allowInfinity = false } = {}\n) {\n if (value === undefined) {\n return;\n }\n\n if (typeof value !== \"number\" || Number.isNaN(value)) {\n throw new TypeError(\n `Expected \\`${name}\\` to be a number${\n allowInfinity ? \" or Infinity\" : \"\"\n }.`\n );\n }\n\n if (!allowInfinity && !Number.isFinite(value)) {\n throw new TypeError(`Expected \\`${name}\\` to be a finite number.`);\n }\n\n if (value < min) {\n throw new TypeError(`Expected \\`${name}\\` to be \\u2265 ${min}.`);\n }\n}\n\nexport class AbortError extends Error {\n constructor(message) {\n super();\n\n if (message instanceof Error) {\n this.originalError = message;\n ({ message } = message);\n } else {\n this.originalError = new Error(message);\n this.originalError.stack = this.stack;\n }\n\n this.name = \"AbortError\";\n this.message = message;\n }\n}\n\nfunction calculateDelay(retriesConsumed, options) {\n const attempt = Math.max(1, retriesConsumed + 1);\n const random = options.randomize ? Math.random() + 1 : 1;\n\n let timeout = Math.round(\n random * options.minTimeout * options.factor ** (attempt - 1)\n );\n timeout = Math.min(timeout, options.maxTimeout);\n\n return timeout;\n}\n\nfunction calculateRemainingTime(start, max) {\n if (!Number.isFinite(max)) {\n return max;\n }\n\n return max - (performance.now() - start);\n}\n\nasync function onAttemptFailure({\n error,\n attemptNumber,\n retriesConsumed,\n startTime,\n options,\n}) {\n const normalizedError =\n error instanceof Error\n ? error\n : new TypeError(\n `Non-error was thrown: \"${error}\". You should only throw errors.`\n );\n\n if (normalizedError instanceof AbortError) {\n throw normalizedError.originalError;\n }\n\n const retriesLeft = Number.isFinite(options.retries)\n ? Math.max(0, options.retries - retriesConsumed)\n : options.retries;\n\n const maxRetryTime = options.maxRetryTime ?? Number.POSITIVE_INFINITY;\n\n const context = Object.freeze({\n error: normalizedError,\n attemptNumber,\n retriesLeft,\n retriesConsumed,\n });\n\n await options.onFailedAttempt(context);\n\n if (calculateRemainingTime(startTime, maxRetryTime) <= 0) {\n throw normalizedError;\n }\n\n const consumeRetry = await options.shouldConsumeRetry(context);\n\n const remainingTime = calculateRemainingTime(startTime, maxRetryTime);\n\n if (remainingTime <= 0 || retriesLeft <= 0) {\n throw normalizedError;\n }\n\n if (\n normalizedError instanceof TypeError &&\n !isNetworkError(normalizedError)\n ) {\n if (consumeRetry) {\n throw normalizedError;\n }\n\n options.signal?.throwIfAborted();\n return false;\n }\n\n if (!(await options.shouldRetry(context))) {\n throw normalizedError;\n }\n\n if (!consumeRetry) {\n options.signal?.throwIfAborted();\n return false;\n }\n\n const delayTime = calculateDelay(retriesConsumed, options);\n const finalDelay = Math.min(delayTime, remainingTime);\n\n if (finalDelay > 0) {\n await new Promise((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timeoutToken);\n options.signal?.removeEventListener(\"abort\", onAbort);\n reject(options.signal.reason);\n };\n\n const timeoutToken = setTimeout(() => {\n options.signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, finalDelay);\n\n if (options.unref) {\n timeoutToken.unref?.();\n }\n\n options.signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n }\n\n options.signal?.throwIfAborted();\n\n return true;\n}\n\nexport default async function pRetry(input, options = {}) {\n options = { ...options };\n\n validateRetries(options.retries);\n\n if (Object.hasOwn(options, \"forever\")) {\n throw new Error(\n \"The `forever` option is no longer supported. For many use-cases, you can set `retries: Infinity` instead.\"\n );\n }\n\n options.retries ??= 10;\n options.factor ??= 2;\n options.minTimeout ??= 1000;\n options.maxTimeout ??= Number.POSITIVE_INFINITY;\n options.maxRetryTime ??= Number.POSITIVE_INFINITY;\n options.randomize ??= false;\n options.onFailedAttempt ??= () => {};\n options.shouldRetry ??= () => true;\n options.shouldConsumeRetry ??= () => true;\n\n // Validate numeric options and normalize edge cases\n validateNumberOption(\"factor\", options.factor, {\n min: 0,\n allowInfinity: false,\n });\n validateNumberOption(\"minTimeout\", options.minTimeout, {\n min: 0,\n allowInfinity: false,\n });\n validateNumberOption(\"maxTimeout\", options.maxTimeout, {\n min: 0,\n allowInfinity: true,\n });\n validateNumberOption(\"maxRetryTime\", options.maxRetryTime, {\n min: 0,\n allowInfinity: true,\n });\n\n // Treat non-positive factor as 1 to avoid zero backoff or negative behavior\n if (!(options.factor > 0)) {\n options.factor = 1;\n }\n\n options.signal?.throwIfAborted();\n\n let attemptNumber = 0;\n let retriesConsumed = 0;\n const startTime = performance.now();\n\n while (\n Number.isFinite(options.retries) ? retriesConsumed <= options.retries : true\n ) {\n attemptNumber++;\n\n try {\n options.signal?.throwIfAborted();\n\n const result = await input(attemptNumber);\n\n options.signal?.throwIfAborted();\n\n return result;\n } catch (error) {\n if (\n await onAttemptFailure({\n error,\n attemptNumber,\n retriesConsumed,\n startTime,\n options,\n })\n ) {\n retriesConsumed++;\n }\n }\n }\n\n // Should not reach here, but in case it does, throw an error\n throw new Error(\"Retry attempts exhausted without throwing an error.\");\n}\n\nexport function makeRetriable(function_, options) {\n return function (...arguments_) {\n return pRetry(() => function_.apply(this, arguments_), options);\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["isNetworkError"],"sources":["../../../src/utils/p-retry/index.js"],"sourcesContent":["/* oxlint-disable */\nimport isNetworkError from \"../is-network-error/index.js\";\n\nfunction validateRetries(retries) {\n if (typeof retries === \"number\") {\n if (retries < 0) {\n throw new TypeError(\"Expected `retries` to be a non-negative number.\");\n }\n\n if (Number.isNaN(retries)) {\n throw new TypeError(\n \"Expected `retries` to be a valid number or Infinity, got NaN.\"\n );\n }\n } else if (retries !== undefined) {\n throw new TypeError(\"Expected `retries` to be a number or Infinity.\");\n }\n}\n\nfunction validateNumberOption(\n name,\n value,\n { min = 0, allowInfinity = false } = {}\n) {\n if (value === undefined) {\n return;\n }\n\n if (typeof value !== \"number\" || Number.isNaN(value)) {\n throw new TypeError(\n `Expected \\`${name}\\` to be a number${\n allowInfinity ? \" or Infinity\" : \"\"\n }.`\n );\n }\n\n if (!allowInfinity && !Number.isFinite(value)) {\n throw new TypeError(`Expected \\`${name}\\` to be a finite number.`);\n }\n\n if (value < min) {\n throw new TypeError(`Expected \\`${name}\\` to be \\u2265 ${min}.`);\n }\n}\n\nexport class AbortError extends Error {\n constructor(message) {\n super();\n\n if (message instanceof Error) {\n this.originalError = message;\n ({ message } = message);\n } else {\n this.originalError = new Error(message);\n this.originalError.stack = this.stack;\n }\n\n this.name = \"AbortError\";\n this.message = message;\n }\n}\n\nfunction calculateDelay(retriesConsumed, options) {\n const attempt = Math.max(1, retriesConsumed + 1);\n const random = options.randomize ? Math.random() + 1 : 1;\n\n let timeout = Math.round(\n random * options.minTimeout * options.factor ** (attempt - 1)\n );\n timeout = Math.min(timeout, options.maxTimeout);\n\n return timeout;\n}\n\nfunction calculateRemainingTime(start, max) {\n if (!Number.isFinite(max)) {\n return max;\n }\n\n return max - (performance.now() - start);\n}\n\nasync function onAttemptFailure({\n error,\n attemptNumber,\n retriesConsumed,\n startTime,\n options,\n}) {\n const normalizedError =\n error instanceof Error\n ? error\n : new TypeError(\n `Non-error was thrown: \"${error}\". You should only throw errors.`\n );\n\n if (normalizedError instanceof AbortError) {\n throw normalizedError.originalError;\n }\n\n const retriesLeft = Number.isFinite(options.retries)\n ? Math.max(0, options.retries - retriesConsumed)\n : options.retries;\n\n const maxRetryTime = options.maxRetryTime ?? Number.POSITIVE_INFINITY;\n\n const context = Object.freeze({\n error: normalizedError,\n attemptNumber,\n retriesLeft,\n retriesConsumed,\n });\n\n await options.onFailedAttempt(context);\n\n if (calculateRemainingTime(startTime, maxRetryTime) <= 0) {\n throw normalizedError;\n }\n\n const consumeRetry = await options.shouldConsumeRetry(context);\n\n const remainingTime = calculateRemainingTime(startTime, maxRetryTime);\n\n if (remainingTime <= 0 || retriesLeft <= 0) {\n throw normalizedError;\n }\n\n if (\n normalizedError instanceof TypeError &&\n !isNetworkError(normalizedError)\n ) {\n if (consumeRetry) {\n throw normalizedError;\n }\n\n options.signal?.throwIfAborted();\n return false;\n }\n\n if (!(await options.shouldRetry(context))) {\n throw normalizedError;\n }\n\n if (!consumeRetry) {\n options.signal?.throwIfAborted();\n return false;\n }\n\n let delayTime = calculateDelay(retriesConsumed, options);\n const retryAfterMs =\n typeof normalizedError.retryAfterMs === \"number\" &&\n normalizedError.retryAfterMs >= 0\n ? normalizedError.retryAfterMs\n : undefined;\n\n if (retryAfterMs !== undefined) {\n delayTime = Math.max(delayTime, retryAfterMs);\n }\n\n const finalDelay = Math.min(delayTime, remainingTime);\n\n if (finalDelay > 0) {\n await new Promise((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timeoutToken);\n options.signal?.removeEventListener(\"abort\", onAbort);\n reject(options.signal.reason);\n };\n\n const timeoutToken = setTimeout(() => {\n options.signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, finalDelay);\n\n if (options.unref) {\n timeoutToken.unref?.();\n }\n\n options.signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n }\n\n options.signal?.throwIfAborted();\n\n return true;\n}\n\nexport default async function pRetry(input, options = {}) {\n options = { ...options };\n\n validateRetries(options.retries);\n\n if (Object.hasOwn(options, \"forever\")) {\n throw new Error(\n \"The `forever` option is no longer supported. For many use-cases, you can set `retries: Infinity` instead.\"\n );\n }\n\n options.retries ??= 10;\n options.factor ??= 2;\n options.minTimeout ??= 1000;\n options.maxTimeout ??= Number.POSITIVE_INFINITY;\n options.maxRetryTime ??= Number.POSITIVE_INFINITY;\n options.randomize ??= false;\n options.onFailedAttempt ??= () => {};\n options.shouldRetry ??= () => true;\n options.shouldConsumeRetry ??= () => true;\n\n // Validate numeric options and normalize edge cases\n validateNumberOption(\"factor\", options.factor, {\n min: 0,\n allowInfinity: false,\n });\n validateNumberOption(\"minTimeout\", options.minTimeout, {\n min: 0,\n allowInfinity: false,\n });\n validateNumberOption(\"maxTimeout\", options.maxTimeout, {\n min: 0,\n allowInfinity: true,\n });\n validateNumberOption(\"maxRetryTime\", options.maxRetryTime, {\n min: 0,\n allowInfinity: true,\n });\n\n // Treat non-positive factor as 1 to avoid zero backoff or negative behavior\n if (!(options.factor > 0)) {\n options.factor = 1;\n }\n\n options.signal?.throwIfAborted();\n\n let attemptNumber = 0;\n let retriesConsumed = 0;\n const startTime = performance.now();\n\n while (\n Number.isFinite(options.retries) ? retriesConsumed <= options.retries : true\n ) {\n attemptNumber++;\n\n try {\n options.signal?.throwIfAborted();\n\n const result = await input(attemptNumber);\n\n options.signal?.throwIfAborted();\n\n return result;\n } catch (error) {\n if (\n await onAttemptFailure({\n error,\n attemptNumber,\n retriesConsumed,\n startTime,\n options,\n })\n ) {\n retriesConsumed++;\n }\n }\n }\n\n // Should not reach here, but in case it does, throw an error\n throw new Error(\"Retry attempts exhausted without throwing an error.\");\n}\n\nexport function makeRetriable(function_, options) {\n return function (...arguments_) {\n return pRetry(() => function_.apply(this, arguments_), options);\n };\n}\n"],"mappings":"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"}
@@ -63,7 +63,9 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
63
63
  options.signal?.throwIfAborted();
64
64
  return false;
65
65
  }
66
- const delayTime = calculateDelay(retriesConsumed, options);
66
+ let delayTime = calculateDelay(retriesConsumed, options);
67
+ const retryAfterMs = typeof normalizedError.retryAfterMs === "number" && normalizedError.retryAfterMs >= 0 ? normalizedError.retryAfterMs : void 0;
68
+ if (retryAfterMs !== void 0) delayTime = Math.max(delayTime, retryAfterMs);
67
69
  const finalDelay = Math.min(delayTime, remainingTime);
68
70
  if (finalDelay > 0) await new Promise((resolve, reject) => {
69
71
  const onAbort = () => {