@poolzin/pool-bot 2026.2.20 → 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 +8 -0
- package/dist/agents/model-auth.js +12 -0
- package/dist/agents/model-fallback.js +24 -0
- package/dist/agents/pi-embedded-runner/run/attempt.js +15 -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/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,541 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Pool Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages multiple API keys per provider with intelligent rotation,
|
|
5
|
+
* rate limit awareness, and scheduling strategies.
|
|
6
|
+
*
|
|
7
|
+
* @module provider/token-pool
|
|
8
|
+
*/
|
|
9
|
+
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
|
10
|
+
import { RateLimits } from "./rate-limits.js";
|
|
11
|
+
export var TokenPool;
|
|
12
|
+
(function (TokenPool) {
|
|
13
|
+
const log = createSubsystemLogger("provider/token-pool");
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Internal State
|
|
16
|
+
// ============================================================================
|
|
17
|
+
/** Pools per provider */
|
|
18
|
+
const pools = new Map();
|
|
19
|
+
/** Maximum number of provider pools to prevent unbounded growth */
|
|
20
|
+
const MAX_POOLS = 50;
|
|
21
|
+
/** Default pool configuration */
|
|
22
|
+
const DEFAULT_CONFIG = {
|
|
23
|
+
scheduling: "priority",
|
|
24
|
+
maxWaitMs: 5000,
|
|
25
|
+
autoDisable: true,
|
|
26
|
+
autoDisableThreshold: 5,
|
|
27
|
+
};
|
|
28
|
+
/** Tier priority (lower is higher priority) */
|
|
29
|
+
const TIER_PRIORITY = {
|
|
30
|
+
enterprise: 0,
|
|
31
|
+
paid: 1,
|
|
32
|
+
free: 2,
|
|
33
|
+
};
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Helper Functions
|
|
36
|
+
// ============================================================================
|
|
37
|
+
/**
|
|
38
|
+
* Gets or creates a pool for a provider.
|
|
39
|
+
* Enforces MAX_POOLS limit by evicting least recently used pools.
|
|
40
|
+
*/
|
|
41
|
+
function getOrCreatePool(providerID) {
|
|
42
|
+
let pool = pools.get(providerID);
|
|
43
|
+
if (!pool) {
|
|
44
|
+
if (pools.size >= MAX_POOLS) {
|
|
45
|
+
evictLeastUsedPool();
|
|
46
|
+
}
|
|
47
|
+
pool = {
|
|
48
|
+
providerID,
|
|
49
|
+
config: { ...DEFAULT_CONFIG },
|
|
50
|
+
tokens: new Map(),
|
|
51
|
+
currentIndex: 0,
|
|
52
|
+
failureCounts: new Map(),
|
|
53
|
+
};
|
|
54
|
+
pools.set(providerID, pool);
|
|
55
|
+
}
|
|
56
|
+
return pool;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Evicts the pool with the fewest total requests (least used).
|
|
60
|
+
*/
|
|
61
|
+
function evictLeastUsedPool() {
|
|
62
|
+
let leastUsedID;
|
|
63
|
+
let leastUsedRequests = Infinity;
|
|
64
|
+
for (const [providerID, pool] of pools.entries()) {
|
|
65
|
+
const totalRequests = Array.from(pool.tokens.values()).reduce((sum, t) => sum + t.usage.totalRequests, 0);
|
|
66
|
+
if (totalRequests < leastUsedRequests) {
|
|
67
|
+
leastUsedRequests = totalRequests;
|
|
68
|
+
leastUsedID = providerID;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (leastUsedID) {
|
|
72
|
+
pools.delete(leastUsedID);
|
|
73
|
+
RateLimits.clearProvider(leastUsedID);
|
|
74
|
+
log.info("pool-evicted", { providerID: leastUsedID, reason: "max-pools-reached" });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Creates a default token entry.
|
|
79
|
+
*/
|
|
80
|
+
function createTokenEntry(id, key, tier = "paid", label) {
|
|
81
|
+
return {
|
|
82
|
+
id,
|
|
83
|
+
key,
|
|
84
|
+
tier,
|
|
85
|
+
enabled: true,
|
|
86
|
+
label,
|
|
87
|
+
usage: {
|
|
88
|
+
totalRequests: 0,
|
|
89
|
+
totalInputTokens: 0,
|
|
90
|
+
totalOutputTokens: 0,
|
|
91
|
+
requestsToday: 0,
|
|
92
|
+
},
|
|
93
|
+
addedAt: Date.now(),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Gets available (enabled and not rate-limited) tokens from a pool.
|
|
98
|
+
*/
|
|
99
|
+
function getAvailableTokens(pool) {
|
|
100
|
+
const available = [];
|
|
101
|
+
for (const token of pool.tokens.values()) {
|
|
102
|
+
if (!token.enabled)
|
|
103
|
+
continue;
|
|
104
|
+
const limitCheck = RateLimits.isLimited(pool.providerID, token.id);
|
|
105
|
+
if (!limitCheck.isLimited) {
|
|
106
|
+
available.push(token);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return available;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Sorts tokens by scheduling strategy.
|
|
113
|
+
*/
|
|
114
|
+
function sortByStrategy(tokens, mode) {
|
|
115
|
+
switch (mode) {
|
|
116
|
+
case "priority":
|
|
117
|
+
return [...tokens].sort((a, b) => {
|
|
118
|
+
// First by tier
|
|
119
|
+
const tierDiff = TIER_PRIORITY[a.tier] - TIER_PRIORITY[b.tier];
|
|
120
|
+
if (tierDiff !== 0)
|
|
121
|
+
return tierDiff;
|
|
122
|
+
// Then by least used
|
|
123
|
+
return a.usage.totalRequests - b.usage.totalRequests;
|
|
124
|
+
});
|
|
125
|
+
case "least-used":
|
|
126
|
+
return [...tokens].sort((a, b) => a.usage.totalRequests - b.usage.totalRequests);
|
|
127
|
+
case "random":
|
|
128
|
+
return [...tokens].sort(() => Math.random() - 0.5);
|
|
129
|
+
case "round-robin":
|
|
130
|
+
default:
|
|
131
|
+
return tokens;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Gets the current date string (YYYY-MM-DD).
|
|
136
|
+
*/
|
|
137
|
+
function getDateString() {
|
|
138
|
+
return new Date().toISOString().split("T")[0];
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Resets daily counters if needed.
|
|
142
|
+
*/
|
|
143
|
+
function resetDailyCountersIfNeeded(token) {
|
|
144
|
+
const today = getDateString();
|
|
145
|
+
if (token.usage.requestsDate !== today) {
|
|
146
|
+
token.usage.requestsToday = 0;
|
|
147
|
+
token.usage.requestsDate = today;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// ============================================================================
|
|
151
|
+
// Public API
|
|
152
|
+
// ============================================================================
|
|
153
|
+
/**
|
|
154
|
+
* Adds a token to a provider's pool.
|
|
155
|
+
*
|
|
156
|
+
* @param providerID - Provider identifier
|
|
157
|
+
* @param id - Unique token identifier
|
|
158
|
+
* @param key - The API key
|
|
159
|
+
* @param options - Additional options
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```typescript
|
|
163
|
+
* TokenPool.addToken("anthropic", "personal", "sk-ant-...", {
|
|
164
|
+
* tier: "paid",
|
|
165
|
+
* label: "Personal API Key"
|
|
166
|
+
* })
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
function addToken(providerID, id, key, options = {}) {
|
|
170
|
+
const pool = getOrCreatePool(providerID);
|
|
171
|
+
const entry = createTokenEntry(id, key, options.tier ?? "paid", options.label);
|
|
172
|
+
if (options.enabled !== undefined) {
|
|
173
|
+
entry.enabled = options.enabled;
|
|
174
|
+
}
|
|
175
|
+
pool.tokens.set(id, entry);
|
|
176
|
+
log.info("token-added", { providerID, tokenID: id, tier: entry.tier });
|
|
177
|
+
return entry;
|
|
178
|
+
}
|
|
179
|
+
TokenPool.addToken = addToken;
|
|
180
|
+
/**
|
|
181
|
+
* Removes a token from a provider's pool.
|
|
182
|
+
*
|
|
183
|
+
* @param providerID - Provider identifier
|
|
184
|
+
* @param tokenID - Token identifier to remove
|
|
185
|
+
* @returns Whether the token was found and removed
|
|
186
|
+
*/
|
|
187
|
+
function removeToken(providerID, tokenID) {
|
|
188
|
+
const pool = pools.get(providerID);
|
|
189
|
+
if (!pool)
|
|
190
|
+
return false;
|
|
191
|
+
const removed = pool.tokens.delete(tokenID);
|
|
192
|
+
if (removed) {
|
|
193
|
+
pool.failureCounts.delete(tokenID);
|
|
194
|
+
RateLimits.clear(providerID, tokenID);
|
|
195
|
+
log.info("token-removed", { providerID, tokenID });
|
|
196
|
+
}
|
|
197
|
+
return removed;
|
|
198
|
+
}
|
|
199
|
+
TokenPool.removeToken = removeToken;
|
|
200
|
+
/**
|
|
201
|
+
* Configures a provider's token pool.
|
|
202
|
+
*
|
|
203
|
+
* @param providerID - Provider identifier
|
|
204
|
+
* @param poolConfig - Partial configuration to merge
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```typescript
|
|
208
|
+
* TokenPool.configure("anthropic", {
|
|
209
|
+
* scheduling: "priority",
|
|
210
|
+
* maxWaitMs: 10000
|
|
211
|
+
* })
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
function configure(providerID, poolConfig) {
|
|
215
|
+
const pool = getOrCreatePool(providerID);
|
|
216
|
+
pool.config = { ...pool.config, ...poolConfig };
|
|
217
|
+
log.info("configured", { providerID, config: { ...pool.config } });
|
|
218
|
+
}
|
|
219
|
+
TokenPool.configure = configure;
|
|
220
|
+
/**
|
|
221
|
+
* Gets the current configuration for a provider.
|
|
222
|
+
*
|
|
223
|
+
* @param providerID - Provider identifier
|
|
224
|
+
* @returns Configuration or default if no pool exists
|
|
225
|
+
*/
|
|
226
|
+
function getConfig(providerID) {
|
|
227
|
+
const pool = pools.get(providerID);
|
|
228
|
+
return pool?.config ?? { ...DEFAULT_CONFIG };
|
|
229
|
+
}
|
|
230
|
+
TokenPool.getConfig = getConfig;
|
|
231
|
+
/**
|
|
232
|
+
* Gets a token from the pool using the configured scheduling strategy.
|
|
233
|
+
* Automatically skips rate-limited tokens.
|
|
234
|
+
*
|
|
235
|
+
* @param providerID - Provider identifier
|
|
236
|
+
* @param options - Selection options
|
|
237
|
+
* @returns Token result or undefined if no tokens available
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```typescript
|
|
241
|
+
* const result = await TokenPool.getToken("anthropic")
|
|
242
|
+
* if (result) {
|
|
243
|
+
* console.log("Using token:", result.token.id)
|
|
244
|
+
* // Make API call with result.token.key
|
|
245
|
+
* }
|
|
246
|
+
* ```
|
|
247
|
+
*/
|
|
248
|
+
async function getToken(providerID, options = {}) {
|
|
249
|
+
const pool = pools.get(providerID);
|
|
250
|
+
if (!pool || pool.tokens.size === 0) {
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
const maxWaitMs = options.maxWaitMs ?? pool.config.maxWaitMs;
|
|
254
|
+
// Check preferred token first (for sticky sessions)
|
|
255
|
+
if (options.preferredTokenID) {
|
|
256
|
+
const preferred = pool.tokens.get(options.preferredTokenID);
|
|
257
|
+
if (preferred?.enabled) {
|
|
258
|
+
const limitCheck = RateLimits.isLimited(providerID, preferred.id);
|
|
259
|
+
if (!limitCheck.isLimited) {
|
|
260
|
+
return {
|
|
261
|
+
token: preferred,
|
|
262
|
+
waited: false,
|
|
263
|
+
waitedMs: 0,
|
|
264
|
+
onlyOption: pool.tokens.size === 1,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
// Wait for preferred if requested and wait time is acceptable
|
|
268
|
+
if (options.waitForPreferred && limitCheck.waitTimeMs <= maxWaitMs) {
|
|
269
|
+
log.info("waiting-for-preferred", {
|
|
270
|
+
providerID,
|
|
271
|
+
tokenID: preferred.id,
|
|
272
|
+
waitMs: limitCheck.waitTimeMs,
|
|
273
|
+
});
|
|
274
|
+
await sleep(limitCheck.waitTimeMs);
|
|
275
|
+
return {
|
|
276
|
+
token: preferred,
|
|
277
|
+
waited: true,
|
|
278
|
+
waitedMs: limitCheck.waitTimeMs,
|
|
279
|
+
onlyOption: pool.tokens.size === 1,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Get available tokens
|
|
285
|
+
const available = getAvailableTokens(pool);
|
|
286
|
+
if (available.length > 0) {
|
|
287
|
+
// Sort by strategy
|
|
288
|
+
const sorted = sortByStrategy(available, pool.config.scheduling);
|
|
289
|
+
// For round-robin, use index
|
|
290
|
+
let selected;
|
|
291
|
+
if (pool.config.scheduling === "round-robin") {
|
|
292
|
+
pool.currentIndex = pool.currentIndex % sorted.length;
|
|
293
|
+
selected = sorted[pool.currentIndex];
|
|
294
|
+
pool.currentIndex++;
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
selected = sorted[0];
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
token: selected,
|
|
301
|
+
waited: false,
|
|
302
|
+
waitedMs: 0,
|
|
303
|
+
onlyOption: available.length === 1,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
// All tokens are rate-limited, find the one that resets soonest
|
|
307
|
+
const allTokens = Array.from(pool.tokens.values()).filter((t) => t.enabled);
|
|
308
|
+
if (allTokens.length === 0) {
|
|
309
|
+
return undefined;
|
|
310
|
+
}
|
|
311
|
+
let soonestToken;
|
|
312
|
+
let soonestWaitMs = Infinity;
|
|
313
|
+
for (const token of allTokens) {
|
|
314
|
+
const limitCheck = RateLimits.isLimited(providerID, token.id);
|
|
315
|
+
if (limitCheck.waitTimeMs < soonestWaitMs) {
|
|
316
|
+
soonestWaitMs = limitCheck.waitTimeMs;
|
|
317
|
+
soonestToken = token;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (!soonestToken) {
|
|
321
|
+
return undefined;
|
|
322
|
+
}
|
|
323
|
+
// Wait if within acceptable range
|
|
324
|
+
if (soonestWaitMs <= maxWaitMs) {
|
|
325
|
+
log.info("waiting-for-soonest", {
|
|
326
|
+
providerID,
|
|
327
|
+
tokenID: soonestToken.id,
|
|
328
|
+
waitMs: soonestWaitMs,
|
|
329
|
+
});
|
|
330
|
+
await sleep(soonestWaitMs);
|
|
331
|
+
return {
|
|
332
|
+
token: soonestToken,
|
|
333
|
+
waited: true,
|
|
334
|
+
waitedMs: soonestWaitMs,
|
|
335
|
+
onlyOption: allTokens.length === 1,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
// Return soonest token anyway (caller can decide to wait or fail)
|
|
339
|
+
return {
|
|
340
|
+
token: soonestToken,
|
|
341
|
+
waited: false,
|
|
342
|
+
waitedMs: 0,
|
|
343
|
+
onlyOption: allTokens.length === 1,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
TokenPool.getToken = getToken;
|
|
347
|
+
/**
|
|
348
|
+
* Records a successful request with a token.
|
|
349
|
+
*
|
|
350
|
+
* @param providerID - Provider identifier
|
|
351
|
+
* @param tokenID - Token identifier
|
|
352
|
+
* @param usage - Token usage from the request
|
|
353
|
+
*/
|
|
354
|
+
function recordSuccess(providerID, tokenID, usage) {
|
|
355
|
+
const pool = pools.get(providerID);
|
|
356
|
+
const token = pool?.tokens.get(tokenID);
|
|
357
|
+
if (!pool || !token)
|
|
358
|
+
return;
|
|
359
|
+
// Reset failure count on success
|
|
360
|
+
pool.failureCounts.set(tokenID, 0);
|
|
361
|
+
// Update usage stats
|
|
362
|
+
resetDailyCountersIfNeeded(token);
|
|
363
|
+
token.usage.totalRequests++;
|
|
364
|
+
token.usage.requestsToday++;
|
|
365
|
+
token.usage.lastUsedAt = Date.now();
|
|
366
|
+
if (usage?.inputTokens) {
|
|
367
|
+
token.usage.totalInputTokens += usage.inputTokens;
|
|
368
|
+
}
|
|
369
|
+
if (usage?.outputTokens) {
|
|
370
|
+
token.usage.totalOutputTokens += usage.outputTokens;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
TokenPool.recordSuccess = recordSuccess;
|
|
374
|
+
/**
|
|
375
|
+
* Records a failed request with a token.
|
|
376
|
+
* May auto-disable the token if threshold is reached.
|
|
377
|
+
*
|
|
378
|
+
* @param providerID - Provider identifier
|
|
379
|
+
* @param tokenID - Token identifier
|
|
380
|
+
* @param statusCode - HTTP status code
|
|
381
|
+
* @param headers - Response headers (for rate limit parsing)
|
|
382
|
+
*/
|
|
383
|
+
function recordFailure(providerID, tokenID, statusCode, headers) {
|
|
384
|
+
const pool = pools.get(providerID);
|
|
385
|
+
const token = pool?.tokens.get(tokenID);
|
|
386
|
+
if (!pool || !token)
|
|
387
|
+
return;
|
|
388
|
+
// Track rate limits
|
|
389
|
+
if (statusCode === RateLimits.RATE_LIMIT_STATUS_CODES.TOO_MANY_REQUESTS ||
|
|
390
|
+
statusCode === RateLimits.RATE_LIMIT_STATUS_CODES.SERVICE_UNAVAILABLE ||
|
|
391
|
+
statusCode === RateLimits.RATE_LIMIT_STATUS_CODES.SITE_OVERLOADED) {
|
|
392
|
+
if (headers) {
|
|
393
|
+
RateLimits.markLimitedFromResponse(providerID, statusCode, headers, tokenID);
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
RateLimits.markLimited(providerID, statusCode, `HTTP ${statusCode}`, {
|
|
397
|
+
keyID: tokenID,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
// Track consecutive failures
|
|
402
|
+
const failures = (pool.failureCounts.get(tokenID) ?? 0) + 1;
|
|
403
|
+
pool.failureCounts.set(tokenID, failures);
|
|
404
|
+
// Auto-disable if threshold reached
|
|
405
|
+
if (pool.config.autoDisable && failures >= pool.config.autoDisableThreshold) {
|
|
406
|
+
token.enabled = false;
|
|
407
|
+
log.warn("token-auto-disabled", {
|
|
408
|
+
providerID,
|
|
409
|
+
tokenID,
|
|
410
|
+
failures,
|
|
411
|
+
threshold: pool.config.autoDisableThreshold,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
TokenPool.recordFailure = recordFailure;
|
|
416
|
+
/**
|
|
417
|
+
* Enables a previously disabled token.
|
|
418
|
+
*
|
|
419
|
+
* @param providerID - Provider identifier
|
|
420
|
+
* @param tokenID - Token identifier
|
|
421
|
+
*/
|
|
422
|
+
function enableToken(providerID, tokenID) {
|
|
423
|
+
const pool = pools.get(providerID);
|
|
424
|
+
const token = pool?.tokens.get(tokenID);
|
|
425
|
+
if (!pool || !token)
|
|
426
|
+
return false;
|
|
427
|
+
token.enabled = true;
|
|
428
|
+
pool.failureCounts.set(tokenID, 0);
|
|
429
|
+
RateLimits.clear(providerID, tokenID);
|
|
430
|
+
log.info("token-enabled", { providerID, tokenID });
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
TokenPool.enableToken = enableToken;
|
|
434
|
+
/**
|
|
435
|
+
* Disables a token.
|
|
436
|
+
*
|
|
437
|
+
* @param providerID - Provider identifier
|
|
438
|
+
* @param tokenID - Token identifier
|
|
439
|
+
*/
|
|
440
|
+
function disableToken(providerID, tokenID) {
|
|
441
|
+
const pool = pools.get(providerID);
|
|
442
|
+
const token = pool?.tokens.get(tokenID);
|
|
443
|
+
if (!token)
|
|
444
|
+
return false;
|
|
445
|
+
token.enabled = false;
|
|
446
|
+
log.info("token-disabled", { providerID, tokenID });
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
TokenPool.disableToken = disableToken;
|
|
450
|
+
/**
|
|
451
|
+
* Gets all tokens for a provider.
|
|
452
|
+
*
|
|
453
|
+
* @param providerID - Provider identifier
|
|
454
|
+
* @returns Array of token entries (without key values for security)
|
|
455
|
+
*/
|
|
456
|
+
function getTokens(providerID) {
|
|
457
|
+
const pool = pools.get(providerID);
|
|
458
|
+
if (!pool)
|
|
459
|
+
return [];
|
|
460
|
+
return Array.from(pool.tokens.values()).map((t) => {
|
|
461
|
+
const { key: _key, ...rest } = t;
|
|
462
|
+
return rest;
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
TokenPool.getTokens = getTokens;
|
|
466
|
+
/**
|
|
467
|
+
* Gets a summary of a provider's pool.
|
|
468
|
+
*
|
|
469
|
+
* @param providerID - Provider identifier
|
|
470
|
+
*/
|
|
471
|
+
function getPoolSummary(providerID) {
|
|
472
|
+
const pool = pools.get(providerID);
|
|
473
|
+
if (!pool)
|
|
474
|
+
return undefined;
|
|
475
|
+
const tokens = Array.from(pool.tokens.values());
|
|
476
|
+
const enabled = tokens.filter((t) => t.enabled);
|
|
477
|
+
const available = getAvailableTokens(pool);
|
|
478
|
+
const rateLimited = enabled.length - available.length;
|
|
479
|
+
return {
|
|
480
|
+
providerID,
|
|
481
|
+
config: pool.config,
|
|
482
|
+
totalTokens: tokens.length,
|
|
483
|
+
enabledTokens: enabled.length,
|
|
484
|
+
availableTokens: available.length,
|
|
485
|
+
rateLimitedTokens: rateLimited,
|
|
486
|
+
tokens: tokens.map((t) => {
|
|
487
|
+
const { key: _key, ...rest } = t;
|
|
488
|
+
return rest;
|
|
489
|
+
}),
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
TokenPool.getPoolSummary = getPoolSummary;
|
|
493
|
+
/**
|
|
494
|
+
* Gets summaries for all pools.
|
|
495
|
+
*/
|
|
496
|
+
function getAllPoolSummaries() {
|
|
497
|
+
const result = {};
|
|
498
|
+
for (const providerID of pools.keys()) {
|
|
499
|
+
result[providerID] = getPoolSummary(providerID);
|
|
500
|
+
}
|
|
501
|
+
return result;
|
|
502
|
+
}
|
|
503
|
+
TokenPool.getAllPoolSummaries = getAllPoolSummaries;
|
|
504
|
+
/**
|
|
505
|
+
* Checks if a provider has a token pool configured.
|
|
506
|
+
*
|
|
507
|
+
* @param providerID - Provider identifier
|
|
508
|
+
*/
|
|
509
|
+
function hasPool(providerID) {
|
|
510
|
+
const pool = pools.get(providerID);
|
|
511
|
+
return pool !== undefined && pool.tokens.size > 0;
|
|
512
|
+
}
|
|
513
|
+
TokenPool.hasPool = hasPool;
|
|
514
|
+
/**
|
|
515
|
+
* Clears all tokens from a provider's pool.
|
|
516
|
+
*
|
|
517
|
+
* @param providerID - Provider identifier
|
|
518
|
+
*/
|
|
519
|
+
function clearPool(providerID) {
|
|
520
|
+
pools.delete(providerID);
|
|
521
|
+
RateLimits.clearProvider(providerID);
|
|
522
|
+
log.info("pool-cleared", { providerID });
|
|
523
|
+
}
|
|
524
|
+
TokenPool.clearPool = clearPool;
|
|
525
|
+
/**
|
|
526
|
+
* Clears all pools.
|
|
527
|
+
*/
|
|
528
|
+
function clearAll() {
|
|
529
|
+
const count = pools.size;
|
|
530
|
+
pools.clear();
|
|
531
|
+
RateLimits.clearAll();
|
|
532
|
+
log.info("all-pools-cleared", { count });
|
|
533
|
+
}
|
|
534
|
+
TokenPool.clearAll = clearAll;
|
|
535
|
+
/**
|
|
536
|
+
* Simple sleep utility.
|
|
537
|
+
*/
|
|
538
|
+
function sleep(ms) {
|
|
539
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
540
|
+
}
|
|
541
|
+
})(TokenPool || (TokenPool = {}));
|
package/dist/build-info.json
CHANGED