oira666_pi-limits-wait 0.4.11 → 0.4.13

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.
@@ -1,259 +1,263 @@
1
- import { DEFAULT_NETWORK_WAIT_MS, DEFAULT_OVERLOADED_WAIT_MS, DEFAULT_RATE_LIMIT_WAIT_MS } from "./constants.js";
2
- import type { RetryableError } from "./types.js";
3
-
4
- function isExplicitRateLimitError(msg: string): boolean {
5
- const lower = msg.toLowerCase();
6
- return (
7
- /(?:^|\D)429(?:\D|$)/.test(msg) ||
8
- lower.includes("rate_limit") ||
9
- /rate\s*limit/.test(lower) ||
10
- lower.includes("too many requests") ||
11
- lower.includes("quota exceeded") ||
12
- lower.includes("quota will reset") ||
13
- lower.includes("usage limit") ||
14
- lower.includes("limit reached")
15
- );
16
- }
17
-
18
- export function isRateLimitError(msg: string): boolean {
19
- const lower = msg.toLowerCase();
20
- return (
21
- isExplicitRateLimitError(msg) ||
22
- lower.includes("retry delay") ||
23
- lower.includes("retry-after")
24
- );
25
- }
26
-
27
- export function isServerOverloadedError(msg: string): boolean {
28
- const lower = msg.toLowerCase();
29
- return (
30
- lower.includes("server_is_overloaded") ||
31
- lower.includes("server is overloaded") ||
32
- lower.includes("overloaded_error")
33
- );
34
- }
35
-
36
- export function isTransientNetworkError(msg: string): boolean {
37
- const lower = msg.toLowerCase();
38
- return (
39
- lower.includes("und_err_headers_timeout") ||
40
- lower.includes("und_err_body_timeout") ||
41
- lower.includes("und_err_connect_timeout") ||
42
- lower.includes("und_err_socket") ||
43
- lower.includes("headers timeout") ||
44
- lower.includes("body timeout") ||
45
- lower.includes("connect timeout") ||
46
- lower.includes("fetch failed") ||
47
- lower.includes("terminated") ||
48
- lower.includes("socket hang up") ||
49
- lower.includes("other side closed") ||
50
- lower.includes("network_error") ||
51
- lower.includes("network error") ||
52
- lower.includes("econnreset") ||
53
- lower.includes("econnrefused") ||
54
- lower.includes("etimedout") ||
55
- lower.includes("enetunreach") ||
56
- lower.includes("ehostunreach") ||
57
- lower.includes("eai_again") ||
58
- lower.includes("enotfound")
59
- );
60
- }
61
-
62
- export function isAuthenticationRefreshError(msg: string): boolean {
63
- const lower = msg.toLowerCase();
64
- return (
65
- /(?:^|\D)401(?:\D|$)/.test(msg) &&
66
- (
67
- lower.includes("authentication_error") ||
68
- lower.includes("invalid authentication credentials") ||
69
- lower.includes("invalid authentication")
70
- )
71
- );
72
- }
73
-
74
- function msUntilTimestamp(timestampMs: number): number | undefined {
75
- if (!Number.isFinite(timestampMs)) return undefined;
76
- return Math.max(0, timestampMs - Date.now());
77
- }
78
-
79
- function parseResetValueMs(value: string): number | undefined {
80
- const trimmed = value.trim();
81
- if (!trimmed) return undefined;
82
-
83
- const numeric = Number(trimmed);
84
- if (Number.isFinite(numeric) && numeric > 0) {
85
- // Epoch milliseconds / epoch seconds / duration seconds, depending on size.
86
- if (numeric > 1_000_000_000_000) return msUntilTimestamp(numeric);
87
- if (numeric > 1_000_000_000) return msUntilTimestamp(numeric * 1_000);
88
- return numeric * 1_000;
89
- }
90
-
91
- const dateMs = Date.parse(trimmed);
92
- if (!Number.isNaN(dateMs)) return msUntilTimestamp(dateMs);
93
- return undefined;
94
- }
95
-
96
- function parseClockTimeResetMs(msg: string): number | undefined {
97
- const match = msg.match(/(?:reset|resets|retry|try again)[^\n.]*?\bat\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)?/i);
98
- if (!match?.[1]) return undefined;
99
-
100
- let hours = Number(match[1]);
101
- const minutes = match[2] ? Number(match[2]) : 0;
102
- const meridiem = match[3]?.toLowerCase();
103
- if (!Number.isFinite(hours) || !Number.isFinite(minutes) || minutes < 0 || minutes > 59) return undefined;
104
-
105
- if (meridiem) {
106
- if (hours < 1 || hours > 12) return undefined;
107
- if (meridiem === "pm" && hours !== 12) hours += 12;
108
- if (meridiem === "am" && hours === 12) hours = 0;
109
- } else if (hours > 23) {
110
- return undefined;
111
- }
112
-
113
- const target = new Date();
114
- target.setHours(hours, minutes, 0, 0);
115
- if (target.getTime() <= Date.now()) target.setDate(target.getDate() + 1);
116
- return target.getTime() - Date.now();
117
- }
118
-
119
- export function parseRetryDelayMs(msg: string): number | undefined {
120
- // Claude subscription errors often include a Unix reset timestamp after a pipe,
121
- // e.g. "Claude AI usage limit reached|1750359600".
122
- const pipeTimestamp = msg.match(/\|(\d{10,13})(?:\D|$)/);
123
- if (pipeTimestamp?.[1]) {
124
- const parsed = parseResetValueMs(pipeTimestamp[1]);
125
- if (parsed !== undefined) return parsed;
126
- }
127
-
128
- const retryAfter = msg.match(/retry-after(?:-ms)?[^0-9]*(\d+(?:\.\d+)?)/i);
129
- if (retryAfter?.[1]) {
130
- const value = Number(retryAfter[1]);
131
- if (Number.isFinite(value) && value > 0) {
132
- return msg.toLowerCase().includes("retry-after-ms") ? value : value * 1_000;
133
- }
134
- }
135
-
136
- const requested = msg.match(/requested\s+(\d+(?:\.\d+)?)s\s+retry delay/i);
137
- if (requested?.[1]) {
138
- const value = Number(requested[1]);
139
- if (Number.isFinite(value) && value > 0) return value * 1_000;
140
- }
141
-
142
- const retryIn = msg.match(/retry\s+in\s+(\d+(?:\.\d+)?)(ms|s|m|h)/i);
143
- if (retryIn?.[1] && retryIn[2]) {
144
- const value = Number(retryIn[1]);
145
- if (Number.isFinite(value) && value > 0) {
146
- const unit = retryIn[2].toLowerCase();
147
- if (unit === "ms") return value;
148
- if (unit === "s") return value * 1_000;
149
- if (unit === "m") return value * 60_000;
150
- if (unit === "h") return value * 3_600_000;
151
- }
152
- }
153
-
154
- const resetAfter = msg.match(/reset after (?:(\d+)h)?(?:(\d+)m)?(\d+(?:\.\d+)?)s/i);
155
- if (resetAfter) {
156
- const hours = resetAfter[1] ? Number(resetAfter[1]) : 0;
157
- const mins = resetAfter[2] ? Number(resetAfter[2]) : 0;
158
- const secs = Number(resetAfter[3]);
159
- if ([hours, mins, secs].every(Number.isFinite)) {
160
- return ((hours * 60 + mins) * 60 + secs) * 1_000;
161
- }
162
- }
163
-
164
- const resetAtIso = msg.match(/(?:reset|resets|retry|try again)[^\n.]*?\bat\s+([0-9]{4}-[0-9]{2}-[0-9]{2}[^\s,;.}]*)/i);
165
- if (resetAtIso?.[1]) {
166
- const parsed = parseResetValueMs(resetAtIso[1]);
167
- if (parsed !== undefined) return parsed;
168
- }
169
-
170
- return parseClockTimeResetMs(msg);
171
- }
172
-
173
- export function rateLimitWaitMs(msg: string): number {
174
- return parseRetryDelayMs(msg) ?? DEFAULT_RATE_LIMIT_WAIT_MS;
175
- }
176
-
177
- export function retryAfterHeaderMs(headers: Headers): number | undefined {
178
- const retryAfterMs = headers.get("retry-after-ms");
179
- if (retryAfterMs) {
180
- const value = Number(retryAfterMs);
181
- if (Number.isFinite(value) && value > 0) return value;
182
- }
183
-
184
- const retryAfter = headers.get("retry-after");
185
- if (retryAfter) {
186
- const seconds = Number(retryAfter);
187
- if (Number.isFinite(seconds) && seconds > 0) return seconds * 1_000;
188
-
189
- const dateMs = Date.parse(retryAfter);
190
- if (!Number.isNaN(dateMs)) return Math.max(0, dateMs - Date.now());
191
- }
192
-
193
- for (const name of [
194
- "anthropic-ratelimit-unified-reset",
195
- "anthropic-ratelimit-unified-5h-reset",
196
- "anthropic-ratelimit-unified-7d-reset",
197
- "anthropic-ratelimit-requests-reset",
198
- "anthropic-ratelimit-tokens-reset",
199
- "x-ratelimit-reset",
200
- "x-ratelimit-reset-requests",
201
- "x-ratelimit-reset-tokens",
202
- "ratelimit-reset",
203
- ]) {
204
- const value = headers.get(name);
205
- if (!value) continue;
206
- const parsed = parseResetValueMs(value);
207
- if (parsed !== undefined) return parsed;
208
- }
209
-
210
- return undefined;
211
- }
212
-
213
- export function getRetryableError(msg: string): RetryableError | undefined {
214
- if (isServerOverloadedError(msg)) {
215
- return { reason: "overloaded", waitMs: parseRetryDelayMs(msg) ?? DEFAULT_OVERLOADED_WAIT_MS };
216
- }
217
- if (isAuthenticationRefreshError(msg)) {
218
- return { reason: "authentication", waitMs: parseRetryDelayMs(msg) ?? DEFAULT_RATE_LIMIT_WAIT_MS };
219
- }
220
- if (isTransientNetworkError(msg) && !isExplicitRateLimitError(msg)) {
221
- return { reason: "network", waitMs: parseRetryDelayMs(msg) ?? DEFAULT_NETWORK_WAIT_MS };
222
- }
223
- if (isRateLimitError(msg)) {
224
- return { reason: "rate-limit", waitMs: rateLimitWaitMs(msg) };
225
- }
226
- if (isTransientNetworkError(msg)) {
227
- return { reason: "network", waitMs: parseRetryDelayMs(msg) ?? DEFAULT_NETWORK_WAIT_MS };
228
- }
229
- return undefined;
230
- }
231
-
232
- export function errorMessageWithCauses(err: unknown): string {
233
- const messages: string[] = [];
234
- let current: unknown = err;
235
- const seen = new Set<unknown>();
236
-
237
- while (current && !seen.has(current)) {
238
- seen.add(current);
239
- if (current instanceof Error) {
240
- if (current.message) messages.push(current.message);
241
- current = (current as Error & { cause?: unknown }).cause;
242
- continue;
243
- }
244
- messages.push(String(current));
245
- break;
246
- }
247
-
248
- return messages.filter(Boolean).join(" Cause: ") || String(err);
249
- }
250
-
251
- export function isAbortMessage(msg: string): boolean {
252
- const lower = msg.toLowerCase();
253
- return (
254
- lower.includes("operation aborted") ||
255
- lower.includes("request aborted") ||
256
- lower.includes("aborterror") ||
257
- /\baborted\b/.test(lower)
258
- );
259
- }
1
+ import { DEFAULT_NETWORK_WAIT_MS, DEFAULT_OVERLOADED_WAIT_MS, DEFAULT_RATE_LIMIT_WAIT_MS } from "./constants.js";
2
+ import type { RetryableError } from "./types.js";
3
+
4
+ function isExplicitRateLimitError(msg: string): boolean {
5
+ const lower = msg.toLowerCase();
6
+ return (
7
+ /(?:^|\D)429(?:\D|$)/.test(msg) ||
8
+ lower.includes("rate_limit") ||
9
+ /rate\s*limit/.test(lower) ||
10
+ lower.includes("too many requests") ||
11
+ lower.includes("quota exceeded") ||
12
+ lower.includes("quota will reset") ||
13
+ lower.includes("usage limit") ||
14
+ lower.includes("limit reached")
15
+ );
16
+ }
17
+
18
+ export function isRateLimitError(msg: string): boolean {
19
+ const lower = msg.toLowerCase();
20
+ return (
21
+ isExplicitRateLimitError(msg) ||
22
+ lower.includes("retry delay") ||
23
+ lower.includes("retry-after")
24
+ );
25
+ }
26
+
27
+ export function isServerOverloadedError(msg: string): boolean {
28
+ const lower = msg.toLowerCase();
29
+ return (
30
+ lower.includes("server_is_overloaded") ||
31
+ lower.includes("server is overloaded") ||
32
+ lower.includes("overloaded_error")
33
+ );
34
+ }
35
+
36
+ export function isTransientNetworkError(msg: string): boolean {
37
+ const lower = msg.toLowerCase();
38
+ return (
39
+ lower.includes("und_err_headers_timeout") ||
40
+ lower.includes("und_err_body_timeout") ||
41
+ lower.includes("und_err_connect_timeout") ||
42
+ lower.includes("und_err_socket") ||
43
+ lower.includes("headers timeout") ||
44
+ lower.includes("body timeout") ||
45
+ lower.includes("connect timeout") ||
46
+ lower.includes("request timed out") ||
47
+ lower.includes("timed out") ||
48
+ lower.includes("timedout") ||
49
+ lower.includes("timeout") ||
50
+ lower.includes("fetch failed") ||
51
+ lower.includes("terminated") ||
52
+ lower.includes("socket hang up") ||
53
+ lower.includes("other side closed") ||
54
+ lower.includes("network_error") ||
55
+ lower.includes("network error") ||
56
+ lower.includes("econnreset") ||
57
+ lower.includes("econnrefused") ||
58
+ lower.includes("etimedout") ||
59
+ lower.includes("enetunreach") ||
60
+ lower.includes("ehostunreach") ||
61
+ lower.includes("eai_again") ||
62
+ lower.includes("enotfound")
63
+ );
64
+ }
65
+
66
+ export function isAuthenticationRefreshError(msg: string): boolean {
67
+ const lower = msg.toLowerCase();
68
+ return (
69
+ /(?:^|\D)401(?:\D|$)/.test(msg) &&
70
+ (
71
+ lower.includes("authentication_error") ||
72
+ lower.includes("invalid authentication credentials") ||
73
+ lower.includes("invalid authentication")
74
+ )
75
+ );
76
+ }
77
+
78
+ function msUntilTimestamp(timestampMs: number): number | undefined {
79
+ if (!Number.isFinite(timestampMs)) return undefined;
80
+ return Math.max(0, timestampMs - Date.now());
81
+ }
82
+
83
+ function parseResetValueMs(value: string): number | undefined {
84
+ const trimmed = value.trim();
85
+ if (!trimmed) return undefined;
86
+
87
+ const numeric = Number(trimmed);
88
+ if (Number.isFinite(numeric) && numeric > 0) {
89
+ // Epoch milliseconds / epoch seconds / duration seconds, depending on size.
90
+ if (numeric > 1_000_000_000_000) return msUntilTimestamp(numeric);
91
+ if (numeric > 1_000_000_000) return msUntilTimestamp(numeric * 1_000);
92
+ return numeric * 1_000;
93
+ }
94
+
95
+ const dateMs = Date.parse(trimmed);
96
+ if (!Number.isNaN(dateMs)) return msUntilTimestamp(dateMs);
97
+ return undefined;
98
+ }
99
+
100
+ function parseClockTimeResetMs(msg: string): number | undefined {
101
+ const match = msg.match(/(?:reset|resets|retry|try again)[^\n.]*?\bat\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)?/i);
102
+ if (!match?.[1]) return undefined;
103
+
104
+ let hours = Number(match[1]);
105
+ const minutes = match[2] ? Number(match[2]) : 0;
106
+ const meridiem = match[3]?.toLowerCase();
107
+ if (!Number.isFinite(hours) || !Number.isFinite(minutes) || minutes < 0 || minutes > 59) return undefined;
108
+
109
+ if (meridiem) {
110
+ if (hours < 1 || hours > 12) return undefined;
111
+ if (meridiem === "pm" && hours !== 12) hours += 12;
112
+ if (meridiem === "am" && hours === 12) hours = 0;
113
+ } else if (hours > 23) {
114
+ return undefined;
115
+ }
116
+
117
+ const target = new Date();
118
+ target.setHours(hours, minutes, 0, 0);
119
+ if (target.getTime() <= Date.now()) target.setDate(target.getDate() + 1);
120
+ return target.getTime() - Date.now();
121
+ }
122
+
123
+ export function parseRetryDelayMs(msg: string): number | undefined {
124
+ // Claude subscription errors often include a Unix reset timestamp after a pipe,
125
+ // e.g. "Claude AI usage limit reached|1750359600".
126
+ const pipeTimestamp = msg.match(/\|(\d{10,13})(?:\D|$)/);
127
+ if (pipeTimestamp?.[1]) {
128
+ const parsed = parseResetValueMs(pipeTimestamp[1]);
129
+ if (parsed !== undefined) return parsed;
130
+ }
131
+
132
+ const retryAfter = msg.match(/retry-after(?:-ms)?[^0-9]*(\d+(?:\.\d+)?)/i);
133
+ if (retryAfter?.[1]) {
134
+ const value = Number(retryAfter[1]);
135
+ if (Number.isFinite(value) && value > 0) {
136
+ return msg.toLowerCase().includes("retry-after-ms") ? value : value * 1_000;
137
+ }
138
+ }
139
+
140
+ const requested = msg.match(/requested\s+(\d+(?:\.\d+)?)s\s+retry delay/i);
141
+ if (requested?.[1]) {
142
+ const value = Number(requested[1]);
143
+ if (Number.isFinite(value) && value > 0) return value * 1_000;
144
+ }
145
+
146
+ const retryIn = msg.match(/retry\s+in\s+(\d+(?:\.\d+)?)(ms|s|m|h)/i);
147
+ if (retryIn?.[1] && retryIn[2]) {
148
+ const value = Number(retryIn[1]);
149
+ if (Number.isFinite(value) && value > 0) {
150
+ const unit = retryIn[2].toLowerCase();
151
+ if (unit === "ms") return value;
152
+ if (unit === "s") return value * 1_000;
153
+ if (unit === "m") return value * 60_000;
154
+ if (unit === "h") return value * 3_600_000;
155
+ }
156
+ }
157
+
158
+ const resetAfter = msg.match(/reset after (?:(\d+)h)?(?:(\d+)m)?(\d+(?:\.\d+)?)s/i);
159
+ if (resetAfter) {
160
+ const hours = resetAfter[1] ? Number(resetAfter[1]) : 0;
161
+ const mins = resetAfter[2] ? Number(resetAfter[2]) : 0;
162
+ const secs = Number(resetAfter[3]);
163
+ if ([hours, mins, secs].every(Number.isFinite)) {
164
+ return ((hours * 60 + mins) * 60 + secs) * 1_000;
165
+ }
166
+ }
167
+
168
+ const resetAtIso = msg.match(/(?:reset|resets|retry|try again)[^\n.]*?\bat\s+([0-9]{4}-[0-9]{2}-[0-9]{2}[^\s,;.}]*)/i);
169
+ if (resetAtIso?.[1]) {
170
+ const parsed = parseResetValueMs(resetAtIso[1]);
171
+ if (parsed !== undefined) return parsed;
172
+ }
173
+
174
+ return parseClockTimeResetMs(msg);
175
+ }
176
+
177
+ export function rateLimitWaitMs(msg: string): number {
178
+ return parseRetryDelayMs(msg) ?? DEFAULT_RATE_LIMIT_WAIT_MS;
179
+ }
180
+
181
+ export function retryAfterHeaderMs(headers: Headers): number | undefined {
182
+ const retryAfterMs = headers.get("retry-after-ms");
183
+ if (retryAfterMs) {
184
+ const value = Number(retryAfterMs);
185
+ if (Number.isFinite(value) && value > 0) return value;
186
+ }
187
+
188
+ const retryAfter = headers.get("retry-after");
189
+ if (retryAfter) {
190
+ const seconds = Number(retryAfter);
191
+ if (Number.isFinite(seconds) && seconds > 0) return seconds * 1_000;
192
+
193
+ const dateMs = Date.parse(retryAfter);
194
+ if (!Number.isNaN(dateMs)) return Math.max(0, dateMs - Date.now());
195
+ }
196
+
197
+ for (const name of [
198
+ "anthropic-ratelimit-unified-reset",
199
+ "anthropic-ratelimit-unified-5h-reset",
200
+ "anthropic-ratelimit-unified-7d-reset",
201
+ "anthropic-ratelimit-requests-reset",
202
+ "anthropic-ratelimit-tokens-reset",
203
+ "x-ratelimit-reset",
204
+ "x-ratelimit-reset-requests",
205
+ "x-ratelimit-reset-tokens",
206
+ "ratelimit-reset",
207
+ ]) {
208
+ const value = headers.get(name);
209
+ if (!value) continue;
210
+ const parsed = parseResetValueMs(value);
211
+ if (parsed !== undefined) return parsed;
212
+ }
213
+
214
+ return undefined;
215
+ }
216
+
217
+ export function getRetryableError(msg: string): RetryableError | undefined {
218
+ if (isServerOverloadedError(msg)) {
219
+ return { reason: "overloaded", waitMs: parseRetryDelayMs(msg) ?? DEFAULT_OVERLOADED_WAIT_MS };
220
+ }
221
+ if (isAuthenticationRefreshError(msg)) {
222
+ return { reason: "authentication", waitMs: parseRetryDelayMs(msg) ?? DEFAULT_RATE_LIMIT_WAIT_MS };
223
+ }
224
+ if (isTransientNetworkError(msg) && !isExplicitRateLimitError(msg)) {
225
+ return { reason: "network", waitMs: parseRetryDelayMs(msg) ?? DEFAULT_NETWORK_WAIT_MS };
226
+ }
227
+ if (isRateLimitError(msg)) {
228
+ return { reason: "rate-limit", waitMs: rateLimitWaitMs(msg) };
229
+ }
230
+ if (isTransientNetworkError(msg)) {
231
+ return { reason: "network", waitMs: parseRetryDelayMs(msg) ?? DEFAULT_NETWORK_WAIT_MS };
232
+ }
233
+ return undefined;
234
+ }
235
+
236
+ export function errorMessageWithCauses(err: unknown): string {
237
+ const messages: string[] = [];
238
+ let current: unknown = err;
239
+ const seen = new Set<unknown>();
240
+
241
+ while (current && !seen.has(current)) {
242
+ seen.add(current);
243
+ if (current instanceof Error) {
244
+ if (current.message) messages.push(current.message);
245
+ current = (current as Error & { cause?: unknown }).cause;
246
+ continue;
247
+ }
248
+ messages.push(String(current));
249
+ break;
250
+ }
251
+
252
+ return messages.filter(Boolean).join(" Cause: ") || String(err);
253
+ }
254
+
255
+ export function isAbortMessage(msg: string): boolean {
256
+ const lower = msg.toLowerCase();
257
+ return (
258
+ lower.includes("operation aborted") ||
259
+ lower.includes("request aborted") ||
260
+ lower.includes("aborterror") ||
261
+ /\baborted\b/.test(lower)
262
+ );
263
+ }