@kb-labs/core-resource-broker 1.0.0
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/README.md +96 -0
- package/dist/index.d.ts +1217 -0
- package/dist/index.js +1418 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1418 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
|
|
3
|
+
// src/types.ts
|
|
4
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
5
|
+
maxRetries: 3,
|
|
6
|
+
baseDelay: 1e3,
|
|
7
|
+
maxDelay: 3e4,
|
|
8
|
+
jitter: 0.1,
|
|
9
|
+
retryableErrors: ["rate_limit", "server_error", "timeout", "network"]
|
|
10
|
+
};
|
|
11
|
+
var DEFAULT_RATE_LIMIT_CONFIG = {
|
|
12
|
+
safetyMargin: 0.9
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// src/queue/priority-queue.ts
|
|
16
|
+
var PriorityQueue = class {
|
|
17
|
+
high = [];
|
|
18
|
+
normal = [];
|
|
19
|
+
low = [];
|
|
20
|
+
/**
|
|
21
|
+
* Add an item to the queue.
|
|
22
|
+
*
|
|
23
|
+
* @param item - Queue item to add
|
|
24
|
+
*/
|
|
25
|
+
enqueue(item) {
|
|
26
|
+
const priority = item.request.priority;
|
|
27
|
+
switch (priority) {
|
|
28
|
+
case "high":
|
|
29
|
+
this.high.push(item);
|
|
30
|
+
break;
|
|
31
|
+
case "low":
|
|
32
|
+
this.low.push(item);
|
|
33
|
+
break;
|
|
34
|
+
default:
|
|
35
|
+
this.normal.push(item);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Remove and return the highest priority item.
|
|
40
|
+
*
|
|
41
|
+
* @returns The next item or undefined if queue is empty
|
|
42
|
+
*/
|
|
43
|
+
dequeue() {
|
|
44
|
+
if (this.high.length > 0) {
|
|
45
|
+
return this.high.shift();
|
|
46
|
+
}
|
|
47
|
+
if (this.normal.length > 0) {
|
|
48
|
+
return this.normal.shift();
|
|
49
|
+
}
|
|
50
|
+
if (this.low.length > 0) {
|
|
51
|
+
return this.low.shift();
|
|
52
|
+
}
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Peek at the highest priority item without removing it.
|
|
57
|
+
*
|
|
58
|
+
* @returns The next item or undefined if queue is empty
|
|
59
|
+
*/
|
|
60
|
+
peek() {
|
|
61
|
+
if (this.high.length > 0) {
|
|
62
|
+
return this.high[0];
|
|
63
|
+
}
|
|
64
|
+
if (this.normal.length > 0) {
|
|
65
|
+
return this.normal[0];
|
|
66
|
+
}
|
|
67
|
+
if (this.low.length > 0) {
|
|
68
|
+
return this.low[0];
|
|
69
|
+
}
|
|
70
|
+
return void 0;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get total queue size.
|
|
74
|
+
*/
|
|
75
|
+
size() {
|
|
76
|
+
return this.high.length + this.normal.length + this.low.length;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get queue size by priority.
|
|
80
|
+
*/
|
|
81
|
+
sizeByPriority() {
|
|
82
|
+
return {
|
|
83
|
+
high: this.high.length,
|
|
84
|
+
normal: this.normal.length,
|
|
85
|
+
low: this.low.length
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Check if queue is empty.
|
|
90
|
+
*/
|
|
91
|
+
isEmpty() {
|
|
92
|
+
return this.size() === 0;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Clear all items from the queue.
|
|
96
|
+
*
|
|
97
|
+
* @returns All removed items (for cleanup/rejection)
|
|
98
|
+
*/
|
|
99
|
+
clear() {
|
|
100
|
+
const all = [...this.high, ...this.normal, ...this.low];
|
|
101
|
+
this.high = [];
|
|
102
|
+
this.normal = [];
|
|
103
|
+
this.low = [];
|
|
104
|
+
return all;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Remove a specific item by request ID.
|
|
108
|
+
*
|
|
109
|
+
* @param requestId - ID of the request to remove
|
|
110
|
+
* @returns The removed item or undefined if not found
|
|
111
|
+
*/
|
|
112
|
+
remove(requestId) {
|
|
113
|
+
const highIndex = this.high.findIndex((item) => item.request.id === requestId);
|
|
114
|
+
if (highIndex !== -1) {
|
|
115
|
+
return this.high.splice(highIndex, 1)[0];
|
|
116
|
+
}
|
|
117
|
+
const normalIndex = this.normal.findIndex((item) => item.request.id === requestId);
|
|
118
|
+
if (normalIndex !== -1) {
|
|
119
|
+
return this.normal.splice(normalIndex, 1)[0];
|
|
120
|
+
}
|
|
121
|
+
const lowIndex = this.low.findIndex((item) => item.request.id === requestId);
|
|
122
|
+
if (lowIndex !== -1) {
|
|
123
|
+
return this.low.splice(lowIndex, 1)[0];
|
|
124
|
+
}
|
|
125
|
+
return void 0;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get all items for a specific resource.
|
|
129
|
+
*
|
|
130
|
+
* @param resource - Resource identifier
|
|
131
|
+
* @returns Items matching the resource
|
|
132
|
+
*/
|
|
133
|
+
getByResource(resource) {
|
|
134
|
+
return [
|
|
135
|
+
...this.high.filter((item) => item.request.resource === resource),
|
|
136
|
+
...this.normal.filter((item) => item.request.resource === resource),
|
|
137
|
+
...this.low.filter((item) => item.request.resource === resource)
|
|
138
|
+
];
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get queue size for a specific resource.
|
|
142
|
+
*
|
|
143
|
+
* @param resource - Resource identifier
|
|
144
|
+
*/
|
|
145
|
+
sizeByResource(resource) {
|
|
146
|
+
return this.high.filter((item) => item.request.resource === resource).length + this.normal.filter((item) => item.request.resource === resource).length + this.low.filter((item) => item.request.resource === resource).length;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Iterate over all items (for inspection, not removal).
|
|
150
|
+
*/
|
|
151
|
+
*[Symbol.iterator]() {
|
|
152
|
+
yield* this.high;
|
|
153
|
+
yield* this.normal;
|
|
154
|
+
yield* this.low;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// src/rate-limit/presets.ts
|
|
159
|
+
var RATE_LIMIT_PRESETS = {
|
|
160
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
161
|
+
// OPENAI
|
|
162
|
+
// https://platform.openai.com/docs/guides/rate-limits
|
|
163
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
164
|
+
/**
|
|
165
|
+
* OpenAI Tier 1 (paid accounts, entry level)
|
|
166
|
+
* Updated Nov 2024: Tier 1 now has 1M TPM for embeddings
|
|
167
|
+
*/
|
|
168
|
+
"openai-tier-1": {
|
|
169
|
+
tokensPerMinute: 1e6,
|
|
170
|
+
requestsPerMinute: 3e3,
|
|
171
|
+
maxTokensPerRequest: 8191,
|
|
172
|
+
safetyMargin: 0.85
|
|
173
|
+
// More conservative to avoid edge cases
|
|
174
|
+
},
|
|
175
|
+
/**
|
|
176
|
+
* OpenAI Tier 2 (after $50+ spent)
|
|
177
|
+
*/
|
|
178
|
+
"openai-tier-2": {
|
|
179
|
+
tokensPerMinute: 2e6,
|
|
180
|
+
requestsPerMinute: 5e3,
|
|
181
|
+
maxTokensPerRequest: 8191,
|
|
182
|
+
safetyMargin: 0.9
|
|
183
|
+
},
|
|
184
|
+
/**
|
|
185
|
+
* OpenAI Tier 3
|
|
186
|
+
*/
|
|
187
|
+
"openai-tier-3": {
|
|
188
|
+
tokensPerMinute: 5e6,
|
|
189
|
+
requestsPerMinute: 5e3,
|
|
190
|
+
maxTokensPerRequest: 8191,
|
|
191
|
+
safetyMargin: 0.9
|
|
192
|
+
},
|
|
193
|
+
/**
|
|
194
|
+
* OpenAI Tier 4
|
|
195
|
+
*/
|
|
196
|
+
"openai-tier-4": {
|
|
197
|
+
tokensPerMinute: 1e7,
|
|
198
|
+
requestsPerMinute: 1e4,
|
|
199
|
+
maxTokensPerRequest: 8191,
|
|
200
|
+
safetyMargin: 0.9
|
|
201
|
+
},
|
|
202
|
+
/**
|
|
203
|
+
* OpenAI Tier 5 (enterprise)
|
|
204
|
+
*/
|
|
205
|
+
"openai-tier-5": {
|
|
206
|
+
tokensPerMinute: 5e7,
|
|
207
|
+
requestsPerMinute: 1e4,
|
|
208
|
+
maxTokensPerRequest: 8191,
|
|
209
|
+
safetyMargin: 0.9
|
|
210
|
+
},
|
|
211
|
+
/**
|
|
212
|
+
* OpenAI GPT-4 specific limits (more restrictive)
|
|
213
|
+
*/
|
|
214
|
+
"openai-gpt4": {
|
|
215
|
+
tokensPerMinute: 15e4,
|
|
216
|
+
requestsPerMinute: 500,
|
|
217
|
+
maxTokensPerRequest: 8192,
|
|
218
|
+
safetyMargin: 0.85
|
|
219
|
+
},
|
|
220
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
221
|
+
// ANTHROPIC
|
|
222
|
+
// https://docs.anthropic.com/en/api/rate-limits
|
|
223
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
224
|
+
/**
|
|
225
|
+
* Anthropic Tier 1 (default)
|
|
226
|
+
*/
|
|
227
|
+
"anthropic-tier-1": {
|
|
228
|
+
tokensPerMinute: 4e4,
|
|
229
|
+
requestsPerMinute: 50,
|
|
230
|
+
maxTokensPerRequest: 4096,
|
|
231
|
+
safetyMargin: 0.85
|
|
232
|
+
},
|
|
233
|
+
/**
|
|
234
|
+
* Anthropic Tier 2
|
|
235
|
+
*/
|
|
236
|
+
"anthropic-tier-2": {
|
|
237
|
+
tokensPerMinute: 8e4,
|
|
238
|
+
requestsPerMinute: 100,
|
|
239
|
+
maxTokensPerRequest: 4096,
|
|
240
|
+
safetyMargin: 0.9
|
|
241
|
+
},
|
|
242
|
+
/**
|
|
243
|
+
* Anthropic Tier 3
|
|
244
|
+
*/
|
|
245
|
+
"anthropic-tier-3": {
|
|
246
|
+
tokensPerMinute: 16e4,
|
|
247
|
+
requestsPerMinute: 200,
|
|
248
|
+
maxTokensPerRequest: 4096,
|
|
249
|
+
safetyMargin: 0.9
|
|
250
|
+
},
|
|
251
|
+
/**
|
|
252
|
+
* Anthropic Tier 4
|
|
253
|
+
*/
|
|
254
|
+
"anthropic-tier-4": {
|
|
255
|
+
tokensPerMinute: 4e5,
|
|
256
|
+
requestsPerMinute: 400,
|
|
257
|
+
maxTokensPerRequest: 4096,
|
|
258
|
+
safetyMargin: 0.9
|
|
259
|
+
},
|
|
260
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
261
|
+
// RUSSIAN PROVIDERS
|
|
262
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
263
|
+
/**
|
|
264
|
+
* Sber GigaChat API
|
|
265
|
+
* Conservative limits for typical access
|
|
266
|
+
*/
|
|
267
|
+
"sber-gigachat": {
|
|
268
|
+
requestsPerMinute: 100,
|
|
269
|
+
requestsPerSecond: 5,
|
|
270
|
+
safetyMargin: 0.8
|
|
271
|
+
},
|
|
272
|
+
/**
|
|
273
|
+
* Yandex GPT API
|
|
274
|
+
*/
|
|
275
|
+
"yandex-gpt": {
|
|
276
|
+
requestsPerMinute: 100,
|
|
277
|
+
requestsPerSecond: 10,
|
|
278
|
+
safetyMargin: 0.8
|
|
279
|
+
},
|
|
280
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
281
|
+
// LOCAL MODELS
|
|
282
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
283
|
+
/**
|
|
284
|
+
* Local Ollama
|
|
285
|
+
* No external rate limits, only GPU concurrency
|
|
286
|
+
*/
|
|
287
|
+
"ollama-local": {
|
|
288
|
+
maxConcurrentRequests: 4
|
|
289
|
+
},
|
|
290
|
+
/**
|
|
291
|
+
* Self-hosted vLLM
|
|
292
|
+
*/
|
|
293
|
+
"vllm-local": {
|
|
294
|
+
maxConcurrentRequests: 8,
|
|
295
|
+
requestsPerSecond: 100
|
|
296
|
+
},
|
|
297
|
+
/**
|
|
298
|
+
* Self-hosted text-embeddings-inference
|
|
299
|
+
*/
|
|
300
|
+
"tei-local": {
|
|
301
|
+
maxConcurrentRequests: 16
|
|
302
|
+
},
|
|
303
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
304
|
+
// SPECIAL
|
|
305
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
306
|
+
/**
|
|
307
|
+
* No rate limiting (for testing or unlimited APIs)
|
|
308
|
+
*/
|
|
309
|
+
unlimited: {},
|
|
310
|
+
/**
|
|
311
|
+
* Very conservative (for debugging rate limit issues)
|
|
312
|
+
*/
|
|
313
|
+
debug: {
|
|
314
|
+
tokensPerMinute: 1e4,
|
|
315
|
+
requestsPerMinute: 10,
|
|
316
|
+
maxConcurrentRequests: 1,
|
|
317
|
+
safetyMargin: 0.5
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
function getRateLimitConfig(configOrPreset) {
|
|
321
|
+
if (!configOrPreset) {
|
|
322
|
+
return RATE_LIMIT_PRESETS["openai-tier-2"];
|
|
323
|
+
}
|
|
324
|
+
if (typeof configOrPreset === "string") {
|
|
325
|
+
const preset = RATE_LIMIT_PRESETS[configOrPreset];
|
|
326
|
+
if (!preset) {
|
|
327
|
+
throw new Error(`Unknown rate limit preset: ${configOrPreset}`);
|
|
328
|
+
}
|
|
329
|
+
return preset;
|
|
330
|
+
}
|
|
331
|
+
return configOrPreset;
|
|
332
|
+
}
|
|
333
|
+
function estimateTokens(text) {
|
|
334
|
+
return Math.ceil(text.length / 3.5);
|
|
335
|
+
}
|
|
336
|
+
function estimateBatchTokens(texts) {
|
|
337
|
+
return texts.reduce((sum, text) => sum + estimateTokens(text), 0);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/retry/error-classifier.ts
|
|
341
|
+
function classifyError(error) {
|
|
342
|
+
if (!error) {
|
|
343
|
+
return "unknown";
|
|
344
|
+
}
|
|
345
|
+
if (error instanceof Error) {
|
|
346
|
+
const message = error.message.toLowerCase();
|
|
347
|
+
const name = error.name.toLowerCase();
|
|
348
|
+
if (message.includes("429") || message.includes("rate limit") || message.includes("too many requests") || message.includes("quota exceeded") || name.includes("ratelimit")) {
|
|
349
|
+
return "rate_limit";
|
|
350
|
+
}
|
|
351
|
+
if (message.includes("timeout") || message.includes("timed out") || message.includes("etimedout") || message.includes("deadline exceeded") || name.includes("timeout")) {
|
|
352
|
+
return "timeout";
|
|
353
|
+
}
|
|
354
|
+
if (message.includes("econnrefused") || message.includes("econnreset") || message.includes("enotfound") || message.includes("network") || message.includes("socket") || message.includes("dns") || name.includes("fetch") || name.includes("network")) {
|
|
355
|
+
return "network";
|
|
356
|
+
}
|
|
357
|
+
if (message.includes("500") || message.includes("502") || message.includes("503") || message.includes("504") || message.includes("internal server") || message.includes("bad gateway") || message.includes("service unavailable") || message.includes("gateway timeout")) {
|
|
358
|
+
return "server_error";
|
|
359
|
+
}
|
|
360
|
+
if (message.includes("400") || message.includes("401") || message.includes("403") || message.includes("404") || message.includes("bad request") || message.includes("unauthorized") || message.includes("forbidden") || message.includes("not found")) {
|
|
361
|
+
return "client_error";
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (typeof error === "object" && error !== null) {
|
|
365
|
+
const obj = error;
|
|
366
|
+
const status = obj.status ?? obj.statusCode ?? obj.code;
|
|
367
|
+
if (typeof status === "number") {
|
|
368
|
+
if (status === 429) {
|
|
369
|
+
return "rate_limit";
|
|
370
|
+
}
|
|
371
|
+
if (status >= 500) {
|
|
372
|
+
return "server_error";
|
|
373
|
+
}
|
|
374
|
+
if (status >= 400) {
|
|
375
|
+
return "client_error";
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const code = obj.code;
|
|
379
|
+
if (typeof code === "string") {
|
|
380
|
+
const lowerCode = code.toLowerCase();
|
|
381
|
+
if (lowerCode.includes("timeout")) {
|
|
382
|
+
return "timeout";
|
|
383
|
+
}
|
|
384
|
+
if (lowerCode.includes("econnrefused")) {
|
|
385
|
+
return "network";
|
|
386
|
+
}
|
|
387
|
+
if (lowerCode.includes("econnreset")) {
|
|
388
|
+
return "network";
|
|
389
|
+
}
|
|
390
|
+
if (lowerCode.includes("enotfound")) {
|
|
391
|
+
return "network";
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return "unknown";
|
|
396
|
+
}
|
|
397
|
+
function isRateLimitError(error) {
|
|
398
|
+
return classifyError(error) === "rate_limit";
|
|
399
|
+
}
|
|
400
|
+
function isRetryableError(error, retryableTypes = ["rate_limit", "server_error", "timeout", "network"]) {
|
|
401
|
+
const errorType = classifyError(error);
|
|
402
|
+
return retryableTypes.includes(errorType);
|
|
403
|
+
}
|
|
404
|
+
function extractRetryAfter(error) {
|
|
405
|
+
if (!error || typeof error !== "object") {
|
|
406
|
+
return void 0;
|
|
407
|
+
}
|
|
408
|
+
const obj = error;
|
|
409
|
+
const retryAfter = obj.retryAfter ?? obj["retry-after"] ?? obj.retryAfterMs ?? obj.headers?.["retry-after"];
|
|
410
|
+
if (typeof retryAfter === "number") {
|
|
411
|
+
return retryAfter < 1e3 ? retryAfter * 1e3 : retryAfter;
|
|
412
|
+
}
|
|
413
|
+
if (typeof retryAfter === "string") {
|
|
414
|
+
const parsed = parseInt(retryAfter, 10);
|
|
415
|
+
if (!isNaN(parsed)) {
|
|
416
|
+
return parsed < 1e3 ? parsed * 1e3 : parsed;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return void 0;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/retry/retry-strategy.ts
|
|
423
|
+
function calculateBackoffDelay(attempt, config, retryAfterHint) {
|
|
424
|
+
if (retryAfterHint !== void 0 && retryAfterHint > 0) {
|
|
425
|
+
return Math.min(retryAfterHint, config.maxDelay);
|
|
426
|
+
}
|
|
427
|
+
const exponentialDelay = config.baseDelay * Math.pow(2, attempt);
|
|
428
|
+
const cappedDelay = Math.min(exponentialDelay, config.maxDelay);
|
|
429
|
+
const jitterMultiplier = 1 + Math.random() * config.jitter;
|
|
430
|
+
return Math.floor(cappedDelay * jitterMultiplier);
|
|
431
|
+
}
|
|
432
|
+
function shouldRetry(error, attempt, config = {}) {
|
|
433
|
+
const fullConfig = {
|
|
434
|
+
...DEFAULT_RETRY_CONFIG,
|
|
435
|
+
...config
|
|
436
|
+
};
|
|
437
|
+
const errorType = classifyError(error);
|
|
438
|
+
const isRetryable = isRetryableError(error, fullConfig.retryableErrors);
|
|
439
|
+
const hasAttemptsLeft = attempt < fullConfig.maxRetries;
|
|
440
|
+
const shouldRetryNow = isRetryable && hasAttemptsLeft;
|
|
441
|
+
let delayMs = 0;
|
|
442
|
+
if (shouldRetryNow) {
|
|
443
|
+
const retryAfterHint = extractRetryAfter(error);
|
|
444
|
+
if (errorType === "rate_limit") {
|
|
445
|
+
const rateLimitConfig = {
|
|
446
|
+
...fullConfig,
|
|
447
|
+
baseDelay: Math.max(fullConfig.baseDelay, 5e3)
|
|
448
|
+
// At least 5s for rate limits
|
|
449
|
+
};
|
|
450
|
+
delayMs = calculateBackoffDelay(attempt, rateLimitConfig, retryAfterHint);
|
|
451
|
+
} else {
|
|
452
|
+
delayMs = calculateBackoffDelay(attempt, fullConfig, retryAfterHint);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
shouldRetry: shouldRetryNow,
|
|
457
|
+
delayMs,
|
|
458
|
+
errorType,
|
|
459
|
+
attempt,
|
|
460
|
+
maxAttempts: fullConfig.maxRetries
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
async function withRetry(fn, config = {}) {
|
|
464
|
+
const fullConfig = {
|
|
465
|
+
...DEFAULT_RETRY_CONFIG,
|
|
466
|
+
...config
|
|
467
|
+
};
|
|
468
|
+
let lastError;
|
|
469
|
+
let attempts = 0;
|
|
470
|
+
for (let attempt = 0; attempt <= fullConfig.maxRetries; attempt++) {
|
|
471
|
+
attempts = attempt + 1;
|
|
472
|
+
try {
|
|
473
|
+
const result = await fn();
|
|
474
|
+
return { result, attempts };
|
|
475
|
+
} catch (error) {
|
|
476
|
+
lastError = error;
|
|
477
|
+
const decision = shouldRetry(error, attempt, fullConfig);
|
|
478
|
+
if (!decision.shouldRetry) {
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
await sleep(decision.delayMs);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
throw lastError;
|
|
485
|
+
}
|
|
486
|
+
function sleep(ms) {
|
|
487
|
+
return new Promise((resolve) => {
|
|
488
|
+
setTimeout(resolve, ms);
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
function createRateLimitRetryConfig(maxRetries = 5) {
|
|
492
|
+
return {
|
|
493
|
+
maxRetries,
|
|
494
|
+
baseDelay: 5e3,
|
|
495
|
+
// Start with 5s for rate limits
|
|
496
|
+
maxDelay: 6e4,
|
|
497
|
+
// Cap at 1 minute
|
|
498
|
+
jitter: 0.2,
|
|
499
|
+
// 20% jitter for distributed systems
|
|
500
|
+
retryableErrors: ["rate_limit", "server_error", "timeout", "network"]
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
function createQuickRetryConfig(maxRetries = 3) {
|
|
504
|
+
return {
|
|
505
|
+
maxRetries,
|
|
506
|
+
baseDelay: 500,
|
|
507
|
+
// Start with 500ms
|
|
508
|
+
maxDelay: 5e3,
|
|
509
|
+
// Cap at 5s
|
|
510
|
+
jitter: 0.1,
|
|
511
|
+
// 10% jitter
|
|
512
|
+
retryableErrors: ["server_error", "timeout", "network"]
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// src/broker/resource-broker.ts
|
|
517
|
+
var ResourceBroker = class {
|
|
518
|
+
constructor(rateLimitBackend) {
|
|
519
|
+
this.rateLimitBackend = rateLimitBackend;
|
|
520
|
+
}
|
|
521
|
+
resources = /* @__PURE__ */ new Map();
|
|
522
|
+
queue = new PriorityQueue();
|
|
523
|
+
processing = false;
|
|
524
|
+
shuttingDown = false;
|
|
525
|
+
startTime = Date.now();
|
|
526
|
+
/**
|
|
527
|
+
* Active processing count per resource (for concurrent limit tracking).
|
|
528
|
+
*/
|
|
529
|
+
activeProcessing = /* @__PURE__ */ new Map();
|
|
530
|
+
/**
|
|
531
|
+
* Register a resource with its configuration.
|
|
532
|
+
*
|
|
533
|
+
* @param resource - Resource identifier ('llm', 'embeddings', 'vectorStore')
|
|
534
|
+
* @param config - Resource configuration including executor
|
|
535
|
+
*/
|
|
536
|
+
register(resource, config) {
|
|
537
|
+
const rateLimits = typeof config.rateLimits === "string" ? getRateLimitConfig(config.rateLimits) : config.rateLimits ?? DEFAULT_RATE_LIMIT_CONFIG;
|
|
538
|
+
this.resources.set(resource, {
|
|
539
|
+
config,
|
|
540
|
+
rateLimits,
|
|
541
|
+
stats: {
|
|
542
|
+
totalRequests: 0,
|
|
543
|
+
totalSuccess: 0,
|
|
544
|
+
totalErrors: 0,
|
|
545
|
+
totalWaitTime: 0,
|
|
546
|
+
totalProcessingTime: 0
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
this.activeProcessing.set(resource, 0);
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Enqueue a request for execution.
|
|
553
|
+
*
|
|
554
|
+
* @param request - Resource request (without id and createdAt)
|
|
555
|
+
* @returns Promise that resolves with response when execution completes
|
|
556
|
+
*/
|
|
557
|
+
enqueue(request) {
|
|
558
|
+
if (this.shuttingDown) {
|
|
559
|
+
return Promise.resolve({
|
|
560
|
+
success: false,
|
|
561
|
+
error: new Error("ResourceBroker is shutting down"),
|
|
562
|
+
retries: 0,
|
|
563
|
+
waitTime: 0,
|
|
564
|
+
processingTime: 0,
|
|
565
|
+
totalTime: 0
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
const registered = this.resources.get(request.resource);
|
|
569
|
+
if (!registered) {
|
|
570
|
+
return Promise.resolve({
|
|
571
|
+
success: false,
|
|
572
|
+
error: new Error(`Resource not registered: ${request.resource}`),
|
|
573
|
+
retries: 0,
|
|
574
|
+
waitTime: 0,
|
|
575
|
+
processingTime: 0,
|
|
576
|
+
totalTime: 0
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
const fullRequest = {
|
|
580
|
+
...request,
|
|
581
|
+
id: randomUUID(),
|
|
582
|
+
createdAt: Date.now(),
|
|
583
|
+
timeout: request.timeout ?? registered.config.timeout ?? 6e4,
|
|
584
|
+
maxRetries: request.maxRetries ?? registered.config.maxRetries ?? DEFAULT_RETRY_CONFIG.maxRetries
|
|
585
|
+
};
|
|
586
|
+
return new Promise((resolve, reject) => {
|
|
587
|
+
const item = {
|
|
588
|
+
request: fullRequest,
|
|
589
|
+
resolve,
|
|
590
|
+
reject,
|
|
591
|
+
enqueuedAt: Date.now()
|
|
592
|
+
};
|
|
593
|
+
this.queue.enqueue(item);
|
|
594
|
+
registered.stats.totalRequests++;
|
|
595
|
+
this.processQueue();
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Process queue items continuously.
|
|
600
|
+
*/
|
|
601
|
+
async processQueue() {
|
|
602
|
+
if (this.processing) {
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
this.processing = true;
|
|
606
|
+
try {
|
|
607
|
+
while (!this.queue.isEmpty() && !this.shuttingDown) {
|
|
608
|
+
const item = this.queue.peek();
|
|
609
|
+
if (!item) {
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
const registered = this.resources.get(item.request.resource);
|
|
613
|
+
if (!registered) {
|
|
614
|
+
this.queue.dequeue();
|
|
615
|
+
item.reject(new Error(`Resource not registered: ${item.request.resource}`));
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
const tokens = item.request.estimatedTokens ?? 0;
|
|
619
|
+
const acquireResult = await this.rateLimitBackend.acquire(
|
|
620
|
+
item.request.resource,
|
|
621
|
+
tokens,
|
|
622
|
+
registered.rateLimits
|
|
623
|
+
);
|
|
624
|
+
if (!acquireResult.allowed) {
|
|
625
|
+
await sleep(acquireResult.waitTimeMs ?? 100);
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
this.queue.dequeue();
|
|
629
|
+
const currentActive = this.activeProcessing.get(item.request.resource) ?? 0;
|
|
630
|
+
this.activeProcessing.set(item.request.resource, currentActive + 1);
|
|
631
|
+
this.executeItem(item, registered).catch(() => {
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
} finally {
|
|
635
|
+
this.processing = false;
|
|
636
|
+
if (!this.queue.isEmpty() && !this.shuttingDown) {
|
|
637
|
+
setImmediate(() => this.processQueue());
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Execute a single queue item with retry logic.
|
|
643
|
+
*/
|
|
644
|
+
async executeItem(item, registered) {
|
|
645
|
+
const startTime = Date.now();
|
|
646
|
+
const waitTime = startTime - item.enqueuedAt;
|
|
647
|
+
let retries = 0;
|
|
648
|
+
let lastError;
|
|
649
|
+
const retryConfig = {
|
|
650
|
+
maxRetries: item.request.maxRetries ?? DEFAULT_RETRY_CONFIG.maxRetries,
|
|
651
|
+
baseDelay: registered.config.baseDelay ?? DEFAULT_RETRY_CONFIG.baseDelay,
|
|
652
|
+
maxDelay: registered.config.maxDelay ?? DEFAULT_RETRY_CONFIG.maxDelay,
|
|
653
|
+
jitter: DEFAULT_RETRY_CONFIG.jitter,
|
|
654
|
+
retryableErrors: DEFAULT_RETRY_CONFIG.retryableErrors
|
|
655
|
+
};
|
|
656
|
+
try {
|
|
657
|
+
for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {
|
|
658
|
+
try {
|
|
659
|
+
const timeoutMs = item.request.timeout ?? 6e4;
|
|
660
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
661
|
+
setTimeout(() => reject(new Error(`Request timeout after ${timeoutMs}ms`)), timeoutMs);
|
|
662
|
+
});
|
|
663
|
+
const executionPromise = registered.config.executor(
|
|
664
|
+
item.request.operation,
|
|
665
|
+
item.request.args
|
|
666
|
+
);
|
|
667
|
+
const result = await Promise.race([executionPromise, timeoutPromise]);
|
|
668
|
+
const endTime2 = Date.now();
|
|
669
|
+
const processingTime2 = endTime2 - startTime;
|
|
670
|
+
registered.stats.totalSuccess++;
|
|
671
|
+
registered.stats.totalWaitTime += waitTime;
|
|
672
|
+
registered.stats.totalProcessingTime += processingTime2;
|
|
673
|
+
item.resolve({
|
|
674
|
+
success: true,
|
|
675
|
+
data: result,
|
|
676
|
+
retries,
|
|
677
|
+
waitTime,
|
|
678
|
+
processingTime: processingTime2,
|
|
679
|
+
totalTime: endTime2 - item.enqueuedAt
|
|
680
|
+
});
|
|
681
|
+
return;
|
|
682
|
+
} catch (error) {
|
|
683
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
684
|
+
retries = attempt;
|
|
685
|
+
const decision = shouldRetry(error, attempt, retryConfig);
|
|
686
|
+
if (!decision.shouldRetry) {
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
await sleep(decision.delayMs);
|
|
690
|
+
const tokens = item.request.estimatedTokens ?? 0;
|
|
691
|
+
const acquireResult = await this.rateLimitBackend.acquire(
|
|
692
|
+
item.request.resource,
|
|
693
|
+
tokens,
|
|
694
|
+
registered.rateLimits
|
|
695
|
+
);
|
|
696
|
+
if (!acquireResult.allowed && acquireResult.waitTimeMs) {
|
|
697
|
+
await sleep(acquireResult.waitTimeMs);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
const endTime = Date.now();
|
|
702
|
+
const processingTime = endTime - startTime;
|
|
703
|
+
registered.stats.totalErrors++;
|
|
704
|
+
registered.stats.totalWaitTime += waitTime;
|
|
705
|
+
registered.stats.totalProcessingTime += processingTime;
|
|
706
|
+
item.resolve({
|
|
707
|
+
success: false,
|
|
708
|
+
error: lastError,
|
|
709
|
+
retries,
|
|
710
|
+
waitTime,
|
|
711
|
+
processingTime,
|
|
712
|
+
totalTime: endTime - item.enqueuedAt
|
|
713
|
+
});
|
|
714
|
+
} finally {
|
|
715
|
+
await this.rateLimitBackend.release(item.request.resource);
|
|
716
|
+
const currentActive = this.activeProcessing.get(item.request.resource) ?? 1;
|
|
717
|
+
this.activeProcessing.set(item.request.resource, Math.max(0, currentActive - 1));
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Get broker statistics.
|
|
722
|
+
*/
|
|
723
|
+
getStats() {
|
|
724
|
+
const resources = {};
|
|
725
|
+
let totalRequests = 0;
|
|
726
|
+
let totalSuccess = 0;
|
|
727
|
+
let totalErrors = 0;
|
|
728
|
+
for (const [resourceName, registered] of this.resources) {
|
|
729
|
+
const queueByPriority = this.queue.sizeByPriority();
|
|
730
|
+
const queueSize = this.queue.sizeByResource(resourceName);
|
|
731
|
+
const activeRequests = this.activeProcessing.get(resourceName) ?? 0;
|
|
732
|
+
const rateLimitStats = {
|
|
733
|
+
resource: resourceName,
|
|
734
|
+
tokensThisMinute: 0,
|
|
735
|
+
requestsThisMinute: 0,
|
|
736
|
+
requestsThisSecond: 0,
|
|
737
|
+
activeRequests,
|
|
738
|
+
totalRequests: registered.stats.totalRequests,
|
|
739
|
+
totalTokens: 0,
|
|
740
|
+
waitCount: 0,
|
|
741
|
+
totalWaitTime: registered.stats.totalWaitTime
|
|
742
|
+
};
|
|
743
|
+
const avgWaitTime = registered.stats.totalRequests > 0 ? registered.stats.totalWaitTime / registered.stats.totalRequests : 0;
|
|
744
|
+
const avgProcessingTime = registered.stats.totalRequests > 0 ? registered.stats.totalProcessingTime / registered.stats.totalRequests : 0;
|
|
745
|
+
resources[resourceName] = {
|
|
746
|
+
rateLimits: rateLimitStats,
|
|
747
|
+
queueSize,
|
|
748
|
+
queueByPriority,
|
|
749
|
+
totalRequests: registered.stats.totalRequests,
|
|
750
|
+
totalSuccess: registered.stats.totalSuccess,
|
|
751
|
+
totalErrors: registered.stats.totalErrors,
|
|
752
|
+
avgWaitTime,
|
|
753
|
+
avgProcessingTime
|
|
754
|
+
};
|
|
755
|
+
totalRequests += registered.stats.totalRequests;
|
|
756
|
+
totalSuccess += registered.stats.totalSuccess;
|
|
757
|
+
totalErrors += registered.stats.totalErrors;
|
|
758
|
+
}
|
|
759
|
+
return {
|
|
760
|
+
resources,
|
|
761
|
+
totalRequests,
|
|
762
|
+
totalSuccess,
|
|
763
|
+
totalErrors,
|
|
764
|
+
queueSize: this.queue.size(),
|
|
765
|
+
uptime: Date.now() - this.startTime
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Graceful shutdown - drain queues and stop processing.
|
|
770
|
+
*
|
|
771
|
+
* @param timeoutMs - Maximum time to wait for drain (default: 30000)
|
|
772
|
+
*/
|
|
773
|
+
async shutdown(timeoutMs = 3e4) {
|
|
774
|
+
this.shuttingDown = true;
|
|
775
|
+
const startTime = Date.now();
|
|
776
|
+
while (!this.queue.isEmpty() && Date.now() - startTime < timeoutMs) {
|
|
777
|
+
await sleep(100);
|
|
778
|
+
}
|
|
779
|
+
const remaining = this.queue.clear();
|
|
780
|
+
for (const item of remaining) {
|
|
781
|
+
item.resolve({
|
|
782
|
+
success: false,
|
|
783
|
+
error: new Error("ResourceBroker shutdown"),
|
|
784
|
+
retries: 0,
|
|
785
|
+
waitTime: Date.now() - item.enqueuedAt,
|
|
786
|
+
processingTime: 0,
|
|
787
|
+
totalTime: Date.now() - item.enqueuedAt
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Check if broker is shutting down.
|
|
793
|
+
*/
|
|
794
|
+
isShuttingDown() {
|
|
795
|
+
return this.shuttingDown;
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Get registered resource names.
|
|
799
|
+
*/
|
|
800
|
+
getRegisteredResources() {
|
|
801
|
+
return Array.from(this.resources.keys());
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Check if a resource is registered.
|
|
805
|
+
*/
|
|
806
|
+
hasResource(resource) {
|
|
807
|
+
return this.resources.has(resource);
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Unregister a resource.
|
|
811
|
+
* Note: Pending requests for this resource will fail.
|
|
812
|
+
*/
|
|
813
|
+
unregister(resource) {
|
|
814
|
+
this.resources.delete(resource);
|
|
815
|
+
this.activeProcessing.delete(resource);
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
// src/rate-limit/in-memory-backend.ts
|
|
820
|
+
var InMemoryRateLimitBackend = class {
|
|
821
|
+
states = /* @__PURE__ */ new Map();
|
|
822
|
+
/**
|
|
823
|
+
* Get or create state for a resource.
|
|
824
|
+
*/
|
|
825
|
+
getState(resource) {
|
|
826
|
+
let state = this.states.get(resource);
|
|
827
|
+
if (!state) {
|
|
828
|
+
const now = Date.now();
|
|
829
|
+
state = {
|
|
830
|
+
tokensThisMinute: 0,
|
|
831
|
+
requestsThisMinute: 0,
|
|
832
|
+
requestsThisSecond: 0,
|
|
833
|
+
activeRequests: 0,
|
|
834
|
+
minuteWindowStart: now,
|
|
835
|
+
secondWindowStart: now,
|
|
836
|
+
totalRequests: 0,
|
|
837
|
+
totalTokens: 0,
|
|
838
|
+
waitCount: 0,
|
|
839
|
+
totalWaitTime: 0
|
|
840
|
+
};
|
|
841
|
+
this.states.set(resource, state);
|
|
842
|
+
}
|
|
843
|
+
return state;
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Reset windows if time has passed.
|
|
847
|
+
*/
|
|
848
|
+
resetWindowsIfNeeded(state) {
|
|
849
|
+
const now = Date.now();
|
|
850
|
+
if (now - state.minuteWindowStart >= 6e4) {
|
|
851
|
+
state.tokensThisMinute = 0;
|
|
852
|
+
state.requestsThisMinute = 0;
|
|
853
|
+
state.minuteWindowStart = now;
|
|
854
|
+
}
|
|
855
|
+
if (now - state.secondWindowStart >= 1e3) {
|
|
856
|
+
state.requestsThisSecond = 0;
|
|
857
|
+
state.secondWindowStart = now;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Check if all limits allow proceeding.
|
|
862
|
+
*/
|
|
863
|
+
checkLimits(state, tokens, config) {
|
|
864
|
+
const now = Date.now();
|
|
865
|
+
const safetyMargin = config.safetyMargin ?? 0.9;
|
|
866
|
+
const effectiveTPM = config.tokensPerMinute ? Math.floor(config.tokensPerMinute * safetyMargin) : void 0;
|
|
867
|
+
const effectiveRPM = config.requestsPerMinute ? Math.floor(config.requestsPerMinute * safetyMargin) : void 0;
|
|
868
|
+
const effectiveRPS = config.requestsPerSecond ? Math.floor(config.requestsPerSecond * safetyMargin) : void 0;
|
|
869
|
+
const delays = [];
|
|
870
|
+
if (effectiveTPM !== void 0 && state.tokensThisMinute + tokens > effectiveTPM) {
|
|
871
|
+
const timeUntilMinuteReset = 6e4 - (now - state.minuteWindowStart);
|
|
872
|
+
delays.push(Math.max(100, timeUntilMinuteReset + 100));
|
|
873
|
+
}
|
|
874
|
+
if (effectiveRPM !== void 0 && state.requestsThisMinute >= effectiveRPM) {
|
|
875
|
+
const timeUntilMinuteReset = 6e4 - (now - state.minuteWindowStart);
|
|
876
|
+
delays.push(Math.max(100, timeUntilMinuteReset + 100));
|
|
877
|
+
}
|
|
878
|
+
if (effectiveRPS !== void 0 && state.requestsThisSecond >= effectiveRPS) {
|
|
879
|
+
const timeUntilSecondReset = 1e3 - (now - state.secondWindowStart);
|
|
880
|
+
delays.push(Math.max(50, timeUntilSecondReset + 50));
|
|
881
|
+
}
|
|
882
|
+
if (config.maxConcurrentRequests !== void 0 && state.activeRequests >= config.maxConcurrentRequests) {
|
|
883
|
+
delays.push(100);
|
|
884
|
+
}
|
|
885
|
+
if (delays.length > 0) {
|
|
886
|
+
return { allowed: false, waitTimeMs: Math.min(...delays) };
|
|
887
|
+
}
|
|
888
|
+
return { allowed: true };
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* @inheritdoc
|
|
892
|
+
*/
|
|
893
|
+
async acquire(resource, tokens, config) {
|
|
894
|
+
const state = this.getState(resource);
|
|
895
|
+
this.resetWindowsIfNeeded(state);
|
|
896
|
+
const check = this.checkLimits(state, tokens, config);
|
|
897
|
+
if (!check.allowed) {
|
|
898
|
+
state.waitCount++;
|
|
899
|
+
state.totalWaitTime += check.waitTimeMs ?? 0;
|
|
900
|
+
const safetyMargin2 = config.safetyMargin ?? 0.9;
|
|
901
|
+
const effectiveTPM2 = config.tokensPerMinute ? Math.floor(config.tokensPerMinute * safetyMargin2) : void 0;
|
|
902
|
+
const effectiveRPM2 = config.requestsPerMinute ? Math.floor(config.requestsPerMinute * safetyMargin2) : void 0;
|
|
903
|
+
return {
|
|
904
|
+
allowed: false,
|
|
905
|
+
waitTimeMs: check.waitTimeMs,
|
|
906
|
+
tokensRemaining: effectiveTPM2 ? Math.max(0, effectiveTPM2 - state.tokensThisMinute) : void 0,
|
|
907
|
+
requestsRemaining: effectiveRPM2 ? Math.max(0, effectiveRPM2 - state.requestsThisMinute) : void 0,
|
|
908
|
+
activeRequests: state.activeRequests
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
state.tokensThisMinute += tokens;
|
|
912
|
+
state.requestsThisMinute++;
|
|
913
|
+
state.requestsThisSecond++;
|
|
914
|
+
state.activeRequests++;
|
|
915
|
+
state.totalRequests++;
|
|
916
|
+
state.totalTokens += tokens;
|
|
917
|
+
const safetyMargin = config.safetyMargin ?? 0.9;
|
|
918
|
+
const effectiveTPM = config.tokensPerMinute ? Math.floor(config.tokensPerMinute * safetyMargin) : void 0;
|
|
919
|
+
const effectiveRPM = config.requestsPerMinute ? Math.floor(config.requestsPerMinute * safetyMargin) : void 0;
|
|
920
|
+
return {
|
|
921
|
+
allowed: true,
|
|
922
|
+
tokensRemaining: effectiveTPM ? Math.max(0, effectiveTPM - state.tokensThisMinute) : void 0,
|
|
923
|
+
requestsRemaining: effectiveRPM ? Math.max(0, effectiveRPM - state.requestsThisMinute) : void 0,
|
|
924
|
+
activeRequests: state.activeRequests
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* @inheritdoc
|
|
929
|
+
*/
|
|
930
|
+
async release(resource) {
|
|
931
|
+
const state = this.states.get(resource);
|
|
932
|
+
if (state) {
|
|
933
|
+
state.activeRequests = Math.max(0, state.activeRequests - 1);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* @inheritdoc
|
|
938
|
+
*/
|
|
939
|
+
async getStats(resource) {
|
|
940
|
+
const state = this.getState(resource);
|
|
941
|
+
this.resetWindowsIfNeeded(state);
|
|
942
|
+
return {
|
|
943
|
+
resource,
|
|
944
|
+
tokensThisMinute: state.tokensThisMinute,
|
|
945
|
+
requestsThisMinute: state.requestsThisMinute,
|
|
946
|
+
requestsThisSecond: state.requestsThisSecond,
|
|
947
|
+
activeRequests: state.activeRequests,
|
|
948
|
+
totalRequests: state.totalRequests,
|
|
949
|
+
totalTokens: state.totalTokens,
|
|
950
|
+
waitCount: state.waitCount,
|
|
951
|
+
totalWaitTime: state.totalWaitTime
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* @inheritdoc
|
|
956
|
+
*/
|
|
957
|
+
async reset(resource) {
|
|
958
|
+
this.states.delete(resource);
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Reset all resources.
|
|
962
|
+
*/
|
|
963
|
+
resetAll() {
|
|
964
|
+
this.states.clear();
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
|
|
968
|
+
// src/rate-limit/state-broker-backend.ts
|
|
969
|
+
var StateBrokerRateLimitBackend = class {
|
|
970
|
+
constructor(broker) {
|
|
971
|
+
this.broker = broker;
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Get current minute window key.
|
|
975
|
+
*/
|
|
976
|
+
getMinuteKey(resource) {
|
|
977
|
+
const window = (/* @__PURE__ */ new Date()).toISOString().slice(0, 16);
|
|
978
|
+
return `ratelimit:${resource}:minute:${window}`;
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Get current second window key.
|
|
982
|
+
*/
|
|
983
|
+
getSecondKey(resource) {
|
|
984
|
+
const window = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19);
|
|
985
|
+
return `ratelimit:${resource}:second:${window}`;
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Get active requests key.
|
|
989
|
+
*/
|
|
990
|
+
getActiveKey(resource) {
|
|
991
|
+
return `ratelimit:${resource}:active`;
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Get stats key.
|
|
995
|
+
*/
|
|
996
|
+
getStatsKey(resource) {
|
|
997
|
+
return `ratelimit:${resource}:stats`;
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Get or initialize window state.
|
|
1001
|
+
*/
|
|
1002
|
+
async getWindowState(key) {
|
|
1003
|
+
const state = await this.broker.get(key);
|
|
1004
|
+
return state ?? { tokens: 0, requests: 0, activeRequests: 0, updatedAt: Date.now() };
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Get or initialize stats.
|
|
1008
|
+
*/
|
|
1009
|
+
async getCumulativeStats(resource) {
|
|
1010
|
+
const stats = await this.broker.get(this.getStatsKey(resource));
|
|
1011
|
+
return stats ?? { totalRequests: 0, totalTokens: 0, waitCount: 0, totalWaitTime: 0 };
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* @inheritdoc
|
|
1015
|
+
*/
|
|
1016
|
+
async acquire(resource, tokens, config) {
|
|
1017
|
+
const safetyMargin = config.safetyMargin ?? 0.9;
|
|
1018
|
+
const [minuteState, secondState, activeCount] = await Promise.all([
|
|
1019
|
+
this.getWindowState(this.getMinuteKey(resource)),
|
|
1020
|
+
this.getWindowState(this.getSecondKey(resource)),
|
|
1021
|
+
this.broker.get(this.getActiveKey(resource)).then((v) => v ?? 0)
|
|
1022
|
+
]);
|
|
1023
|
+
const effectiveTPM = config.tokensPerMinute ? Math.floor(config.tokensPerMinute * safetyMargin) : void 0;
|
|
1024
|
+
const effectiveRPM = config.requestsPerMinute ? Math.floor(config.requestsPerMinute * safetyMargin) : void 0;
|
|
1025
|
+
const effectiveRPS = config.requestsPerSecond ? Math.floor(config.requestsPerSecond * safetyMargin) : void 0;
|
|
1026
|
+
const delays = [];
|
|
1027
|
+
const now = Date.now();
|
|
1028
|
+
if (effectiveTPM && minuteState.tokens + tokens > effectiveTPM) {
|
|
1029
|
+
const secondsRemaining = 60 - (/* @__PURE__ */ new Date()).getSeconds();
|
|
1030
|
+
delays.push(secondsRemaining * 1e3 + 100);
|
|
1031
|
+
}
|
|
1032
|
+
if (effectiveRPM && minuteState.requests >= effectiveRPM) {
|
|
1033
|
+
const secondsRemaining = 60 - (/* @__PURE__ */ new Date()).getSeconds();
|
|
1034
|
+
delays.push(secondsRemaining * 1e3 + 100);
|
|
1035
|
+
}
|
|
1036
|
+
if (effectiveRPS && secondState.requests >= effectiveRPS) {
|
|
1037
|
+
const msRemaining = 1e3 - (/* @__PURE__ */ new Date()).getMilliseconds();
|
|
1038
|
+
delays.push(msRemaining + 50);
|
|
1039
|
+
}
|
|
1040
|
+
if (config.maxConcurrentRequests && activeCount >= config.maxConcurrentRequests) {
|
|
1041
|
+
delays.push(100);
|
|
1042
|
+
}
|
|
1043
|
+
if (delays.length > 0) {
|
|
1044
|
+
const stats2 = await this.getCumulativeStats(resource);
|
|
1045
|
+
stats2.waitCount++;
|
|
1046
|
+
stats2.totalWaitTime += Math.min(...delays);
|
|
1047
|
+
await this.broker.set(this.getStatsKey(resource), stats2);
|
|
1048
|
+
return {
|
|
1049
|
+
allowed: false,
|
|
1050
|
+
waitTimeMs: Math.min(...delays),
|
|
1051
|
+
tokensRemaining: effectiveTPM ? Math.max(0, effectiveTPM - minuteState.tokens) : void 0,
|
|
1052
|
+
requestsRemaining: effectiveRPM ? Math.max(0, effectiveRPM - minuteState.requests) : void 0,
|
|
1053
|
+
activeRequests: activeCount
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
const minuteKey = this.getMinuteKey(resource);
|
|
1057
|
+
const secondKey = this.getSecondKey(resource);
|
|
1058
|
+
const activeKey = this.getActiveKey(resource);
|
|
1059
|
+
const statsKey = this.getStatsKey(resource);
|
|
1060
|
+
const newMinuteState = {
|
|
1061
|
+
tokens: minuteState.tokens + tokens,
|
|
1062
|
+
requests: minuteState.requests + 1,
|
|
1063
|
+
activeRequests: activeCount + 1,
|
|
1064
|
+
updatedAt: now
|
|
1065
|
+
};
|
|
1066
|
+
await this.broker.set(minuteKey, newMinuteState, 12e4);
|
|
1067
|
+
const newSecondState = {
|
|
1068
|
+
tokens: secondState.tokens + tokens,
|
|
1069
|
+
requests: secondState.requests + 1,
|
|
1070
|
+
activeRequests: activeCount + 1,
|
|
1071
|
+
updatedAt: now
|
|
1072
|
+
};
|
|
1073
|
+
await this.broker.set(secondKey, newSecondState, 1e4);
|
|
1074
|
+
await this.broker.set(activeKey, activeCount + 1);
|
|
1075
|
+
const stats = await this.getCumulativeStats(resource);
|
|
1076
|
+
stats.totalRequests++;
|
|
1077
|
+
stats.totalTokens += tokens;
|
|
1078
|
+
await this.broker.set(statsKey, stats);
|
|
1079
|
+
return {
|
|
1080
|
+
allowed: true,
|
|
1081
|
+
tokensRemaining: effectiveTPM ? Math.max(0, effectiveTPM - newMinuteState.tokens) : void 0,
|
|
1082
|
+
requestsRemaining: effectiveRPM ? Math.max(0, effectiveRPM - newMinuteState.requests) : void 0,
|
|
1083
|
+
activeRequests: activeCount + 1
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* @inheritdoc
|
|
1088
|
+
*/
|
|
1089
|
+
async release(resource) {
|
|
1090
|
+
const activeKey = this.getActiveKey(resource);
|
|
1091
|
+
const current = await this.broker.get(activeKey);
|
|
1092
|
+
if (current !== null && current > 0) {
|
|
1093
|
+
await this.broker.set(activeKey, current - 1);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* @inheritdoc
|
|
1098
|
+
*/
|
|
1099
|
+
async getStats(resource) {
|
|
1100
|
+
const [minuteState, secondState, activeCount, cumulativeStats] = await Promise.all([
|
|
1101
|
+
this.getWindowState(this.getMinuteKey(resource)),
|
|
1102
|
+
this.getWindowState(this.getSecondKey(resource)),
|
|
1103
|
+
this.broker.get(this.getActiveKey(resource)).then((v) => v ?? 0),
|
|
1104
|
+
this.getCumulativeStats(resource)
|
|
1105
|
+
]);
|
|
1106
|
+
return {
|
|
1107
|
+
resource,
|
|
1108
|
+
tokensThisMinute: minuteState.tokens,
|
|
1109
|
+
requestsThisMinute: minuteState.requests,
|
|
1110
|
+
requestsThisSecond: secondState.requests,
|
|
1111
|
+
activeRequests: activeCount,
|
|
1112
|
+
totalRequests: cumulativeStats.totalRequests,
|
|
1113
|
+
totalTokens: cumulativeStats.totalTokens,
|
|
1114
|
+
waitCount: cumulativeStats.waitCount,
|
|
1115
|
+
totalWaitTime: cumulativeStats.totalWaitTime
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* @inheritdoc
|
|
1120
|
+
*/
|
|
1121
|
+
async reset(resource) {
|
|
1122
|
+
await Promise.all([
|
|
1123
|
+
this.broker.clear(`ratelimit:${resource}:*`)
|
|
1124
|
+
]);
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1128
|
+
// src/wrappers/queued-llm.ts
|
|
1129
|
+
var QueuedLLM = class {
|
|
1130
|
+
constructor(broker, realLLM) {
|
|
1131
|
+
this.broker = broker;
|
|
1132
|
+
this.realLLM = realLLM;
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Generate a completion through the queue.
|
|
1136
|
+
*
|
|
1137
|
+
* @param prompt - Text prompt
|
|
1138
|
+
* @param options - Optional generation options with priority
|
|
1139
|
+
* @returns LLM response
|
|
1140
|
+
* @throws Error if request fails after all retries
|
|
1141
|
+
*/
|
|
1142
|
+
async complete(prompt, options) {
|
|
1143
|
+
const priority = options?.priority ?? "normal";
|
|
1144
|
+
const estimatedTokens = estimateTokens(prompt);
|
|
1145
|
+
const resource = options?.metadata?.resource ?? "llm";
|
|
1146
|
+
const response = await this.broker.enqueue({
|
|
1147
|
+
resource,
|
|
1148
|
+
operation: "complete",
|
|
1149
|
+
args: [prompt, options],
|
|
1150
|
+
priority,
|
|
1151
|
+
estimatedTokens
|
|
1152
|
+
});
|
|
1153
|
+
if (!response.success) {
|
|
1154
|
+
throw response.error ?? new Error("LLM request failed");
|
|
1155
|
+
}
|
|
1156
|
+
return response.data;
|
|
1157
|
+
}
|
|
1158
|
+
async getProtocolCapabilities() {
|
|
1159
|
+
if (!this.realLLM.getProtocolCapabilities) {
|
|
1160
|
+
return {
|
|
1161
|
+
cache: { supported: false },
|
|
1162
|
+
stream: { supported: true }
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
return this.realLLM.getProtocolCapabilities();
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Stream a completion (bypasses queue for real-time UX).
|
|
1169
|
+
*
|
|
1170
|
+
* Streaming is passed through directly to the underlying LLM
|
|
1171
|
+
* because:
|
|
1172
|
+
* 1. Real-time user experience requires immediate response
|
|
1173
|
+
* 2. Token counting happens after streaming completes
|
|
1174
|
+
* 3. Rate limits are still enforced by the underlying adapter
|
|
1175
|
+
*
|
|
1176
|
+
* @param prompt - Text prompt
|
|
1177
|
+
* @param options - Optional generation options
|
|
1178
|
+
* @returns Async iterable of text chunks
|
|
1179
|
+
*/
|
|
1180
|
+
stream(prompt, options) {
|
|
1181
|
+
return this.realLLM.stream(prompt, options);
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Chat with native tool calling support (proxies to underlying LLM).
|
|
1185
|
+
* Currently NOT queued - passes through directly for simplicity.
|
|
1186
|
+
* TODO: Add queueing support when needed.
|
|
1187
|
+
*/
|
|
1188
|
+
async chatWithTools(messages, options) {
|
|
1189
|
+
if (!this.realLLM.chatWithTools) {
|
|
1190
|
+
throw new Error("Underlying LLM does not support chatWithTools");
|
|
1191
|
+
}
|
|
1192
|
+
return this.realLLM.chatWithTools(messages, options);
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1195
|
+
function createQueuedLLM(broker, llm) {
|
|
1196
|
+
return new QueuedLLM(broker, llm);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// src/wrappers/queued-embeddings.ts
|
|
1200
|
+
var QueuedEmbeddings = class {
|
|
1201
|
+
constructor(broker, realEmbeddings) {
|
|
1202
|
+
this.broker = broker;
|
|
1203
|
+
this.realEmbeddings = realEmbeddings;
|
|
1204
|
+
}
|
|
1205
|
+
_priority = "normal";
|
|
1206
|
+
/**
|
|
1207
|
+
* Get embedding dimensions from the underlying implementation.
|
|
1208
|
+
*/
|
|
1209
|
+
get dimensions() {
|
|
1210
|
+
return this.realEmbeddings.dimensions;
|
|
1211
|
+
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Set default priority for subsequent operations.
|
|
1214
|
+
*
|
|
1215
|
+
* @param priority - Priority level
|
|
1216
|
+
* @returns this for chaining
|
|
1217
|
+
*/
|
|
1218
|
+
withPriority(priority) {
|
|
1219
|
+
this._priority = priority;
|
|
1220
|
+
return this;
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Generate embedding vector for a single text through the queue.
|
|
1224
|
+
*
|
|
1225
|
+
* @param text - Input text
|
|
1226
|
+
* @returns Embedding vector
|
|
1227
|
+
* @throws Error if request fails after all retries
|
|
1228
|
+
*/
|
|
1229
|
+
async embed(text) {
|
|
1230
|
+
const estimatedTokens = estimateTokens(text);
|
|
1231
|
+
const response = await this.broker.enqueue({
|
|
1232
|
+
resource: "embeddings",
|
|
1233
|
+
operation: "embed",
|
|
1234
|
+
args: [text],
|
|
1235
|
+
priority: this._priority,
|
|
1236
|
+
estimatedTokens
|
|
1237
|
+
});
|
|
1238
|
+
if (!response.success) {
|
|
1239
|
+
throw response.error ?? new Error("Embeddings request failed");
|
|
1240
|
+
}
|
|
1241
|
+
return response.data;
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Generate embedding vectors for multiple texts through the queue.
|
|
1245
|
+
*
|
|
1246
|
+
* @param texts - Array of input texts
|
|
1247
|
+
* @returns Array of embedding vectors
|
|
1248
|
+
* @throws Error if request fails after all retries
|
|
1249
|
+
*/
|
|
1250
|
+
async embedBatch(texts) {
|
|
1251
|
+
const estimatedTokens = estimateBatchTokens(texts);
|
|
1252
|
+
const response = await this.broker.enqueue({
|
|
1253
|
+
resource: "embeddings",
|
|
1254
|
+
operation: "embedBatch",
|
|
1255
|
+
args: [texts],
|
|
1256
|
+
priority: this._priority,
|
|
1257
|
+
estimatedTokens
|
|
1258
|
+
});
|
|
1259
|
+
if (!response.success) {
|
|
1260
|
+
throw response.error ?? new Error("Embeddings batch request failed");
|
|
1261
|
+
}
|
|
1262
|
+
return response.data;
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Get the dimensions of the embeddings.
|
|
1266
|
+
* This method is needed for IPC/Unix Socket transport to access the dimensions property.
|
|
1267
|
+
*/
|
|
1268
|
+
async getDimensions() {
|
|
1269
|
+
return this.realEmbeddings.dimensions;
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
function createQueuedEmbeddings(broker, embeddings) {
|
|
1273
|
+
return new QueuedEmbeddings(broker, embeddings);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
// src/wrappers/queued-vector-store.ts
|
|
1277
|
+
var QueuedVectorStore = class {
|
|
1278
|
+
constructor(broker, realVectorStore) {
|
|
1279
|
+
this.broker = broker;
|
|
1280
|
+
this.realVectorStore = realVectorStore;
|
|
1281
|
+
}
|
|
1282
|
+
_priority = "normal";
|
|
1283
|
+
/**
|
|
1284
|
+
* Set default priority for subsequent operations.
|
|
1285
|
+
*
|
|
1286
|
+
* @param priority - Priority level
|
|
1287
|
+
* @returns this for chaining
|
|
1288
|
+
*/
|
|
1289
|
+
withPriority(priority) {
|
|
1290
|
+
this._priority = priority;
|
|
1291
|
+
return this;
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Search for similar vectors through the queue.
|
|
1295
|
+
*
|
|
1296
|
+
* @param query - Query embedding vector
|
|
1297
|
+
* @param limit - Maximum number of results
|
|
1298
|
+
* @param filter - Optional metadata filter
|
|
1299
|
+
* @returns Search results
|
|
1300
|
+
* @throws Error if request fails after all retries
|
|
1301
|
+
*/
|
|
1302
|
+
async search(query, limit, filter) {
|
|
1303
|
+
const response = await this.broker.enqueue({
|
|
1304
|
+
resource: "vectorStore",
|
|
1305
|
+
operation: "search",
|
|
1306
|
+
args: [query, limit, filter],
|
|
1307
|
+
priority: this._priority
|
|
1308
|
+
// No token estimation for vector operations
|
|
1309
|
+
});
|
|
1310
|
+
if (!response.success) {
|
|
1311
|
+
throw response.error ?? new Error("VectorStore search failed");
|
|
1312
|
+
}
|
|
1313
|
+
return response.data;
|
|
1314
|
+
}
|
|
1315
|
+
/**
|
|
1316
|
+
* Upsert vectors through the queue.
|
|
1317
|
+
*
|
|
1318
|
+
* @param vectors - Array of vector records to upsert
|
|
1319
|
+
* @throws Error if request fails after all retries
|
|
1320
|
+
*/
|
|
1321
|
+
async upsert(vectors) {
|
|
1322
|
+
const response = await this.broker.enqueue({
|
|
1323
|
+
resource: "vectorStore",
|
|
1324
|
+
operation: "upsert",
|
|
1325
|
+
args: [vectors],
|
|
1326
|
+
priority: this._priority
|
|
1327
|
+
});
|
|
1328
|
+
if (!response.success) {
|
|
1329
|
+
throw response.error ?? new Error("VectorStore upsert failed");
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Delete vectors through the queue.
|
|
1334
|
+
*
|
|
1335
|
+
* @param ids - Array of vector IDs to delete
|
|
1336
|
+
* @throws Error if request fails after all retries
|
|
1337
|
+
*/
|
|
1338
|
+
async delete(ids) {
|
|
1339
|
+
const response = await this.broker.enqueue({
|
|
1340
|
+
resource: "vectorStore",
|
|
1341
|
+
operation: "delete",
|
|
1342
|
+
args: [ids],
|
|
1343
|
+
priority: this._priority
|
|
1344
|
+
});
|
|
1345
|
+
if (!response.success) {
|
|
1346
|
+
throw response.error ?? new Error("VectorStore delete failed");
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Get total count of vectors through the queue.
|
|
1351
|
+
*
|
|
1352
|
+
* @returns Vector count
|
|
1353
|
+
* @throws Error if request fails after all retries
|
|
1354
|
+
*/
|
|
1355
|
+
async count() {
|
|
1356
|
+
const response = await this.broker.enqueue({
|
|
1357
|
+
resource: "vectorStore",
|
|
1358
|
+
operation: "count",
|
|
1359
|
+
args: [],
|
|
1360
|
+
priority: this._priority
|
|
1361
|
+
});
|
|
1362
|
+
if (!response.success) {
|
|
1363
|
+
throw response.error ?? new Error("VectorStore count failed");
|
|
1364
|
+
}
|
|
1365
|
+
return response.data;
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Get vectors by IDs through the queue (if supported).
|
|
1369
|
+
*
|
|
1370
|
+
* @param ids - Array of vector IDs to retrieve
|
|
1371
|
+
* @returns Array of vector records
|
|
1372
|
+
* @throws Error if request fails or method not supported
|
|
1373
|
+
*/
|
|
1374
|
+
async get(ids) {
|
|
1375
|
+
if (!this.realVectorStore.get) {
|
|
1376
|
+
throw new Error("VectorStore.get() not supported by underlying implementation");
|
|
1377
|
+
}
|
|
1378
|
+
const response = await this.broker.enqueue({
|
|
1379
|
+
resource: "vectorStore",
|
|
1380
|
+
operation: "get",
|
|
1381
|
+
args: [ids],
|
|
1382
|
+
priority: this._priority
|
|
1383
|
+
});
|
|
1384
|
+
if (!response.success) {
|
|
1385
|
+
throw response.error ?? new Error("VectorStore get failed");
|
|
1386
|
+
}
|
|
1387
|
+
return response.data;
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Query vectors by filter through the queue (if supported).
|
|
1391
|
+
*
|
|
1392
|
+
* @param filter - Metadata filter to apply
|
|
1393
|
+
* @returns Array of matching vector records
|
|
1394
|
+
* @throws Error if request fails or method not supported
|
|
1395
|
+
*/
|
|
1396
|
+
async query(filter) {
|
|
1397
|
+
if (!this.realVectorStore.query) {
|
|
1398
|
+
throw new Error("VectorStore.query() not supported by underlying implementation");
|
|
1399
|
+
}
|
|
1400
|
+
const response = await this.broker.enqueue({
|
|
1401
|
+
resource: "vectorStore",
|
|
1402
|
+
operation: "query",
|
|
1403
|
+
args: [filter],
|
|
1404
|
+
priority: this._priority
|
|
1405
|
+
});
|
|
1406
|
+
if (!response.success) {
|
|
1407
|
+
throw response.error ?? new Error("VectorStore query failed");
|
|
1408
|
+
}
|
|
1409
|
+
return response.data;
|
|
1410
|
+
}
|
|
1411
|
+
};
|
|
1412
|
+
function createQueuedVectorStore(broker, vectorStore) {
|
|
1413
|
+
return new QueuedVectorStore(broker, vectorStore);
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
export { DEFAULT_RATE_LIMIT_CONFIG, DEFAULT_RETRY_CONFIG, InMemoryRateLimitBackend, PriorityQueue, QueuedEmbeddings, QueuedLLM, QueuedVectorStore, RATE_LIMIT_PRESETS, ResourceBroker, StateBrokerRateLimitBackend, calculateBackoffDelay, classifyError, createQueuedEmbeddings, createQueuedLLM, createQueuedVectorStore, createQuickRetryConfig, createRateLimitRetryConfig, estimateBatchTokens, estimateTokens, extractRetryAfter, getRateLimitConfig, isRateLimitError, isRetryableError, shouldRetry, sleep, withRetry };
|
|
1417
|
+
//# sourceMappingURL=index.js.map
|
|
1418
|
+
//# sourceMappingURL=index.js.map
|