@poolzin/pool-bot 2026.2.19 → 2026.2.21
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.
- package/CHANGELOG.md +17 -0
- package/dist/agents/model-auth.js +12 -0
- package/dist/agents/model-fallback.js +24 -0
- package/dist/agents/models-config.providers.js +85 -0
- package/dist/agents/openclaw-tools.js +16 -0
- package/dist/agents/pi-embedded-runner/run/attempt.js +15 -0
- package/dist/agents/poolbot-tools.js +16 -0
- package/dist/agents/provider/config-loader.js +76 -0
- package/dist/agents/provider/index.js +15 -0
- package/dist/agents/provider/integration.js +136 -0
- package/dist/agents/provider/models-dev.js +129 -0
- package/dist/agents/provider/rate-limits.js +458 -0
- package/dist/agents/provider/request-monitor.js +449 -0
- package/dist/agents/provider/session-binding.js +376 -0
- package/dist/agents/provider/token-pool.js +541 -0
- package/dist/agents/tools/deep-research-tool.js +225 -0
- package/dist/agents/tools/image-generate-tool.js +235 -0
- package/dist/build-info.json +3 -3
- package/package.json +1 -1
- package/skills/plcode-controller/SKILL.md +156 -0
- package/skills/plcode-controller/assets/operator-prompts.md +65 -0
- package/skills/plcode-controller/references/command-cheatsheet.md +53 -0
- package/skills/plcode-controller/references/failure-handling.md +60 -0
- package/skills/plcode-controller/references/model-selection.md +57 -0
- package/skills/plcode-controller/references/plan-vs-build.md +52 -0
- package/skills/plcode-controller/references/question-handling.md +40 -0
- package/skills/plcode-controller/references/session-management.md +63 -0
- package/skills/plcode-controller/references/workflow.md +35 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limit Tracker
|
|
3
|
+
*
|
|
4
|
+
* Proactively tracks rate limit states for API keys across providers.
|
|
5
|
+
* Enables intelligent key rotation by avoiding known-limited keys before making requests.
|
|
6
|
+
*
|
|
7
|
+
* @module provider/rate-limits
|
|
8
|
+
*/
|
|
9
|
+
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
|
10
|
+
export var RateLimits;
|
|
11
|
+
(function (RateLimits) {
|
|
12
|
+
const log = createSubsystemLogger("provider/rate-limits");
|
|
13
|
+
/**
|
|
14
|
+
* Standard HTTP headers used by AI providers for rate limit information.
|
|
15
|
+
* Each provider may use different subsets of these headers.
|
|
16
|
+
*/
|
|
17
|
+
RateLimits.RATE_LIMIT_HEADERS = {
|
|
18
|
+
/** Unix timestamp or seconds until reset */
|
|
19
|
+
reset: [
|
|
20
|
+
"x-ratelimit-reset",
|
|
21
|
+
"x-ratelimit-reset-requests",
|
|
22
|
+
"x-ratelimit-reset-tokens",
|
|
23
|
+
"retry-after",
|
|
24
|
+
"retry-after-ms",
|
|
25
|
+
],
|
|
26
|
+
/** Remaining requests in current window */
|
|
27
|
+
remainingRequests: ["x-ratelimit-remaining", "x-ratelimit-remaining-requests"],
|
|
28
|
+
/** Remaining tokens in current window */
|
|
29
|
+
remainingTokens: ["x-ratelimit-remaining-tokens"],
|
|
30
|
+
/** Rate limit tier/limit info */
|
|
31
|
+
limit: ["x-ratelimit-limit", "x-ratelimit-limit-requests", "x-ratelimit-limit-tokens"],
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* HTTP status codes that indicate rate limiting or temporary unavailability.
|
|
35
|
+
*/
|
|
36
|
+
RateLimits.RATE_LIMIT_STATUS_CODES = {
|
|
37
|
+
/** Standard rate limit exceeded */
|
|
38
|
+
TOO_MANY_REQUESTS: 429,
|
|
39
|
+
/** Service temporarily unavailable (often due to overload) */
|
|
40
|
+
SERVICE_UNAVAILABLE: 503,
|
|
41
|
+
/** Site is overloaded (Cloudflare) */
|
|
42
|
+
SITE_OVERLOADED: 529,
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* In-memory store for rate limit states.
|
|
46
|
+
* Key format: "providerID" or "providerID:keyID"
|
|
47
|
+
*/
|
|
48
|
+
const states = new Map();
|
|
49
|
+
/**
|
|
50
|
+
* Maximum number of rate limit states to prevent unbounded memory growth.
|
|
51
|
+
* States are cleaned up on expiry, but this provides a hard cap.
|
|
52
|
+
*/
|
|
53
|
+
const MAX_STATES = 500;
|
|
54
|
+
/**
|
|
55
|
+
* Interval handle for cleanup task.
|
|
56
|
+
*/
|
|
57
|
+
let cleanupInterval = null;
|
|
58
|
+
/**
|
|
59
|
+
* Generates a unique key for the rate limit state map.
|
|
60
|
+
*/
|
|
61
|
+
function getStateKey(providerID, keyID) {
|
|
62
|
+
return keyID ? `${providerID}:${keyID}` : providerID;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Parses rate limit information from HTTP response headers.
|
|
66
|
+
*
|
|
67
|
+
* @param headers - Response headers (can be Headers object or plain object)
|
|
68
|
+
* @returns Parsed rate limit information
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* const headers = new Headers({
|
|
73
|
+
* 'x-ratelimit-remaining-requests': '100',
|
|
74
|
+
* 'x-ratelimit-reset': '1704067200'
|
|
75
|
+
* })
|
|
76
|
+
* const parsed = RateLimits.parseHeaders(headers)
|
|
77
|
+
* // { remainingRequests: 100, resetAt: 1704067200000 }
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
function parseHeaders(headers) {
|
|
81
|
+
const result = {};
|
|
82
|
+
const get = (name) => {
|
|
83
|
+
if (headers instanceof Headers) {
|
|
84
|
+
return headers.get(name) ?? undefined;
|
|
85
|
+
}
|
|
86
|
+
return headers[name] ?? headers[name.toLowerCase()];
|
|
87
|
+
};
|
|
88
|
+
// Parse reset time
|
|
89
|
+
for (const header of RateLimits.RATE_LIMIT_HEADERS.reset) {
|
|
90
|
+
const value = get(header);
|
|
91
|
+
if (value === undefined)
|
|
92
|
+
continue;
|
|
93
|
+
if (header === "retry-after-ms") {
|
|
94
|
+
const ms = Number.parseFloat(value);
|
|
95
|
+
if (!Number.isNaN(ms)) {
|
|
96
|
+
result.resetAt = Date.now() + ms;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else if (header === "retry-after") {
|
|
101
|
+
const seconds = Number.parseFloat(value);
|
|
102
|
+
if (!Number.isNaN(seconds)) {
|
|
103
|
+
result.resetAt = Date.now() + seconds * 1000;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
// Try HTTP date format
|
|
107
|
+
const date = Date.parse(value);
|
|
108
|
+
if (!Number.isNaN(date)) {
|
|
109
|
+
result.resetAt = date;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// x-ratelimit-reset headers are typically Unix timestamps in seconds
|
|
115
|
+
const timestamp = Number.parseFloat(value);
|
|
116
|
+
if (!Number.isNaN(timestamp)) {
|
|
117
|
+
// If timestamp is small, it's likely seconds from now
|
|
118
|
+
// If large, it's a Unix timestamp
|
|
119
|
+
result.resetAt = timestamp < 1e10 ? Date.now() + timestamp * 1000 : timestamp * 1000;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Parse remaining requests
|
|
125
|
+
for (const header of RateLimits.RATE_LIMIT_HEADERS.remainingRequests) {
|
|
126
|
+
const value = get(header);
|
|
127
|
+
if (value !== undefined) {
|
|
128
|
+
const parsed = Number.parseInt(value, 10);
|
|
129
|
+
if (!Number.isNaN(parsed)) {
|
|
130
|
+
result.remainingRequests = parsed;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Parse remaining tokens
|
|
136
|
+
for (const header of RateLimits.RATE_LIMIT_HEADERS.remainingTokens) {
|
|
137
|
+
const value = get(header);
|
|
138
|
+
if (value !== undefined) {
|
|
139
|
+
const parsed = Number.parseInt(value, 10);
|
|
140
|
+
if (!Number.isNaN(parsed)) {
|
|
141
|
+
result.remainingTokens = parsed;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Parse limits
|
|
147
|
+
for (const header of RateLimits.RATE_LIMIT_HEADERS.limit) {
|
|
148
|
+
const value = get(header);
|
|
149
|
+
if (value !== undefined) {
|
|
150
|
+
const parsed = Number.parseInt(value, 10);
|
|
151
|
+
if (!Number.isNaN(parsed)) {
|
|
152
|
+
if (header.includes("tokens")) {
|
|
153
|
+
result.limitTokens = parsed;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
result.limitRequests = parsed;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
RateLimits.parseHeaders = parseHeaders;
|
|
164
|
+
/**
|
|
165
|
+
* Marks a key as rate limited.
|
|
166
|
+
*
|
|
167
|
+
* @param providerID - Provider identifier
|
|
168
|
+
* @param statusCode - HTTP status code that triggered the limit
|
|
169
|
+
* @param reason - Human-readable reason
|
|
170
|
+
* @param options - Additional options
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```typescript
|
|
174
|
+
* RateLimits.markLimited("anthropic", 429, "Too Many Requests", {
|
|
175
|
+
* keyID: "key-1",
|
|
176
|
+
* resetAt: Date.now() + 60000,
|
|
177
|
+
* remainingRequests: 0
|
|
178
|
+
* })
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
function markLimited(providerID, statusCode, reason, options = {}) {
|
|
182
|
+
const key = getStateKey(providerID, options.keyID);
|
|
183
|
+
const now = Date.now();
|
|
184
|
+
if (states.size >= MAX_STATES && !states.has(key)) {
|
|
185
|
+
evictExpiredOrOldestStates();
|
|
186
|
+
}
|
|
187
|
+
const defaultResetMs = statusCode === RateLimits.RATE_LIMIT_STATUS_CODES.TOO_MANY_REQUESTS ? 60_000 : 30_000;
|
|
188
|
+
const state = {
|
|
189
|
+
id: key,
|
|
190
|
+
providerID,
|
|
191
|
+
keyID: options.keyID,
|
|
192
|
+
resetAt: options.resetAt ?? now + defaultResetMs,
|
|
193
|
+
remainingRequests: options.remainingRequests,
|
|
194
|
+
remainingTokens: options.remainingTokens,
|
|
195
|
+
statusCode,
|
|
196
|
+
reason,
|
|
197
|
+
recordedAt: now,
|
|
198
|
+
};
|
|
199
|
+
states.set(key, state);
|
|
200
|
+
log.info("rate-limited", {
|
|
201
|
+
providerID,
|
|
202
|
+
keyID: options.keyID,
|
|
203
|
+
statusCode,
|
|
204
|
+
reason,
|
|
205
|
+
resetIn: Math.round((state.resetAt - now) / 1000) + "s",
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
RateLimits.markLimited = markLimited;
|
|
209
|
+
function evictExpiredOrOldestStates() {
|
|
210
|
+
const now = Date.now();
|
|
211
|
+
let evicted = 0;
|
|
212
|
+
for (const [key, state] of states.entries()) {
|
|
213
|
+
if (state.resetAt <= now) {
|
|
214
|
+
states.delete(key);
|
|
215
|
+
evicted++;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (evicted > 0) {
|
|
219
|
+
log.debug("evicted-expired-states", { count: evicted });
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
let oldestKey;
|
|
223
|
+
let oldestTime = Infinity;
|
|
224
|
+
for (const [key, state] of states.entries()) {
|
|
225
|
+
if (state.recordedAt < oldestTime) {
|
|
226
|
+
oldestTime = state.recordedAt;
|
|
227
|
+
oldestKey = key;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (oldestKey) {
|
|
231
|
+
states.delete(oldestKey);
|
|
232
|
+
log.debug("evicted-oldest-state", { key: oldestKey });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Marks a key as rate limited from response headers.
|
|
237
|
+
* Convenience method that combines parseHeaders and markLimited.
|
|
238
|
+
*
|
|
239
|
+
* @param providerID - Provider identifier
|
|
240
|
+
* @param statusCode - HTTP status code
|
|
241
|
+
* @param headers - Response headers
|
|
242
|
+
* @param keyID - Optional key identifier
|
|
243
|
+
*/
|
|
244
|
+
function markLimitedFromResponse(providerID, statusCode, headers, keyID) {
|
|
245
|
+
const parsed = parseHeaders(headers);
|
|
246
|
+
const reason = getReasonFromStatusCode(statusCode);
|
|
247
|
+
markLimited(providerID, statusCode, reason, {
|
|
248
|
+
keyID,
|
|
249
|
+
resetAt: parsed.resetAt,
|
|
250
|
+
remainingRequests: parsed.remainingRequests,
|
|
251
|
+
remainingTokens: parsed.remainingTokens,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
RateLimits.markLimitedFromResponse = markLimitedFromResponse;
|
|
255
|
+
/**
|
|
256
|
+
* Gets a human-readable reason for a rate limit status code.
|
|
257
|
+
*/
|
|
258
|
+
function getReasonFromStatusCode(statusCode) {
|
|
259
|
+
switch (statusCode) {
|
|
260
|
+
case RateLimits.RATE_LIMIT_STATUS_CODES.TOO_MANY_REQUESTS:
|
|
261
|
+
return "Too Many Requests";
|
|
262
|
+
case RateLimits.RATE_LIMIT_STATUS_CODES.SERVICE_UNAVAILABLE:
|
|
263
|
+
return "Service Unavailable";
|
|
264
|
+
case RateLimits.RATE_LIMIT_STATUS_CODES.SITE_OVERLOADED:
|
|
265
|
+
return "Service Overloaded";
|
|
266
|
+
default:
|
|
267
|
+
return `HTTP ${statusCode}`;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Checks if a key is currently rate limited.
|
|
272
|
+
*
|
|
273
|
+
* @param providerID - Provider identifier
|
|
274
|
+
* @param keyID - Optional key identifier
|
|
275
|
+
* @returns Check result with limit status and wait time
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* ```typescript
|
|
279
|
+
* const result = RateLimits.isLimited("anthropic", "key-1")
|
|
280
|
+
* if (result.isLimited) {
|
|
281
|
+
* console.log(`Wait ${result.waitTimeMs}ms before retrying`)
|
|
282
|
+
* }
|
|
283
|
+
* ```
|
|
284
|
+
*/
|
|
285
|
+
function isLimited(providerID, keyID) {
|
|
286
|
+
const key = getStateKey(providerID, keyID);
|
|
287
|
+
const state = states.get(key);
|
|
288
|
+
if (!state) {
|
|
289
|
+
return { isLimited: false, waitTimeMs: 0 };
|
|
290
|
+
}
|
|
291
|
+
const now = Date.now();
|
|
292
|
+
const waitTimeMs = Math.max(0, state.resetAt - now);
|
|
293
|
+
if (waitTimeMs <= 0) {
|
|
294
|
+
// Rate limit has expired, clean it up
|
|
295
|
+
states.delete(key);
|
|
296
|
+
return { isLimited: false, waitTimeMs: 0 };
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
isLimited: true,
|
|
300
|
+
waitTimeMs,
|
|
301
|
+
state,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
RateLimits.isLimited = isLimited;
|
|
305
|
+
/**
|
|
306
|
+
* Gets the rate limit state for a key without checking expiration.
|
|
307
|
+
*
|
|
308
|
+
* @param providerID - Provider identifier
|
|
309
|
+
* @param keyID - Optional key identifier
|
|
310
|
+
* @returns The state or undefined if not limited
|
|
311
|
+
*/
|
|
312
|
+
function getState(providerID, keyID) {
|
|
313
|
+
const key = getStateKey(providerID, keyID);
|
|
314
|
+
return states.get(key);
|
|
315
|
+
}
|
|
316
|
+
RateLimits.getState = getState;
|
|
317
|
+
/**
|
|
318
|
+
* Clears the rate limit state for a key.
|
|
319
|
+
*
|
|
320
|
+
* @param providerID - Provider identifier
|
|
321
|
+
* @param keyID - Optional key identifier
|
|
322
|
+
*/
|
|
323
|
+
function clear(providerID, keyID) {
|
|
324
|
+
const key = getStateKey(providerID, keyID);
|
|
325
|
+
const existed = states.delete(key);
|
|
326
|
+
if (existed) {
|
|
327
|
+
log.info("cleared", { providerID, keyID });
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
RateLimits.clear = clear;
|
|
331
|
+
/**
|
|
332
|
+
* Clears all rate limit states for a provider.
|
|
333
|
+
*
|
|
334
|
+
* @param providerID - Provider identifier
|
|
335
|
+
*/
|
|
336
|
+
function clearProvider(providerID) {
|
|
337
|
+
let count = 0;
|
|
338
|
+
for (const key of states.keys()) {
|
|
339
|
+
if (key === providerID || key.startsWith(`${providerID}:`)) {
|
|
340
|
+
states.delete(key);
|
|
341
|
+
count++;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (count > 0) {
|
|
345
|
+
log.info("cleared-provider", { providerID, count });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
RateLimits.clearProvider = clearProvider;
|
|
349
|
+
/**
|
|
350
|
+
* Clears all rate limit states.
|
|
351
|
+
*/
|
|
352
|
+
function clearAll() {
|
|
353
|
+
const count = states.size;
|
|
354
|
+
states.clear();
|
|
355
|
+
if (count > 0) {
|
|
356
|
+
log.info("cleared-all", { count });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
RateLimits.clearAll = clearAll;
|
|
360
|
+
/**
|
|
361
|
+
* Removes expired rate limit states.
|
|
362
|
+
* Called automatically by the cleanup interval.
|
|
363
|
+
*/
|
|
364
|
+
function cleanupExpired() {
|
|
365
|
+
const now = Date.now();
|
|
366
|
+
let removed = 0;
|
|
367
|
+
for (const [key, state] of states.entries()) {
|
|
368
|
+
if (state.resetAt <= now) {
|
|
369
|
+
states.delete(key);
|
|
370
|
+
removed++;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (removed > 0) {
|
|
374
|
+
log.info("cleanup", { removed });
|
|
375
|
+
}
|
|
376
|
+
return removed;
|
|
377
|
+
}
|
|
378
|
+
RateLimits.cleanupExpired = cleanupExpired;
|
|
379
|
+
/**
|
|
380
|
+
* Gets all current rate limit states.
|
|
381
|
+
* Useful for monitoring and debugging.
|
|
382
|
+
*
|
|
383
|
+
* @returns Array of all rate limit states
|
|
384
|
+
*/
|
|
385
|
+
function getAll() {
|
|
386
|
+
return Array.from(states.values());
|
|
387
|
+
}
|
|
388
|
+
RateLimits.getAll = getAll;
|
|
389
|
+
/**
|
|
390
|
+
* Gets all limited keys for a provider.
|
|
391
|
+
*
|
|
392
|
+
* @param providerID - Provider identifier
|
|
393
|
+
* @returns Array of rate limit states for the provider
|
|
394
|
+
*/
|
|
395
|
+
function getProviderStates(providerID) {
|
|
396
|
+
const result = [];
|
|
397
|
+
const now = Date.now();
|
|
398
|
+
for (const [key, state] of states.entries()) {
|
|
399
|
+
if ((key === providerID || key.startsWith(`${providerID}:`)) && state.resetAt > now) {
|
|
400
|
+
result.push(state);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return result;
|
|
404
|
+
}
|
|
405
|
+
RateLimits.getProviderStates = getProviderStates;
|
|
406
|
+
/**
|
|
407
|
+
* Gets summary statistics for rate limits.
|
|
408
|
+
*
|
|
409
|
+
* @returns Summary object with counts and details
|
|
410
|
+
*/
|
|
411
|
+
function getSummary() {
|
|
412
|
+
const now = Date.now();
|
|
413
|
+
const byProvider = {};
|
|
414
|
+
const byStatusCode = {};
|
|
415
|
+
let totalLimited = 0;
|
|
416
|
+
for (const state of states.values()) {
|
|
417
|
+
if (state.resetAt > now) {
|
|
418
|
+
totalLimited++;
|
|
419
|
+
byProvider[state.providerID] = (byProvider[state.providerID] ?? 0) + 1;
|
|
420
|
+
byStatusCode[state.statusCode] = (byStatusCode[state.statusCode] ?? 0) + 1;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return { totalLimited, byProvider, byStatusCode };
|
|
424
|
+
}
|
|
425
|
+
RateLimits.getSummary = getSummary;
|
|
426
|
+
/**
|
|
427
|
+
* Starts the automatic cleanup interval.
|
|
428
|
+
* Called automatically on first use.
|
|
429
|
+
*
|
|
430
|
+
* @param intervalMs - Cleanup interval in milliseconds (default: 60000)
|
|
431
|
+
*/
|
|
432
|
+
function startCleanup(intervalMs = 60_000) {
|
|
433
|
+
if (cleanupInterval)
|
|
434
|
+
return;
|
|
435
|
+
cleanupInterval = setInterval(() => {
|
|
436
|
+
cleanupExpired();
|
|
437
|
+
}, intervalMs);
|
|
438
|
+
// Don't keep the process alive just for cleanup
|
|
439
|
+
if (typeof cleanupInterval.unref === "function") {
|
|
440
|
+
cleanupInterval.unref();
|
|
441
|
+
}
|
|
442
|
+
log.debug("cleanup-started", { intervalMs });
|
|
443
|
+
}
|
|
444
|
+
RateLimits.startCleanup = startCleanup;
|
|
445
|
+
/**
|
|
446
|
+
* Stops the automatic cleanup interval.
|
|
447
|
+
*/
|
|
448
|
+
function stopCleanup() {
|
|
449
|
+
if (cleanupInterval) {
|
|
450
|
+
clearInterval(cleanupInterval);
|
|
451
|
+
cleanupInterval = null;
|
|
452
|
+
log.info("cleanup-stopped");
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
RateLimits.stopCleanup = stopCleanup;
|
|
456
|
+
// Start cleanup on module load
|
|
457
|
+
startCleanup();
|
|
458
|
+
})(RateLimits || (RateLimits = {}));
|