adaptive-memory-multi-model-router 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +114 -0
  2. package/demo/research-demo.js +266 -0
  3. package/dist/cache/prefixCache.d.ts +114 -0
  4. package/dist/cache/prefixCache.d.ts.map +1 -0
  5. package/dist/cache/prefixCache.js +285 -0
  6. package/dist/cache/prefixCache.js.map +1 -0
  7. package/dist/cache/responseCache.d.ts +58 -0
  8. package/dist/cache/responseCache.d.ts.map +1 -0
  9. package/dist/cache/responseCache.js +153 -0
  10. package/dist/cache/responseCache.js.map +1 -0
  11. package/dist/cli.js +59 -0
  12. package/dist/cost/costTracker.d.ts +95 -0
  13. package/dist/cost/costTracker.d.ts.map +1 -0
  14. package/dist/cost/costTracker.js +240 -0
  15. package/dist/cost/costTracker.js.map +1 -0
  16. package/dist/index.d.ts +723 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +239 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/memory/episodicMemory.d.ts +82 -0
  21. package/dist/memory/episodicMemory.d.ts.map +1 -0
  22. package/dist/memory/episodicMemory.js +145 -0
  23. package/dist/memory/episodicMemory.js.map +1 -0
  24. package/dist/orchestration/haloOrchestrator.d.ts +102 -0
  25. package/dist/orchestration/haloOrchestrator.d.ts.map +1 -0
  26. package/dist/orchestration/haloOrchestrator.js +207 -0
  27. package/dist/orchestration/haloOrchestrator.js.map +1 -0
  28. package/dist/orchestration/mctsWorkflow.d.ts +85 -0
  29. package/dist/orchestration/mctsWorkflow.d.ts.map +1 -0
  30. package/dist/orchestration/mctsWorkflow.js +210 -0
  31. package/dist/orchestration/mctsWorkflow.js.map +1 -0
  32. package/dist/providers/localProvider.d.ts +102 -0
  33. package/dist/providers/localProvider.d.ts.map +1 -0
  34. package/dist/providers/localProvider.js +338 -0
  35. package/dist/providers/localProvider.js.map +1 -0
  36. package/dist/providers/registry.d.ts +55 -0
  37. package/dist/providers/registry.d.ts.map +1 -0
  38. package/dist/providers/registry.js +138 -0
  39. package/dist/providers/registry.js.map +1 -0
  40. package/dist/routing/advancedRouter.d.ts +68 -0
  41. package/dist/routing/advancedRouter.d.ts.map +1 -0
  42. package/dist/routing/advancedRouter.js +332 -0
  43. package/dist/routing/advancedRouter.js.map +1 -0
  44. package/dist/tools/tmlpdTools.d.ts +101 -0
  45. package/dist/tools/tmlpdTools.d.ts.map +1 -0
  46. package/dist/tools/tmlpdTools.js +368 -0
  47. package/dist/tools/tmlpdTools.js.map +1 -0
  48. package/dist/utils/batchProcessor.d.ts +96 -0
  49. package/dist/utils/batchProcessor.d.ts.map +1 -0
  50. package/dist/utils/batchProcessor.js +170 -0
  51. package/dist/utils/batchProcessor.js.map +1 -0
  52. package/dist/utils/compression.d.ts +61 -0
  53. package/dist/utils/compression.d.ts.map +1 -0
  54. package/dist/utils/compression.js +281 -0
  55. package/dist/utils/compression.js.map +1 -0
  56. package/dist/utils/reliability.d.ts +74 -0
  57. package/dist/utils/reliability.d.ts.map +1 -0
  58. package/dist/utils/reliability.js +177 -0
  59. package/dist/utils/reliability.js.map +1 -0
  60. package/dist/utils/speculativeDecoding.d.ts +117 -0
  61. package/dist/utils/speculativeDecoding.d.ts.map +1 -0
  62. package/dist/utils/speculativeDecoding.js +246 -0
  63. package/dist/utils/speculativeDecoding.js.map +1 -0
  64. package/dist/utils/tokenUtils.d.ts +50 -0
  65. package/dist/utils/tokenUtils.d.ts.map +1 -0
  66. package/dist/utils/tokenUtils.js +124 -0
  67. package/dist/utils/tokenUtils.js.map +1 -0
  68. package/examples/QUICKSTART.md +183 -0
  69. package/notebooks/quickstart.ipynb +157 -0
  70. package/package.json +83 -0
  71. package/python/examples.py +53 -0
  72. package/python/integrations.py +330 -0
  73. package/python/setup.py +28 -0
  74. package/python/tmlpd.py +369 -0
  75. package/qna/REDDIT_GAP_ANALYSIS.md +299 -0
  76. package/qna/TMLPD_QNA.md +751 -0
  77. package/rust/tmlpd.h +268 -0
  78. package/skill/SKILL.md +238 -0
  79. package/src/cache/prefixCache.ts +365 -0
  80. package/src/cache/responseCache.ts +147 -0
  81. package/src/cost/costTracker.ts +302 -0
  82. package/src/index.ts +224 -0
  83. package/src/memory/episodicMemory.ts +185 -0
  84. package/src/orchestration/haloOrchestrator.ts +266 -0
  85. package/src/orchestration/mctsWorkflow.ts +262 -0
  86. package/src/providers/localProvider.ts +406 -0
  87. package/src/providers/registry.ts +164 -0
  88. package/src/routing/advancedRouter.ts +406 -0
  89. package/src/tools/tmlpdTools.ts +433 -0
  90. package/src/utils/batchProcessor.ts +232 -0
  91. package/src/utils/compression.ts +325 -0
  92. package/src/utils/reliability.ts +221 -0
  93. package/src/utils/speculativeDecoding.ts +344 -0
  94. package/src/utils/tokenUtils.ts +145 -0
  95. package/tsconfig.json +18 -0
