@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.
Files changed (28) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/agents/model-auth.js +12 -0
  3. package/dist/agents/model-fallback.js +24 -0
  4. package/dist/agents/models-config.providers.js +85 -0
  5. package/dist/agents/openclaw-tools.js +16 -0
  6. package/dist/agents/pi-embedded-runner/run/attempt.js +15 -0
  7. package/dist/agents/poolbot-tools.js +16 -0
  8. package/dist/agents/provider/config-loader.js +76 -0
  9. package/dist/agents/provider/index.js +15 -0
  10. package/dist/agents/provider/integration.js +136 -0
  11. package/dist/agents/provider/models-dev.js +129 -0
  12. package/dist/agents/provider/rate-limits.js +458 -0
  13. package/dist/agents/provider/request-monitor.js +449 -0
  14. package/dist/agents/provider/session-binding.js +376 -0
  15. package/dist/agents/provider/token-pool.js +541 -0
  16. package/dist/agents/tools/deep-research-tool.js +225 -0
  17. package/dist/agents/tools/image-generate-tool.js +235 -0
  18. package/dist/build-info.json +3 -3
  19. package/package.json +1 -1
  20. package/skills/plcode-controller/SKILL.md +156 -0
  21. package/skills/plcode-controller/assets/operator-prompts.md +65 -0
  22. package/skills/plcode-controller/references/command-cheatsheet.md +53 -0
  23. package/skills/plcode-controller/references/failure-handling.md +60 -0
  24. package/skills/plcode-controller/references/model-selection.md +57 -0
  25. package/skills/plcode-controller/references/plan-vs-build.md +52 -0
  26. package/skills/plcode-controller/references/question-handling.md +40 -0
  27. package/skills/plcode-controller/references/session-management.md +63 -0
  28. 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 = {}));