@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,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request Monitor
|
|
3
|
+
*
|
|
4
|
+
* Provides observability into API requests for debugging, cost analysis, and analytics.
|
|
5
|
+
* Implements a ring buffer for memory-efficient storage with optional persistence.
|
|
6
|
+
*
|
|
7
|
+
* @module provider/request-monitor
|
|
8
|
+
*/
|
|
9
|
+
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
|
10
|
+
export var RequestMonitor;
|
|
11
|
+
(function (RequestMonitor) {
|
|
12
|
+
const log = createSubsystemLogger("provider/request-monitor");
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Internal State
|
|
15
|
+
// ============================================================================
|
|
16
|
+
/** Ring buffer for request logs */
|
|
17
|
+
const logs = [];
|
|
18
|
+
/** Per-provider statistics */
|
|
19
|
+
const providerStats = new Map();
|
|
20
|
+
/** Overall statistics */
|
|
21
|
+
let globalStats = createEmptyStats();
|
|
22
|
+
/** Event listeners */
|
|
23
|
+
const listeners = new Set();
|
|
24
|
+
/** Monitor configuration */
|
|
25
|
+
let config = {
|
|
26
|
+
enabled: true,
|
|
27
|
+
maxLogs: 1000,
|
|
28
|
+
calculateCosts: true,
|
|
29
|
+
emitEvents: true,
|
|
30
|
+
};
|
|
31
|
+
/** Rolling window for RPM/TPM calculations (last 60 seconds) */
|
|
32
|
+
const rollingWindow = [];
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// Helper Functions
|
|
35
|
+
// ============================================================================
|
|
36
|
+
/**
|
|
37
|
+
* Creates an empty stats object.
|
|
38
|
+
*/
|
|
39
|
+
function createEmptyStats() {
|
|
40
|
+
return {
|
|
41
|
+
totalRequests: 0,
|
|
42
|
+
successCount: 0,
|
|
43
|
+
errorCount: 0,
|
|
44
|
+
rateLimitCount: 0,
|
|
45
|
+
totalInputTokens: 0,
|
|
46
|
+
totalOutputTokens: 0,
|
|
47
|
+
totalCacheReadTokens: 0,
|
|
48
|
+
totalCacheWriteTokens: 0,
|
|
49
|
+
avgLatencyMs: 0,
|
|
50
|
+
minLatencyMs: Infinity,
|
|
51
|
+
maxLatencyMs: 0,
|
|
52
|
+
p50LatencyMs: 0,
|
|
53
|
+
p95LatencyMs: 0,
|
|
54
|
+
p99LatencyMs: 0,
|
|
55
|
+
totalCostUSD: 0,
|
|
56
|
+
requestsPerMinute: 0,
|
|
57
|
+
tokensPerMinute: 0,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Generates a unique request ID.
|
|
62
|
+
*/
|
|
63
|
+
function generateID() {
|
|
64
|
+
const timestamp = Date.now().toString(36);
|
|
65
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
66
|
+
return `req_${timestamp}_${random}`;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Updates statistics with a new request log.
|
|
70
|
+
*/
|
|
71
|
+
function updateStats(stats, entry) {
|
|
72
|
+
stats.totalRequests++;
|
|
73
|
+
if (entry.status >= 200 && entry.status < 400) {
|
|
74
|
+
stats.successCount++;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
stats.errorCount++;
|
|
78
|
+
}
|
|
79
|
+
if (entry.status === 429) {
|
|
80
|
+
stats.rateLimitCount++;
|
|
81
|
+
}
|
|
82
|
+
stats.totalInputTokens += entry.inputTokens ?? 0;
|
|
83
|
+
stats.totalOutputTokens += entry.outputTokens ?? 0;
|
|
84
|
+
stats.totalCacheReadTokens += entry.cacheReadTokens ?? 0;
|
|
85
|
+
stats.totalCacheWriteTokens += entry.cacheWriteTokens ?? 0;
|
|
86
|
+
stats.totalCostUSD += entry.costUSD ?? 0;
|
|
87
|
+
// Update latency stats
|
|
88
|
+
stats.minLatencyMs = Math.min(stats.minLatencyMs, entry.latencyMs);
|
|
89
|
+
stats.maxLatencyMs = Math.max(stats.maxLatencyMs, entry.latencyMs);
|
|
90
|
+
// Rolling average for avgLatencyMs
|
|
91
|
+
stats.avgLatencyMs =
|
|
92
|
+
(stats.avgLatencyMs * (stats.totalRequests - 1) + entry.latencyMs) / stats.totalRequests;
|
|
93
|
+
// Update first/last timestamps
|
|
94
|
+
if (!stats.firstRequestAt) {
|
|
95
|
+
stats.firstRequestAt = entry.timestamp;
|
|
96
|
+
}
|
|
97
|
+
stats.lastRequestAt = entry.timestamp;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Calculates percentile latencies from the log buffer.
|
|
101
|
+
*/
|
|
102
|
+
function calculatePercentiles(stats) {
|
|
103
|
+
if (logs.length === 0)
|
|
104
|
+
return;
|
|
105
|
+
const latencies = logs.map((l) => l.latencyMs).sort((a, b) => a - b);
|
|
106
|
+
const len = latencies.length;
|
|
107
|
+
stats.p50LatencyMs = latencies[Math.floor(len * 0.5)] ?? 0;
|
|
108
|
+
stats.p95LatencyMs = latencies[Math.floor(len * 0.95)] ?? 0;
|
|
109
|
+
stats.p99LatencyMs = latencies[Math.floor(len * 0.99)] ?? 0;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Updates rolling window metrics (RPM, TPM).
|
|
113
|
+
*/
|
|
114
|
+
function updateRollingMetrics() {
|
|
115
|
+
const now = Date.now();
|
|
116
|
+
const oneMinuteAgo = now - 60_000;
|
|
117
|
+
// Remove old entries
|
|
118
|
+
while (rollingWindow.length > 0 && rollingWindow[0].timestamp < oneMinuteAgo) {
|
|
119
|
+
rollingWindow.shift();
|
|
120
|
+
}
|
|
121
|
+
// Calculate RPM and TPM
|
|
122
|
+
globalStats.requestsPerMinute = rollingWindow.length;
|
|
123
|
+
globalStats.tokensPerMinute = rollingWindow.reduce((sum, entry) => sum + entry.tokens, 0);
|
|
124
|
+
// Update per-provider stats
|
|
125
|
+
for (const [providerID, stats] of providerStats.entries()) {
|
|
126
|
+
const providerLogs = logs.filter((l) => l.providerID === providerID && l.timestamp >= oneMinuteAgo);
|
|
127
|
+
stats.requestsPerMinute = providerLogs.length;
|
|
128
|
+
stats.tokensPerMinute = providerLogs.reduce((sum, l) => sum + (l.inputTokens ?? 0) + (l.outputTokens ?? 0), 0);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// ============================================================================
|
|
132
|
+
// Public API
|
|
133
|
+
// ============================================================================
|
|
134
|
+
/**
|
|
135
|
+
* Configures the request monitor.
|
|
136
|
+
*
|
|
137
|
+
* @param newConfig - Partial configuration to merge
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```typescript
|
|
141
|
+
* RequestMonitor.configure({
|
|
142
|
+
* maxLogs: 500,
|
|
143
|
+
* calculateCosts: false
|
|
144
|
+
* })
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
function configure(newConfig) {
|
|
148
|
+
config = { ...config, ...newConfig };
|
|
149
|
+
log.info("configured", { ...config });
|
|
150
|
+
// Trim logs if max reduced
|
|
151
|
+
while (logs.length > config.maxLogs) {
|
|
152
|
+
logs.shift();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
RequestMonitor.configure = configure;
|
|
156
|
+
/**
|
|
157
|
+
* Gets the current configuration.
|
|
158
|
+
*/
|
|
159
|
+
function getConfig() {
|
|
160
|
+
return { ...config };
|
|
161
|
+
}
|
|
162
|
+
RequestMonitor.getConfig = getConfig;
|
|
163
|
+
/**
|
|
164
|
+
* Logs a new API request.
|
|
165
|
+
*
|
|
166
|
+
* @param entry - Partial request log entry (id and timestamp auto-generated if missing)
|
|
167
|
+
* @returns The complete log entry
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```typescript
|
|
171
|
+
* const entry = RequestMonitor.logRequest({
|
|
172
|
+
* providerID: "anthropic",
|
|
173
|
+
* modelID: "claude-sonnet-4-20250514",
|
|
174
|
+
* method: "chat",
|
|
175
|
+
* status: 200,
|
|
176
|
+
* latencyMs: 1234,
|
|
177
|
+
* inputTokens: 500,
|
|
178
|
+
* outputTokens: 200,
|
|
179
|
+
* streaming: true
|
|
180
|
+
* })
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
function logRequest(entry) {
|
|
184
|
+
if (!config.enabled) {
|
|
185
|
+
return {
|
|
186
|
+
...entry,
|
|
187
|
+
id: entry.id ?? generateID(),
|
|
188
|
+
timestamp: entry.timestamp ?? Date.now(),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const requestLog = {
|
|
192
|
+
...entry,
|
|
193
|
+
id: entry.id ?? generateID(),
|
|
194
|
+
timestamp: entry.timestamp ?? Date.now(),
|
|
195
|
+
};
|
|
196
|
+
// Add to ring buffer (remove oldest if at capacity)
|
|
197
|
+
if (logs.length >= config.maxLogs) {
|
|
198
|
+
logs.shift();
|
|
199
|
+
}
|
|
200
|
+
logs.push(requestLog);
|
|
201
|
+
// Update stats
|
|
202
|
+
updateStats(globalStats, requestLog);
|
|
203
|
+
if (!providerStats.has(requestLog.providerID)) {
|
|
204
|
+
providerStats.set(requestLog.providerID, createEmptyStats());
|
|
205
|
+
}
|
|
206
|
+
updateStats(providerStats.get(requestLog.providerID), requestLog);
|
|
207
|
+
// Update rolling window
|
|
208
|
+
const totalTokens = (requestLog.inputTokens ?? 0) + (requestLog.outputTokens ?? 0);
|
|
209
|
+
rollingWindow.push({ timestamp: requestLog.timestamp, tokens: totalTokens });
|
|
210
|
+
updateRollingMetrics();
|
|
211
|
+
// Emit event
|
|
212
|
+
if (config.emitEvents) {
|
|
213
|
+
const event = { type: "request", log: requestLog };
|
|
214
|
+
for (const listener of listeners) {
|
|
215
|
+
try {
|
|
216
|
+
listener(event);
|
|
217
|
+
}
|
|
218
|
+
catch (e) {
|
|
219
|
+
log.error("event-listener-error", { error: String(e) });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
log.debug("logged", {
|
|
224
|
+
id: requestLog.id,
|
|
225
|
+
provider: requestLog.providerID,
|
|
226
|
+
model: requestLog.modelID,
|
|
227
|
+
status: requestLog.status,
|
|
228
|
+
latency: requestLog.latencyMs,
|
|
229
|
+
});
|
|
230
|
+
return requestLog;
|
|
231
|
+
}
|
|
232
|
+
RequestMonitor.logRequest = logRequest;
|
|
233
|
+
/**
|
|
234
|
+
* Gets recent request logs.
|
|
235
|
+
*
|
|
236
|
+
* @param options - Filter options
|
|
237
|
+
* @returns Array of request logs (newest first)
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```typescript
|
|
241
|
+
* // Get last 10 logs
|
|
242
|
+
* const recent = RequestMonitor.getRecentLogs({ limit: 10 })
|
|
243
|
+
*
|
|
244
|
+
* // Get logs for a specific provider
|
|
245
|
+
* const anthropicLogs = RequestMonitor.getRecentLogs({
|
|
246
|
+
* providerID: "anthropic",
|
|
247
|
+
* limit: 50
|
|
248
|
+
* })
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
function getRecentLogs(options = {}) {
|
|
252
|
+
let result = [...logs];
|
|
253
|
+
// Apply filters
|
|
254
|
+
if (options.providerID) {
|
|
255
|
+
result = result.filter((l) => l.providerID === options.providerID);
|
|
256
|
+
}
|
|
257
|
+
if (options.modelID) {
|
|
258
|
+
result = result.filter((l) => l.modelID === options.modelID);
|
|
259
|
+
}
|
|
260
|
+
if (options.sessionID) {
|
|
261
|
+
result = result.filter((l) => l.sessionID === options.sessionID);
|
|
262
|
+
}
|
|
263
|
+
if (options.minStatus !== undefined) {
|
|
264
|
+
result = result.filter((l) => l.status >= options.minStatus);
|
|
265
|
+
}
|
|
266
|
+
if (options.maxStatus !== undefined) {
|
|
267
|
+
result = result.filter((l) => l.status <= options.maxStatus);
|
|
268
|
+
}
|
|
269
|
+
if (options.since !== undefined) {
|
|
270
|
+
result = result.filter((l) => l.timestamp >= options.since);
|
|
271
|
+
}
|
|
272
|
+
if (options.until !== undefined) {
|
|
273
|
+
result = result.filter((l) => l.timestamp <= options.until);
|
|
274
|
+
}
|
|
275
|
+
// Sort newest first and apply limit
|
|
276
|
+
result.reverse();
|
|
277
|
+
if (options.limit) {
|
|
278
|
+
result = result.slice(0, options.limit);
|
|
279
|
+
}
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
RequestMonitor.getRecentLogs = getRecentLogs;
|
|
283
|
+
/**
|
|
284
|
+
* Gets a specific request log by ID.
|
|
285
|
+
*
|
|
286
|
+
* @param id - Request ID
|
|
287
|
+
* @returns The log entry or undefined if not found
|
|
288
|
+
*/
|
|
289
|
+
function getLog(id) {
|
|
290
|
+
return logs.find((l) => l.id === id);
|
|
291
|
+
}
|
|
292
|
+
RequestMonitor.getLog = getLog;
|
|
293
|
+
/**
|
|
294
|
+
* Gets statistics for a provider or overall.
|
|
295
|
+
*
|
|
296
|
+
* @param providerID - Optional provider ID (undefined for global stats)
|
|
297
|
+
* @returns Statistics object
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* ```typescript
|
|
301
|
+
* // Get global stats
|
|
302
|
+
* const global = RequestMonitor.getStats()
|
|
303
|
+
*
|
|
304
|
+
* // Get provider-specific stats
|
|
305
|
+
* const anthropic = RequestMonitor.getStats("anthropic")
|
|
306
|
+
* ```
|
|
307
|
+
*/
|
|
308
|
+
function getStats(providerID) {
|
|
309
|
+
// Calculate percentiles on demand
|
|
310
|
+
calculatePercentiles(globalStats);
|
|
311
|
+
for (const stats of providerStats.values()) {
|
|
312
|
+
calculatePercentiles(stats);
|
|
313
|
+
}
|
|
314
|
+
updateRollingMetrics();
|
|
315
|
+
if (providerID) {
|
|
316
|
+
return providerStats.get(providerID) ?? createEmptyStats();
|
|
317
|
+
}
|
|
318
|
+
return { ...globalStats };
|
|
319
|
+
}
|
|
320
|
+
RequestMonitor.getStats = getStats;
|
|
321
|
+
/**
|
|
322
|
+
* Gets statistics for all providers.
|
|
323
|
+
*
|
|
324
|
+
* @returns Map of provider ID to statistics
|
|
325
|
+
*/
|
|
326
|
+
function getAllStats() {
|
|
327
|
+
updateRollingMetrics();
|
|
328
|
+
calculatePercentiles(globalStats);
|
|
329
|
+
const result = {};
|
|
330
|
+
for (const [providerID, stats] of providerStats.entries()) {
|
|
331
|
+
calculatePercentiles(stats);
|
|
332
|
+
result[providerID] = { ...stats };
|
|
333
|
+
}
|
|
334
|
+
return result;
|
|
335
|
+
}
|
|
336
|
+
RequestMonitor.getAllStats = getAllStats;
|
|
337
|
+
/**
|
|
338
|
+
* Gets a summary of all monitoring data.
|
|
339
|
+
*
|
|
340
|
+
* @returns Summary object suitable for API responses
|
|
341
|
+
*/
|
|
342
|
+
function getSummary() {
|
|
343
|
+
return {
|
|
344
|
+
enabled: config.enabled,
|
|
345
|
+
logCount: logs.length,
|
|
346
|
+
global: getStats(),
|
|
347
|
+
providers: getAllStats(),
|
|
348
|
+
recentErrors: getRecentLogs({ minStatus: 400, limit: 10 }),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
RequestMonitor.getSummary = getSummary;
|
|
352
|
+
/**
|
|
353
|
+
* Adds an event listener.
|
|
354
|
+
*
|
|
355
|
+
* @param listener - Callback function
|
|
356
|
+
* @returns Unsubscribe function
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* ```typescript
|
|
360
|
+
* const unsubscribe = RequestMonitor.addListener((event) => {
|
|
361
|
+
* console.log("Request:", event.log.id)
|
|
362
|
+
* })
|
|
363
|
+
*
|
|
364
|
+
* // Later...
|
|
365
|
+
* unsubscribe()
|
|
366
|
+
* ```
|
|
367
|
+
*/
|
|
368
|
+
function addListener(listener) {
|
|
369
|
+
listeners.add(listener);
|
|
370
|
+
return () => listeners.delete(listener);
|
|
371
|
+
}
|
|
372
|
+
RequestMonitor.addListener = addListener;
|
|
373
|
+
/**
|
|
374
|
+
* Exports logs to JSON format.
|
|
375
|
+
*
|
|
376
|
+
* @param options - Export options
|
|
377
|
+
* @returns JSON string
|
|
378
|
+
*/
|
|
379
|
+
function exportJSON(options = {}) {
|
|
380
|
+
const data = {
|
|
381
|
+
exportedAt: new Date().toISOString(),
|
|
382
|
+
global: getStats(),
|
|
383
|
+
providers: getAllStats(),
|
|
384
|
+
logs: getRecentLogs({
|
|
385
|
+
providerID: options.providerID,
|
|
386
|
+
since: options.since,
|
|
387
|
+
until: options.until,
|
|
388
|
+
}),
|
|
389
|
+
};
|
|
390
|
+
return JSON.stringify(data, null, options.pretty ? 2 : 0);
|
|
391
|
+
}
|
|
392
|
+
RequestMonitor.exportJSON = exportJSON;
|
|
393
|
+
/**
|
|
394
|
+
* Clears all logs and resets statistics.
|
|
395
|
+
*/
|
|
396
|
+
function clear() {
|
|
397
|
+
logs.length = 0;
|
|
398
|
+
providerStats.clear();
|
|
399
|
+
globalStats = createEmptyStats();
|
|
400
|
+
rollingWindow.length = 0;
|
|
401
|
+
log.info("cleared");
|
|
402
|
+
}
|
|
403
|
+
RequestMonitor.clear = clear;
|
|
404
|
+
/**
|
|
405
|
+
* Clears logs and stats for a specific provider.
|
|
406
|
+
*
|
|
407
|
+
* @param providerID - Provider ID to clear
|
|
408
|
+
*/
|
|
409
|
+
function clearProvider(providerID) {
|
|
410
|
+
// Remove logs for this provider
|
|
411
|
+
for (let i = logs.length - 1; i >= 0; i--) {
|
|
412
|
+
if (logs[i].providerID === providerID) {
|
|
413
|
+
logs.splice(i, 1);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
// Remove provider stats
|
|
417
|
+
providerStats.delete(providerID);
|
|
418
|
+
// Recalculate global stats
|
|
419
|
+
globalStats = createEmptyStats();
|
|
420
|
+
for (const l of logs) {
|
|
421
|
+
updateStats(globalStats, l);
|
|
422
|
+
}
|
|
423
|
+
log.info("cleared-provider", { providerID });
|
|
424
|
+
}
|
|
425
|
+
RequestMonitor.clearProvider = clearProvider;
|
|
426
|
+
/**
|
|
427
|
+
* Enables monitoring.
|
|
428
|
+
*/
|
|
429
|
+
function enable() {
|
|
430
|
+
config.enabled = true;
|
|
431
|
+
log.info("enabled");
|
|
432
|
+
}
|
|
433
|
+
RequestMonitor.enable = enable;
|
|
434
|
+
/**
|
|
435
|
+
* Disables monitoring.
|
|
436
|
+
*/
|
|
437
|
+
function disable() {
|
|
438
|
+
config.enabled = false;
|
|
439
|
+
log.info("disabled");
|
|
440
|
+
}
|
|
441
|
+
RequestMonitor.disable = disable;
|
|
442
|
+
/**
|
|
443
|
+
* Checks if monitoring is enabled.
|
|
444
|
+
*/
|
|
445
|
+
function isEnabled() {
|
|
446
|
+
return config.enabled;
|
|
447
|
+
}
|
|
448
|
+
RequestMonitor.isEnabled = isEnabled;
|
|
449
|
+
})(RequestMonitor || (RequestMonitor = {}));
|