@@ -0,0 +1,433 @@
1
+ /**
2
+ * TMLPD PI Tools
3
+ *
4
+ * Main tools exposed to the PI agent via the MCP bridge.
5
+ * Features: streaming, caching, cost tracking, reliability.
6
+ */
7
+
8
+ import { ResponseCache, CacheConfig } from "../cache/responseCache";
9
+ import { CostTracker, BudgetConfig, CostSummary } from "../cost/costTracker";
10
+ import { ProviderRegistry } from "../providers/registry";
11
+ import { CircuitBreaker, withRetry, RetryConfig, DEFAULT_RETRY_CONFIG } from "../utils/reliability";
12
+ import * as https from "https";
13
+ import * as http from "http";
14
+
15
+ export interface TMLPDConfig {
16
+ cache?: Partial<CacheConfig>;
17
+ budget?: BudgetConfig;
18
+ retry?: Partial<RetryConfig>;
19
+ maxConcurrent?: number;
20
+ }
21
+
22
+ export interface ExecuteResult {
23
+ success: boolean;
24
+ content?: string;
25
+ error?: string;
26
+ model: string;
27
+ provider: string;
28
+ tokens?: number;
29
+ cost?: number;
30
+ cached?: boolean;
31
+ duration_ms?: number;
32
+ attempts?: number;
33
+ }
34
+
35
+ export interface ParallelResult {
36
+ responses: ExecuteResult[];
37
+ total_models: number;
38
+ successful_models: number;
39
+ total_cost: number;
40
+ duration_ms: number;
41
+ }
42
+
43
+ export interface StreamingConfig {
44
+ enabled: boolean;
45
+ chunk_size?: number;
46
+ on_chunk?: (chunk: string) => void;
47
+ }
48
+
49
+ export class TMLPDTools {
50
+ private cache: ResponseCache;
51
+ private costTracker: CostTracker;
52
+ private registry: ProviderRegistry;
53
+ private circuitBreakers: Map<string, CircuitBreaker> = new Map();
54
+ private retryConfig: RetryConfig;
55
+ private maxConcurrent: number;
56
+
57
+ constructor(config: TMLPDConfig = {}) {
58
+ this.cache = new ResponseCache(config.cache);
59
+ this.costTracker = new CostTracker(config.budget);
60
+ this.registry = new ProviderRegistry();
61
+ this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...config.retry };
62
+ this.maxConcurrent = config.maxConcurrent || 5;
63
+ }
64
+
65
+ /**
66
+ * Get cost summary
67
+ */
68
+ getCostSummary(): CostSummary {
69
+ return this.costTracker.getSummary();
70
+ }
71
+
72
+ /**
73
+ * Get remaining budget
74
+ */
75
+ getRemainingBudget(): { daily: number | null; monthly: number | null; per_model: Record<string, number> } {
76
+ return this.costTracker.getRemainingBudget();
77
+ }
78
+
79
+ /**
80
+ * Get cache stats
81
+ */
82
+ getCacheStats(): { hits: number; misses: number; size: number; hit_rate: number } {
83
+ return this.cache.getStats();
84
+ }
85
+
86
+ /**
87
+ * Get provider status
88
+ */
89
+ getProviderStatus(): Record<string, any> {
90
+ return this.registry.getStatus();
91
+ }
92
+
93
+ /**
94
+ * Execute single prompt with optional streaming
95
+ */
96
+ async execute(
97
+ prompt: string,
98
+ model?: string,
99
+ streaming?: StreamingConfig
100
+ ): Promise<ExecuteResult> {
101
+ const selectedModel = model || this.registry.selectModel();
102
+ if (!selectedModel) {
103
+ return {
104
+ success: false,
105
+ error: "No providers available",
106
+ model: "unknown",
107
+ provider: "unknown",
108
+ };
109
+ }
110
+
111
+ // Check cache first
112
+ const cached = this.cache.get(prompt, selectedModel);
113
+ if (cached) {
114
+ return {
115
+ success: true,
116
+ content: cached.content,
117
+ model: cached.model,
118
+ provider: cached.provider,
119
+ tokens: cached.tokens,
120
+ cost: 0, // Cache hit = no cost
121
+ cached: true,
122
+ duration_ms: 0,
123
+ };
124
+ }
125
+
126
+ const provider = selectedModel.split("/")[0];
127
+ const breaker = this.getCircuitBreaker(provider);
128
+
129
+ const startTime = Date.now();
130
+ const { result, error, attempts, circuit_tripped } = await withRetry(
131
+ async () => {
132
+ return this.executeRequest(selectedModel, streaming);
133
+ },
134
+ this.retryConfig,
135
+ breaker
136
+ );
137
+
138
+ const duration_ms = Date.now() - startTime;
139
+
140
+ if (!result) {
141
+ return {
142
+ success: false,
143
+ error: error?.message || "Execution failed",
144
+ model: selectedModel,
145
+ provider,
146
+ attempts,
147
+ };
148
+ }
149
+
150
+ // Record cost
151
+ const costSnapshot = this.costTracker.record(
152
+ provider,
153
+ selectedModel,
154
+ result.input_tokens || 0,
155
+ result.output_tokens || 0
156
+ );
157
+
158
+ // Cache successful response
159
+ if (result.content) {
160
+ this.cache.set(prompt, selectedModel, {
161
+ content: result.content,
162
+ model: selectedModel,
163
+ provider,
164
+ tokens: costSnapshot.input_tokens + costSnapshot.output_tokens,
165
+ cost: costSnapshot.total_cost,
166
+ });
167
+ }
168
+
169
+ return {
170
+ success: true,
171
+ content: result.content,
172
+ model: selectedModel,
173
+ provider,
174
+ tokens: costSnapshot.input_tokens + costSnapshot.output_tokens,
175
+ cost: costSnapshot.total_cost,
176
+ duration_ms,
177
+ attempts,
178
+ };
179
+ }
180
+
181
+ /**
182
+ * Execute parallel across multiple models
183
+ */
184
+ async executeParallel(
185
+ prompt: string,
186
+ models?: string[],
187
+ streaming?: StreamingConfig
188
+ ): Promise<ParallelResult> {
189
+ const selectedModels = models || this.registry.getReadyProviders().slice(0, 3).map(p => `${p}/default`);
190
+ const startTime = Date.now();
191
+
192
+ const results: ExecuteResult[] = [];
193
+ let total_cost = 0;
194
+
195
+ // Execute with concurrency limit
196
+ for (let i = 0; i < selectedModels.length; i += this.maxConcurrent) {
197
+ const batch = selectedModels.slice(i, i + this.maxConcurrent);
198
+ const batchResults = await Promise.all(
199
+ batch.map((model) => this.execute(prompt, model, streaming))
200
+ );
201
+ results.push(...batchResults);
202
+
203
+ for (const r of batchResults) {
204
+ if (r.success && r.cost) {
205
+ total_cost += r.cost;
206
+ }
207
+ }
208
+ }
209
+
210
+ const duration_ms = Date.now() - startTime;
211
+
212
+ return {
213
+ responses: results,
214
+ total_models: selectedModels.length,
215
+ successful_models: results.filter((r) => r.success).length,
216
+ total_cost,
217
+ duration_ms,
218
+ };
219
+ }
220
+
221
+ /**
222
+ * Get circuit breaker for provider
223
+ */
224
+ private getCircuitBreaker(provider: string): CircuitBreaker {
225
+ if (!this.circuitBreakers.has(provider)) {
226
+ this.circuitBreakers.set(provider, new CircuitBreaker());
227
+ }
228
+ return this.circuitBreakers.get(provider)!;
229
+ }
230
+
231
+ /**
232
+ * Execute HTTP request to provider
233
+ */
234
+ private async executeRequest(
235
+ model: string,
236
+ streaming?: StreamingConfig
237
+ ): Promise<{ content: string; input_tokens: number; output_tokens: number }> {
238
+ const provider = model.split("/")[0];
239
+ const providerConfig = this.registry.getStatus().providers[provider];
240
+
241
+ if (!providerConfig?.ready) {
242
+ throw new Error(`Provider ${provider} is not available`);
243
+ }
244
+
245
+ // Build request based on provider mode
246
+ if (providerConfig.mode === "gemini") {
247
+ return this.executeGemini(provider, model);
248
+ } else if (providerConfig.mode === "anthropic") {
249
+ return this.executeAnthropic(provider, model);
250
+ } else {
251
+ return this.executeOpenAI(provider, model);
252
+ }
253
+ }
254
+
255
+ private async executeOpenAI(
256
+ provider: string,
257
+ model: string
258
+ ): Promise<{ content: string; input_tokens: number; output_tokens: number }> {
259
+ return new Promise((resolve, reject) => {
260
+ const status = this.registry.getStatus().providers[provider];
261
+ const apiKey = process.env[`${provider.toUpperCase()}_API_KEY`] || "";
262
+ const baseUrl = process.env[`${provider.toUpperCase()}_OPENAI_BASE_URL`] || "";
263
+
264
+ const payload = JSON.stringify({
265
+ model: model.split("/")[1],
266
+ messages: [{ role: "user", content: "Placeholder" }], // Will be replaced by actual prompt via closure
267
+ });
268
+
269
+ const url = new URL(`${baseUrl}/chat/completions`);
270
+ const options = {
271
+ hostname: url.hostname,
272
+ port: url.port || (url.protocol === "https:" ? 443 : 80),
273
+ path: url.pathname,
274
+ method: "POST",
275
+ headers: {
276
+ "Content-Type": "application/json",
277
+ "Authorization": `Bearer ${apiKey}`,
278
+ "User-Agent": "TMLPD-Pi/1.0",
279
+ },
280
+ };
281
+
282
+ const protocol = url.protocol === "https:" ? https : http;
283
+ const req = protocol.request(options, (res) => {
284
+ let data = "";
285
+ res.on("data", (chunk) => (data += chunk));
286
+ res.on("end", () => {
287
+ if (res.statusCode && res.statusCode >= 400) {
288
+ reject(new Error(`HTTP ${res.statusCode}`));
289
+ return;
290
+ }
291
+ try {
292
+ const json = JSON.parse(data);
293
+ const content = json.choices?.[0]?.message?.content || "";
294
+ const tokens = json.usage?.total_tokens || 0;
295
+ resolve({ content, input_tokens: Math.floor(tokens * 0.3), output_tokens: Math.floor(tokens * 0.7) });
296
+ } catch {
297
+ reject(new Error("Invalid JSON response"));
298
+ }
299
+ });
300
+ });
301
+
302
+ req.on("error", reject);
303
+ req.write(payload);
304
+ req.end();
305
+ });
306
+ }
307
+
308
+ private async executeAnthropic(
309
+ provider: string,
310
+ model: string
311
+ ): Promise<{ content: string; input_tokens: number; output_tokens: number }> {
312
+ return new Promise((resolve, reject) => {
313
+ const apiKey = process.env[`${provider.toUpperCase()}_API_KEY`] || "";
314
+ const baseUrl = process.env[`${provider.toUpperCase()}_BASE_URL`] || "";
315
+
316
+ const payload = JSON.stringify({
317
+ model: model.split("/")[1],
318
+ max_tokens: 4096,
319
+ messages: [{ role: "user", content: "Placeholder" }],
320
+ });
321
+
322
+ const url = new URL(baseUrl);
323
+ const options = {
324
+ hostname: url.hostname,
325
+ port: url.port || 443,
326
+ path: url.pathname,
327
+ method: "POST",
328
+ headers: {
329
+ "Content-Type": "application/json",
330
+ "x-api-key": apiKey,
331
+ "anthropic-version": "2023-06-01",
332
+ "User-Agent": "TMLPD-Pi/1.0",
333
+ },
334
+ };
335
+
336
+ const req = https.request(options, (res) => {
337
+ let data = "";
338
+ res.on("data", (chunk) => (data += chunk));
339
+ res.on("end", () => {
340
+ if (res.statusCode && res.statusCode >= 400) {
341
+ reject(new Error(`HTTP ${res.statusCode}`));
342
+ return;
343
+ }
344
+ try {
345
+ const json = JSON.parse(data);
346
+ const content = json.content?.[0]?.text || "";
347
+ const tokens = json.usage?.output_tokens || 0;
348
+ resolve({ content, input_tokens: Math.floor(tokens * 0.3), output_tokens: Math.floor(tokens * 0.7) });
349
+ } catch {
350
+ reject(new Error("Invalid JSON response"));
351
+ }
352
+ });
353
+ });
354
+
355
+ req.on("error", reject);
356
+ req.write(payload);
357
+ req.end();
358
+ });
359
+ }
360
+
361
+ private async executeGemini(
362
+ provider: string,
363
+ model: string
364
+ ): Promise<{ content: string; input_tokens: number; output_tokens: number }> {
365
+ return new Promise((resolve, reject) => {
366
+ const apiKey = process.env.GOOGLE_API_KEY || "";
367
+ const baseUrl = "https://generativelanguage.googleapis.com/v1beta";
368
+
369
+ const payload = JSON.stringify({
370
+ contents: [{ parts: [{ text: "Placeholder" }] }],
371
+ generationConfig: { maxOutputTokens: 4096 },
372
+ });
373
+
374
+ const url = new URL(`${baseUrl}/models/${model.split("/")[1]}:generateContent`);
375
+ url.searchParams.set("key", apiKey);
376
+
377
+ const options = {
378
+ hostname: url.hostname,
379
+ port: url.port || 443,
380
+ path: url.pathname + url.search,
381
+ method: "POST",
382
+ headers: {
383
+ "Content-Type": "application/json",
384
+ "User-Agent": "TMLPD-Pi/1.0",
385
+ },
386
+ };
387
+
388
+ const req = https.request(options, (res) => {
389
+ let data = "";
390
+ res.on("data", (chunk) => (data += chunk));
391
+ res.on("end", () => {
392
+ if (res.statusCode && res.statusCode >= 400) {
393
+ reject(new Error(`HTTP ${res.statusCode}`));
394
+ return;
395
+ }
396
+ try {
397
+ const json = JSON.parse(data);
398
+ const content = json.candidates?.[0]?.content?.parts?.[0]?.text || "";
399
+ resolve({ content, input_tokens: 100, output_tokens: 100 });
400
+ } catch {
401
+ reject(new Error("Invalid JSON response"));
402
+ }
403
+ });
404
+ });
405
+
406
+ req.on("error", reject);
407
+ req.write(payload);
408
+ req.end();
409
+ });
410
+ }
411
+ }
412
+ // ============================================================================
413
+ // Factory Function
414
+ // ============================================================================
415
+
416
+ let _defaultInstance: TMLPDTools | null = null;
417
+
418
+ /**
419
+ * Create a TMLPD instance
420
+ */
421
+ export function createTMLPD(config?: TMLPDConfig): TMLPDTools {
422
+ return new TMLPDTools(config);
423
+ }
424
+
425
+ /**
426
+ * Get default singleton instance
427
+ */
428
+ export function getDefault(): TMLPDTools {
429
+ if (!_defaultInstance) {
430
+ _defaultInstance = new TMLPDTools();
431
+ }
432
+ return _defaultInstance;
433
+ }
@@ -0,0 +1,232 @@
1
+ /**
2
+ * TMLPD Batch Processing Utilities
3
+ *
4
+ * Queue and process multiple prompts with:
5
+ * - Concurrency control
6
+ * - Priority scheduling
7
+ * - Progress callbacks
8
+ * - Rate limiting
9
+ */
10
+
11
+ import { nanoid } from "nanoid";
12
+
13
+ export interface BatchItem {
14
+ id: string;
15
+ prompt: string;
16
+ model?: string;
17
+ priority: "high" | "normal" | "low";
18
+ callback?: (result: BatchResult) => void;
19
+ metadata?: Record<string, any>;
20
+ }
21
+
22
+ export interface BatchResult {
23
+ id: string;
24
+ success: boolean;
25
+ content?: string;
26
+ error?: string;
27
+ model: string;
28
+ cost: number;
29
+ duration_ms: number;
30
+ cached: boolean;
31
+ }
32
+
33
+ export interface BatchOptions {
34
+ concurrency?: number; // Max parallel executions
35
+ model?: string; // Default model for all
36
+ stop_on_error?: boolean; // Stop batch on first error
37
+ rate_limit?: {
38
+ requests_per_minute?: number;
39
+ tokens_per_minute?: number;
40
+ };
41
+ }
42
+
43
+ export interface BatchProgress {
44
+ total: number;
45
+ completed: number;
46
+ failed: number;
47
+ in_progress: number;
48
+ total_cost: number;
49
+ }
50
+
51
+ export type ProgressCallback = (progress: BatchProgress, result?: BatchResult) => void;
52
+
53
+ export class BatchProcessor {
54
+ private queue: BatchItem[] = [];
55
+ private results: BatchResult[] = [];
56
+ private options: Required<BatchOptions>;
57
+ private executing: Set<string> = new Set();
58
+ private progressCallbacks: ProgressCallback[] = [];
59
+
60
+ constructor(options: BatchOptions = {}) {
61
+ this.options = {
62
+ concurrency: options.concurrency || 5,
63
+ model: options.model || "gpt-4o",
64
+ stop_on_error: options.stop_on_error || false,
65
+ rate_limit: options.rate_limit || {}
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Add item to batch queue.
71
+ */
72
+ add(item: Omit<BatchItem, "id">): string {
73
+ const id = nanoid(8);
74
+ this.queue.push({
75
+ ...item,
76
+ id,
77
+ priority: item.priority || "normal"
78
+ });
79
+ return id;
80
+ }
81
+
82
+ /**
83
+ * Add multiple items.
84
+ */
85
+ addBatch(items: Array<Omit<BatchItem, "id">>): string[] {
86
+ return items.map(item => this.add(item));
87
+ }
88
+
89
+ /**
90
+ * Register progress callback.
91
+ */
92
+ onProgress(callback: ProgressCallback): void {
93
+ this.progressCallbacks.push(callback);
94
+ }
95
+
96
+ /**
97
+ * Get current progress.
98
+ */
99
+ getProgress(): BatchProgress {
100
+ return {
101
+ total: this.results.length + this.queue.length + this.executing.size,
102
+ completed: this.results.filter(r => r.success).length,
103
+ failed: this.results.filter(r => !r.success).length,
104
+ in_progress: this.executing.size,
105
+ total_cost: this.results.reduce((sum, r) => sum + r.cost, 0)
106
+ };
107
+ }
108
+
109
+ /**
110
+ * Execute batch with concurrency control.
111
+ */
112
+ async execute(
113
+ executor: (item: BatchItem) => Promise<BatchResult>
114
+ ): Promise<BatchResult[]> {
115
+ // Sort by priority
116
+ this.queue.sort((a, b) => {
117
+ const priorityOrder = { high: 0, normal: 1, low: 2 };
118
+ return priorityOrder[a.priority] - priorityOrder[b.priority];
119
+ });
120
+
121
+ const total = this.queue.length;
122
+ let index = 0;
123
+
124
+ // Process with concurrency limit
125
+ while (index < this.queue.length || this.executing.size > 0) {
126
+ // Launch up to concurrency limit
127
+ while (index < this.queue.length && this.executing.size < this.options.concurrency) {
128
+ const item = this.queue[index++];
129
+ this.executing.add(item.id);
130
+
131
+ // Execute with promise tracking
132
+ executor(item).then(result => {
133
+ this.results.push(result);
134
+ this.executing.delete(item.id);
135
+
136
+ // Notify progress
137
+ const progress = this.getProgress();
138
+ for (const cb of this.progressCallbacks) {
139
+ cb(progress, result);
140
+ }
141
+ }).catch(error => {
142
+ const result: BatchResult = {
143
+ id: item.id,
144
+ success: false,
145
+ error: error.message,
146
+ model: item.model || this.options.model,
147
+ cost: 0,
148
+ duration_ms: 0,
149
+ cached: false
150
+ };
151
+ this.results.push(result);
152
+ this.executing.delete(item.id);
153
+
154
+ const progress = this.getProgress();
155
+ for (const cb of this.progressCallbacks) {
156
+ cb(progress, result);
157
+ }
158
+
159
+ // Check stop_on_error
160
+ if (this.options.stop_on_error) {
161
+ // Cancel remaining items
162
+ this.queue = [];
163
+ }
164
+ });
165
+
166
+ // Rate limiting: wait between launches
167
+ if (this.options.rate_limit.requests_per_minute) {
168
+ const delay = 60000 / this.options.rate_limit.requests_per_minute;
169
+ await this.sleep(delay);
170
+ }
171
+ }
172
+
173
+ // Wait for at least one to complete
174
+ if (this.executing.size > 0) {
175
+ await this.waitForCompletion();
176
+ }
177
+ }
178
+
179
+ return this.results;
180
+ }
181
+
182
+ /**
183
+ * Clear queue and results.
184
+ */
185
+ reset(): void {
186
+ this.queue = [];
187
+ this.results = [];
188
+ this.executing.clear();
189
+ }
190
+
191
+ /**
192
+ * Get queue size.
193
+ */
194
+ size(): number {
195
+ return this.queue.length + this.executing.size + this.results.length;
196
+ }
197
+
198
+ private sleep(ms: number): Promise<void> {
199
+ return new Promise(resolve => setTimeout(resolve, ms));
200
+ }
201
+
202
+ private async waitForCompletion(): Promise<void> {
203
+ await new Promise(resolve => {
204
+ const check = () => {
205
+ if (this.executing.size === 0) {
206
+ resolve(null);
207
+ } else {
208
+ setTimeout(check, 50);
209
+ }
210
+ };
211
+ check();
212
+ });
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Helper function for simple batch execution.
218
+ */
219
+ export async function executeBatch(
220
+ items: Array<{ prompt: string; model?: string; priority?: "high" | "normal" | "low" }>,
221
+ executor: (prompt: string, model?: string) => Promise<BatchResult>,
222
+ options: BatchOptions = {}
223
+ ): Promise<BatchResult[]> {
224
+ const processor = new BatchProcessor(options);
225
+ processor.addBatch(items.map(i => ({ ...i, priority: i.priority || "normal" })));
226
+ return processor.execute(async (item) => executor(item.prompt, item.model));
227
+ }
228
+
229
+ export default {
230
+ BatchProcessor,
231
+ executeBatch
232
+ };