recursive-llm-ts 4.4.1 → 4.6.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/dist/rlm.js CHANGED
@@ -1,4 +1,19 @@
1
1
  "use strict";
2
+ /**
3
+ * Main RLM (Recursive Language Model) class.
4
+ *
5
+ * Provides the primary API for recursive completions, structured output,
6
+ * streaming, file-based context, caching, retry/resilience, and events.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { RLM } from 'recursive-llm-ts';
11
+ *
12
+ * const rlm = new RLM('gpt-4o-mini', { api_key: process.env.OPENAI_API_KEY });
13
+ * const result = await rlm.completion('Summarize this', longDocument);
14
+ * console.log(result.result);
15
+ * ```
16
+ */
2
17
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
18
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
19
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -9,17 +24,265 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
24
  });
10
25
  };
11
26
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.RLM = void 0;
27
+ exports.RLM = exports.RLMBuilder = exports.RLMResultFormatter = void 0;
13
28
  const bridge_factory_1 = require("./bridge-factory");
14
29
  const file_storage_1 = require("./file-storage");
30
+ const events_1 = require("./events");
31
+ const cache_1 = require("./cache");
32
+ const retry_1 = require("./retry");
33
+ const streaming_1 = require("./streaming");
34
+ const config_1 = require("./config");
35
+ const errors_1 = require("./errors");
36
+ /** Pretty-printable result wrapper */
37
+ class RLMResultFormatter {
38
+ constructor(result, stats, cached, model, trace_events) {
39
+ this.result = result;
40
+ this.stats = stats;
41
+ this.cached = cached;
42
+ this.model = model;
43
+ this.trace_events = trace_events;
44
+ }
45
+ /** Format stats as a concise one-liner */
46
+ prettyStats() {
47
+ const parts = [
48
+ `LLM Calls: ${this.stats.llm_calls}`,
49
+ `Iterations: ${this.stats.iterations}`,
50
+ `Depth: ${this.stats.depth}`,
51
+ ];
52
+ if (this.stats.parsing_retries) {
53
+ parts.push(`Retries: ${this.stats.parsing_retries}`);
54
+ }
55
+ if (this.cached) {
56
+ parts.push('(cached)');
57
+ }
58
+ return parts.join(' | ');
59
+ }
60
+ /** Serialize to a JSON-safe object */
61
+ toJSON() {
62
+ return {
63
+ result: this.result,
64
+ stats: this.stats,
65
+ cached: this.cached,
66
+ model: this.model,
67
+ trace_events: this.trace_events,
68
+ };
69
+ }
70
+ /** Format as Markdown */
71
+ toMarkdown() {
72
+ const lines = [
73
+ '## Result',
74
+ '',
75
+ this.result,
76
+ '',
77
+ '## Stats',
78
+ '',
79
+ `| Metric | Value |`,
80
+ `|--------|-------|`,
81
+ `| LLM Calls | ${this.stats.llm_calls} |`,
82
+ `| Iterations | ${this.stats.iterations} |`,
83
+ `| Depth | ${this.stats.depth} |`,
84
+ ];
85
+ if (this.stats.parsing_retries) {
86
+ lines.push(`| Parsing Retries | ${this.stats.parsing_retries} |`);
87
+ }
88
+ lines.push(`| Cached | ${this.cached} |`);
89
+ lines.push(`| Model | ${this.model} |`);
90
+ return lines.join('\n');
91
+ }
92
+ }
93
+ exports.RLMResultFormatter = RLMResultFormatter;
94
+ // ─── Builder ─────────────────────────────────────────────────────────────────
95
+ /**
96
+ * Fluent builder for configuring RLM instances.
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * const rlm = RLM.builder('gpt-4o-mini')
101
+ * .maxDepth(10)
102
+ * .withMetaAgent()
103
+ * .withDebug()
104
+ * .withCache({ strategy: 'exact' })
105
+ * .withRetry({ maxRetries: 3 })
106
+ * .build();
107
+ * ```
108
+ */
109
+ class RLMBuilder {
110
+ constructor(model) {
111
+ this.config = {};
112
+ this.bridgeType = 'auto';
113
+ this.model = model;
114
+ }
115
+ /** Set the API key */
116
+ apiKey(key) {
117
+ this.config.api_key = key;
118
+ return this;
119
+ }
120
+ /** Set the API base URL */
121
+ apiBase(url) {
122
+ this.config.api_base = url;
123
+ return this;
124
+ }
125
+ /** Set maximum recursion depth */
126
+ maxDepth(depth) {
127
+ this.config.max_depth = depth;
128
+ return this;
129
+ }
130
+ /** Set maximum iterations */
131
+ maxIterations(iterations) {
132
+ this.config.max_iterations = iterations;
133
+ return this;
134
+ }
135
+ /** Enable meta-agent query optimization */
136
+ withMetaAgent(config) {
137
+ this.config.meta_agent = Object.assign({ enabled: true }, config);
138
+ return this;
139
+ }
140
+ /** Enable debug mode */
141
+ withDebug(logOutput) {
142
+ this.config.debug = true;
143
+ if (logOutput) {
144
+ this.config.observability = Object.assign(Object.assign({}, this.config.observability), { debug: true, log_output: logOutput });
145
+ }
146
+ return this;
147
+ }
148
+ /** Configure observability */
149
+ withObservability(config) {
150
+ this.config.observability = config;
151
+ return this;
152
+ }
153
+ /** Configure caching */
154
+ withCache(config) {
155
+ this.config.cache = Object.assign({ enabled: true }, config);
156
+ return this;
157
+ }
158
+ /** Configure retry behavior */
159
+ withRetry(config) {
160
+ this.config.retry = config;
161
+ return this;
162
+ }
163
+ /** Configure fallback models */
164
+ withFallback(models) {
165
+ this.config.fallback = { models, strategy: 'sequential' };
166
+ return this;
167
+ }
168
+ /** Set the bridge type */
169
+ bridge(type) {
170
+ this.bridgeType = type;
171
+ return this;
172
+ }
173
+ /** Configure context overflow recovery */
174
+ withContextOverflow(config) {
175
+ this.config.context_overflow = Object.assign({ enabled: true }, config);
176
+ return this;
177
+ }
178
+ /** Set the Go binary path */
179
+ binaryPath(path) {
180
+ this.config.go_binary_path = path;
181
+ return this;
182
+ }
183
+ /** Add LiteLLM passthrough parameters */
184
+ litellmParams(params) {
185
+ this.config.litellm_params = params;
186
+ return this;
187
+ }
188
+ /** Build the RLM instance */
189
+ build() {
190
+ return new RLM(this.model, this.config, this.bridgeType);
191
+ }
192
+ }
193
+ exports.RLMBuilder = RLMBuilder;
194
+ // ─── Main RLM Class ──────────────────────────────────────────────────────────
15
195
  class RLM {
196
+ /**
197
+ * Create a new RLM instance.
198
+ *
199
+ * @param model - The LLM model identifier (e.g., 'gpt-4o-mini', 'claude-sonnet-4-20250514')
200
+ * @param rlmConfig - Configuration options for the RLM engine
201
+ * @param bridgeType - Bridge selection: 'auto' (default), 'go', 'pythonia', 'bunpy'
202
+ *
203
+ * @example
204
+ * ```typescript
205
+ * const rlm = new RLM('gpt-4o-mini', {
206
+ * api_key: process.env.OPENAI_API_KEY,
207
+ * max_depth: 5,
208
+ * cache: { enabled: true },
209
+ * retry: { maxRetries: 3 },
210
+ * });
211
+ * ```
212
+ */
16
213
  constructor(model, rlmConfig = {}, bridgeType = 'auto') {
17
214
  this.bridge = null;
18
215
  this.lastTraceEvents = [];
19
216
  this.model = model;
20
217
  this.rlmConfig = this.normalizeConfig(rlmConfig);
21
218
  this.bridgeType = bridgeType;
219
+ this.events = new events_1.RLMEventEmitter();
220
+ this.cache = new cache_1.RLMCache(rlmConfig.cache);
22
221
  }
222
+ // ─── Static Factory Methods ──────────────────────────────────────────────
223
+ /**
224
+ * Create an RLM instance using environment variables for configuration.
225
+ *
226
+ * @param model - The LLM model identifier
227
+ * @returns RLM instance configured from environment
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * // Uses OPENAI_API_KEY from environment
232
+ * const rlm = RLM.fromEnv('gpt-4o-mini');
233
+ * ```
234
+ */
235
+ static fromEnv(model) {
236
+ return new RLM(model, {
237
+ api_key: process.env.OPENAI_API_KEY,
238
+ api_base: process.env.OPENAI_API_BASE,
239
+ debug: process.env.RLM_DEBUG === '1' || process.env.RLM_DEBUG === 'true',
240
+ });
241
+ }
242
+ /**
243
+ * Create an RLM instance with debug logging enabled.
244
+ *
245
+ * @param model - The LLM model identifier
246
+ * @param config - Additional configuration options
247
+ * @returns RLM instance with debug mode active
248
+ */
249
+ static withDebug(model, config = {}) {
250
+ return new RLM(model, Object.assign(Object.assign({}, config), { debug: true }));
251
+ }
252
+ /**
253
+ * Create an RLM instance configured for Azure OpenAI.
254
+ *
255
+ * @param deploymentName - Azure deployment name
256
+ * @param config - Azure-specific configuration
257
+ * @returns RLM instance configured for Azure
258
+ */
259
+ static forAzure(deploymentName, config) {
260
+ return new RLM(deploymentName, {
261
+ api_base: config.apiBase,
262
+ api_key: config.apiKey || process.env.AZURE_API_KEY,
263
+ litellm_params: { api_version: config.apiVersion || '2024-02-15-preview' },
264
+ });
265
+ }
266
+ /**
267
+ * Create a fluent builder for advanced configuration.
268
+ *
269
+ * @param model - The LLM model identifier
270
+ * @returns Builder instance
271
+ *
272
+ * @example
273
+ * ```typescript
274
+ * const rlm = RLM.builder('gpt-4o-mini')
275
+ * .apiKey(process.env.OPENAI_API_KEY!)
276
+ * .maxDepth(10)
277
+ * .withMetaAgent()
278
+ * .withCache({ strategy: 'exact' })
279
+ * .build();
280
+ * ```
281
+ */
282
+ static builder(model) {
283
+ return new RLMBuilder(model);
284
+ }
285
+ // ─── Config Normalization ────────────────────────────────────────────────
23
286
  normalizeConfig(config) {
24
287
  // Normalize debug shorthand into observability config
25
288
  if (config.debug && !config.observability) {
@@ -30,6 +293,7 @@ class RLM {
30
293
  }
31
294
  return config;
32
295
  }
296
+ // ─── Bridge Management ───────────────────────────────────────────────────
33
297
  ensureBridge() {
34
298
  return __awaiter(this, void 0, void 0, function* () {
35
299
  if (!this.bridge) {
@@ -38,46 +302,544 @@ class RLM {
38
302
  return this.bridge;
39
303
  });
40
304
  }
41
- completion(query, context) {
42
- return __awaiter(this, void 0, void 0, function* () {
43
- const bridge = yield this.ensureBridge();
44
- const result = yield bridge.completion(this.model, query, context, this.rlmConfig);
45
- if (result.trace_events) {
46
- this.lastTraceEvents = result.trace_events;
305
+ // ─── Event System ────────────────────────────────────────────────────────
306
+ /**
307
+ * Register an event listener.
308
+ *
309
+ * @param event - Event type to listen for
310
+ * @param listener - Callback function
311
+ *
312
+ * @example
313
+ * ```typescript
314
+ * rlm.on('llm_call', (e) => console.log(`Calling ${e.model}`));
315
+ * rlm.on('error', (e) => reportError(e.error));
316
+ * rlm.on('cache', (e) => console.log(`Cache ${e.action}`));
317
+ * ```
318
+ */
319
+ on(event, listener) {
320
+ this.events.on(event, listener);
321
+ return this;
322
+ }
323
+ /**
324
+ * Register a one-time event listener.
325
+ *
326
+ * @param event - Event type to listen for
327
+ * @param listener - Callback function (called once then removed)
328
+ */
329
+ once(event, listener) {
330
+ this.events.once(event, listener);
331
+ return this;
332
+ }
333
+ /**
334
+ * Remove an event listener.
335
+ *
336
+ * @param event - Event type
337
+ * @param listener - The listener function to remove
338
+ */
339
+ off(event, listener) {
340
+ this.events.off(event, listener);
341
+ return this;
342
+ }
343
+ /** Remove all event listeners */
344
+ removeAllListeners(event) {
345
+ this.events.removeAllListeners(event);
346
+ return this;
347
+ }
348
+ // ─── Core Completions ────────────────────────────────────────────────────
349
+ /**
350
+ * Execute a completion against an LLM with recursive decomposition.
351
+ *
352
+ * @param query - The question or instruction for the LLM
353
+ * @param context - The document or data to process (can be very large)
354
+ * @param options - Optional completion settings
355
+ * @returns The LLM response with execution statistics
356
+ *
357
+ * @example
358
+ * ```typescript
359
+ * const result = await rlm.completion('Summarize the key points', longDocument);
360
+ * console.log(result.result);
361
+ * console.log(`Used ${result.stats.llm_calls} LLM calls`);
362
+ * ```
363
+ */
364
+ completion(query_1, context_1) {
365
+ return __awaiter(this, arguments, void 0, function* (query, context, options = {}) {
366
+ const startTime = Date.now();
367
+ this.events.emit('completion_start', {
368
+ timestamp: startTime,
369
+ type: 'completion_start',
370
+ model: this.model,
371
+ query,
372
+ contextLength: context.length,
373
+ structured: false,
374
+ });
375
+ // Check cache
376
+ const cached = this.cache.lookup(this.model, query, context);
377
+ if (cached.hit && cached.value) {
378
+ this.events.emit('cache', { timestamp: Date.now(), type: 'cache', action: 'hit' });
379
+ this.events.emit('completion_end', {
380
+ timestamp: Date.now(),
381
+ type: 'completion_end',
382
+ model: this.model,
383
+ duration: Date.now() - startTime,
384
+ stats: cached.value.stats,
385
+ cached: true,
386
+ });
387
+ return Object.assign(Object.assign({}, cached.value), { cached: true, model: this.model });
388
+ }
389
+ this.events.emit('cache', { timestamp: Date.now(), type: 'cache', action: 'miss' });
390
+ // Execute with retry
391
+ const execute = () => __awaiter(this, void 0, void 0, function* () {
392
+ const bridge = yield this.ensureBridge();
393
+ this.events.emit('llm_call', {
394
+ timestamp: Date.now(),
395
+ type: 'llm_call',
396
+ model: this.model,
397
+ queryLength: query.length,
398
+ contextLength: context.length,
399
+ });
400
+ const result = yield bridge.completion(this.model, query, context, this.rlmConfig);
401
+ this.events.emit('llm_response', {
402
+ timestamp: Date.now(),
403
+ type: 'llm_response',
404
+ model: this.model,
405
+ duration: Date.now() - startTime,
406
+ });
407
+ return result;
408
+ });
409
+ try {
410
+ const result = yield (0, retry_1.withRetry)(execute, this.rlmConfig.retry, options.signal);
411
+ if (result.trace_events) {
412
+ this.lastTraceEvents = result.trace_events;
413
+ }
414
+ // Store in cache
415
+ this.cache.store(this.model, query, context, result);
416
+ this.events.emit('cache', { timestamp: Date.now(), type: 'cache', action: 'store' });
417
+ this.events.emit('completion_end', {
418
+ timestamp: Date.now(),
419
+ type: 'completion_end',
420
+ model: this.model,
421
+ duration: Date.now() - startTime,
422
+ stats: result.stats,
423
+ cached: false,
424
+ });
425
+ return Object.assign(Object.assign({}, result), { cached: false, model: this.model });
426
+ }
427
+ catch (err) {
428
+ const error = err instanceof Error ? err : new Error(String(err));
429
+ this.events.emit('error', {
430
+ timestamp: Date.now(),
431
+ type: 'error',
432
+ error,
433
+ operation: 'completion',
434
+ });
435
+ throw err instanceof errors_1.RLMError ? err : (0, errors_1.classifyError)(error);
47
436
  }
48
- return result;
49
437
  });
50
438
  }
439
+ /**
440
+ * Extract structured, typed data from context using a Zod schema.
441
+ *
442
+ * @param query - The extraction task to perform
443
+ * @param context - The document or data to process
444
+ * @param schema - Zod schema defining the expected output structure
445
+ * @param options - Execution options (parallelExecution, maxRetries, signal)
446
+ * @returns Typed result matching your Zod schema
447
+ *
448
+ * @example
449
+ * ```typescript
450
+ * const schema = z.object({
451
+ * summary: z.string(),
452
+ * score: z.number().min(1).max(10),
453
+ * tags: z.array(z.string()),
454
+ * });
455
+ *
456
+ * const result = await rlm.structuredCompletion('Analyze this document', doc, schema);
457
+ * console.log(result.result.summary); // string
458
+ * console.log(result.result.score); // number
459
+ * console.log(result.result.tags); // string[]
460
+ * ```
461
+ */
51
462
  structuredCompletion(query_1, context_1, schema_1) {
52
463
  return __awaiter(this, arguments, void 0, function* (query, context, schema, options = {}) {
53
- var _a, _b;
54
- const bridge = yield this.ensureBridge();
464
+ const startTime = Date.now();
465
+ this.events.emit('completion_start', {
466
+ timestamp: startTime,
467
+ type: 'completion_start',
468
+ model: this.model,
469
+ query,
470
+ contextLength: context.length,
471
+ structured: true,
472
+ });
55
473
  const jsonSchema = this.zodToJsonSchema(schema);
56
- const structuredConfig = {
57
- schema: jsonSchema,
58
- parallelExecution: (_a = options.parallelExecution) !== null && _a !== void 0 ? _a : true,
59
- maxRetries: (_b = options.maxRetries) !== null && _b !== void 0 ? _b : 3
60
- };
61
- const result = yield bridge.completion(this.model, query, context, Object.assign(Object.assign({}, this.rlmConfig), { structured: structuredConfig }));
62
- if (result.trace_events) {
63
- this.lastTraceEvents = result.trace_events;
474
+ const execute = () => __awaiter(this, void 0, void 0, function* () {
475
+ var _a, _b;
476
+ const bridge = yield this.ensureBridge();
477
+ const structuredConfig = {
478
+ schema: jsonSchema,
479
+ parallelExecution: (_a = options.parallelExecution) !== null && _a !== void 0 ? _a : true,
480
+ maxRetries: (_b = options.maxRetries) !== null && _b !== void 0 ? _b : 3,
481
+ };
482
+ this.events.emit('llm_call', {
483
+ timestamp: Date.now(),
484
+ type: 'llm_call',
485
+ model: this.model,
486
+ queryLength: query.length,
487
+ contextLength: context.length,
488
+ });
489
+ const result = yield bridge.completion(this.model, query, context, Object.assign(Object.assign({}, this.rlmConfig), { structured: structuredConfig }));
490
+ return result;
491
+ });
492
+ try {
493
+ const result = yield (0, retry_1.withRetry)(execute, this.rlmConfig.retry, options.signal);
494
+ if (result.trace_events) {
495
+ this.lastTraceEvents = result.trace_events;
496
+ }
497
+ // Validate result against Zod schema for type safety
498
+ let validated;
499
+ try {
500
+ validated = schema.parse(result.result);
501
+ }
502
+ catch (zodErr) {
503
+ throw new errors_1.RLMValidationError({
504
+ message: `Structured output failed Zod validation: ${zodErr.message}`,
505
+ expected: jsonSchema,
506
+ received: result.result,
507
+ zodErrors: zodErr.errors || zodErr.issues,
508
+ });
509
+ }
510
+ this.events.emit('completion_end', {
511
+ timestamp: Date.now(),
512
+ type: 'completion_end',
513
+ model: this.model,
514
+ duration: Date.now() - startTime,
515
+ stats: result.stats,
516
+ cached: false,
517
+ });
518
+ return {
519
+ result: validated,
520
+ stats: result.stats,
521
+ trace_events: result.trace_events,
522
+ };
523
+ }
524
+ catch (err) {
525
+ const error = err instanceof Error ? err : new Error(String(err));
526
+ this.events.emit('error', {
527
+ timestamp: Date.now(),
528
+ type: 'error',
529
+ error,
530
+ operation: 'structuredCompletion',
531
+ });
532
+ throw err;
533
+ }
534
+ });
535
+ }
536
+ // ─── Streaming ───────────────────────────────────────────────────────────
537
+ /**
538
+ * Stream a completion with progressive text output.
539
+ *
540
+ * Returns an async iterable of stream chunks. Supports AbortController
541
+ * for cancellation.
542
+ *
543
+ * Note: Currently simulates streaming by chunking the full response.
544
+ * Full streaming support (from the Go binary) is planned.
545
+ *
546
+ * @param query - The question or instruction for the LLM
547
+ * @param context - The document or data to process
548
+ * @param options - Stream options including AbortController signal
549
+ * @returns Async iterable stream of chunks
550
+ *
551
+ * @example
552
+ * ```typescript
553
+ * const stream = rlm.streamCompletion(query, context);
554
+ * for await (const chunk of stream) {
555
+ * if (chunk.type === 'text') process.stdout.write(chunk.text);
556
+ * }
557
+ *
558
+ * // Or collect as string
559
+ * const text = await rlm.streamCompletion(query, context).toText();
560
+ *
561
+ * // With abort
562
+ * const controller = new AbortController();
563
+ * const stream = rlm.streamCompletion(query, context, { signal: controller.signal });
564
+ * setTimeout(() => controller.abort(), 5000);
565
+ * ```
566
+ */
567
+ streamCompletion(query, context, options = {}) {
568
+ const stream = new streaming_1.RLMStream(options.signal);
569
+ (() => __awaiter(this, void 0, void 0, function* () {
570
+ var _a;
571
+ try {
572
+ const result = yield this.completion(query, context, { signal: options.signal });
573
+ const text = typeof result.result === 'string' ? result.result : JSON.stringify(result.result);
574
+ // Simulate streaming by chunking
575
+ const chunkSize = 20;
576
+ for (let i = 0; i < text.length; i += chunkSize) {
577
+ if ((_a = options.signal) === null || _a === void 0 ? void 0 : _a.aborted)
578
+ return;
579
+ const chunk = text.slice(i, i + chunkSize);
580
+ stream.push({ type: 'text', text: chunk, timestamp: Date.now() });
581
+ if (options.onChunk) {
582
+ options.onChunk({ type: 'text', text: chunk, timestamp: Date.now() });
583
+ }
584
+ // Yield to event loop
585
+ yield new Promise(resolve => setImmediate(resolve));
586
+ }
587
+ stream.complete(result.stats);
64
588
  }
65
- // Validate result against Zod schema for type safety
66
- const validated = schema.parse(result.result);
589
+ catch (err) {
590
+ stream.pushError(err instanceof Error ? err : new Error(String(err)));
591
+ }
592
+ }))();
593
+ return stream;
594
+ }
595
+ /**
596
+ * Stream a structured completion with partial object updates.
597
+ *
598
+ * @param query - The extraction task to perform
599
+ * @param context - The document or data to process
600
+ * @param schema - Zod schema for the output structure
601
+ * @param options - Stream and execution options
602
+ * @returns Async iterable stream with partial object chunks
603
+ */
604
+ streamStructuredCompletion(query, context, schema, options = {}) {
605
+ const stream = new streaming_1.RLMStream(options.signal);
606
+ (() => __awaiter(this, void 0, void 0, function* () {
607
+ try {
608
+ const result = yield this.structuredCompletion(query, context, schema, options);
609
+ stream.push({
610
+ type: 'partial_object',
611
+ object: result.result,
612
+ timestamp: Date.now(),
613
+ });
614
+ stream.complete(result.stats);
615
+ }
616
+ catch (err) {
617
+ stream.pushError(err instanceof Error ? err : new Error(String(err)));
618
+ }
619
+ }))();
620
+ return stream;
621
+ }
622
+ // ─── Batch Operations ────────────────────────────────────────────────────
623
+ /**
624
+ * Execute multiple completions in parallel with concurrency control.
625
+ *
626
+ * @param queries - Array of query+context pairs to process
627
+ * @param options - Batch options including concurrency limit
628
+ * @returns Array of results in the same order as input
629
+ *
630
+ * @example
631
+ * ```typescript
632
+ * const results = await rlm.batchCompletion([
633
+ * { query: 'Summarize chapter 1', context: ch1 },
634
+ * { query: 'Summarize chapter 2', context: ch2 },
635
+ * { query: 'Summarize chapter 3', context: ch3 },
636
+ * ], { concurrency: 2 });
637
+ * ```
638
+ */
639
+ batchCompletion(queries_1) {
640
+ return __awaiter(this, arguments, void 0, function* (queries, options = {}) {
641
+ var _a;
642
+ const concurrency = (_a = options.concurrency) !== null && _a !== void 0 ? _a : 3;
643
+ const results = new Array(queries.length);
644
+ let index = 0;
645
+ const worker = () => __awaiter(this, void 0, void 0, function* () {
646
+ var _a;
647
+ while (index < queries.length) {
648
+ if ((_a = options.signal) === null || _a === void 0 ? void 0 : _a.aborted)
649
+ return;
650
+ const i = index++;
651
+ try {
652
+ results[i] = yield this.completion(queries[i].query, queries[i].context, { signal: options.signal });
653
+ }
654
+ catch (err) {
655
+ results[i] = err instanceof Error ? err : new Error(String(err));
656
+ }
657
+ }
658
+ });
659
+ const workers = Array.from({ length: Math.min(concurrency, queries.length) }, () => worker());
660
+ yield Promise.all(workers);
661
+ return results;
662
+ });
663
+ }
664
+ /**
665
+ * Execute multiple structured completions in parallel.
666
+ *
667
+ * @param queries - Array of query+context+schema triples
668
+ * @param options - Batch options including concurrency limit
669
+ * @returns Array of typed results
670
+ */
671
+ batchStructuredCompletion(queries_1) {
672
+ return __awaiter(this, arguments, void 0, function* (queries, options = {}) {
673
+ var _a;
674
+ const concurrency = (_a = options.concurrency) !== null && _a !== void 0 ? _a : 3;
675
+ const results = new Array(queries.length);
676
+ let index = 0;
677
+ const worker = () => __awaiter(this, void 0, void 0, function* () {
678
+ var _a;
679
+ while (index < queries.length) {
680
+ if ((_a = options.signal) === null || _a === void 0 ? void 0 : _a.aborted)
681
+ return;
682
+ const i = index++;
683
+ try {
684
+ results[i] = yield this.structuredCompletion(queries[i].query, queries[i].context, queries[i].schema, { signal: options.signal });
685
+ }
686
+ catch (err) {
687
+ results[i] = err instanceof Error ? err : new Error(String(err));
688
+ }
689
+ }
690
+ });
691
+ const workers = Array.from({ length: Math.min(concurrency, queries.length) }, () => worker());
692
+ yield Promise.all(workers);
693
+ return results;
694
+ });
695
+ }
696
+ // ─── File-Based Completions ──────────────────────────────────────────────
697
+ /**
698
+ * Run a completion using files from a folder (local or S3) as context.
699
+ *
700
+ * @param query - The question or task to perform
701
+ * @param fileConfig - File storage configuration (local path or S3 bucket)
702
+ * @returns Result with fileStorage metadata (files included, skipped, total size)
703
+ *
704
+ * @example
705
+ * ```typescript
706
+ * const result = await rlm.completionFromFiles(
707
+ * 'Summarize the architecture',
708
+ * { type: 'local', path: './src', extensions: ['.ts'] }
709
+ * );
710
+ * console.log(result.result);
711
+ * console.log(`Processed ${result.fileStorage.files.length} files`);
712
+ * ```
713
+ */
714
+ completionFromFiles(query, fileConfig) {
715
+ return __awaiter(this, void 0, void 0, function* () {
716
+ const builder = new file_storage_1.FileContextBuilder(fileConfig);
717
+ const storageResult = yield builder.buildContext();
718
+ const result = yield this.completion(query, storageResult.context);
719
+ return Object.assign(Object.assign({}, result), { fileStorage: storageResult });
720
+ });
721
+ }
722
+ /**
723
+ * Run a structured completion using files from a folder (local or S3) as context.
724
+ *
725
+ * @param query - The extraction task to perform
726
+ * @param fileConfig - File storage configuration
727
+ * @param schema - Zod schema for the output structure
728
+ * @param options - Execution options
729
+ * @returns Typed result with fileStorage metadata
730
+ */
731
+ structuredCompletionFromFiles(query_1, fileConfig_1, schema_1) {
732
+ return __awaiter(this, arguments, void 0, function* (query, fileConfig, schema, options = {}) {
733
+ const builder = new file_storage_1.FileContextBuilder(fileConfig);
734
+ const storageResult = yield builder.buildContext();
735
+ const result = yield this.structuredCompletion(query, storageResult.context, schema, options);
67
736
  return {
68
- result: validated,
737
+ result: result.result,
69
738
  stats: result.stats,
70
- trace_events: result.trace_events
739
+ trace_events: result.trace_events,
740
+ fileStorage: storageResult,
71
741
  };
72
742
  });
73
743
  }
744
+ /**
745
+ * Preview which files would be included from a file storage config
746
+ * without actually reading them. Useful for dry-runs.
747
+ *
748
+ * @param fileConfig - File storage configuration
749
+ * @returns Array of relative file paths that match the config
750
+ */
751
+ previewFiles(fileConfig) {
752
+ return __awaiter(this, void 0, void 0, function* () {
753
+ const builder = new file_storage_1.FileContextBuilder(fileConfig);
754
+ return builder.listMatchingFiles();
755
+ });
756
+ }
757
+ /**
758
+ * Build context from a file storage config without running a completion.
759
+ * Useful for inspecting the generated context string.
760
+ *
761
+ * @param fileConfig - File storage configuration
762
+ * @returns Built context with metadata
763
+ */
764
+ buildFileContext(fileConfig) {
765
+ return __awaiter(this, void 0, void 0, function* () {
766
+ const builder = new file_storage_1.FileContextBuilder(fileConfig);
767
+ return builder.buildContext();
768
+ });
769
+ }
770
+ // ─── Observability ───────────────────────────────────────────────────────
74
771
  /**
75
772
  * Returns trace events from the last operation.
76
773
  * Only populated when observability is enabled in the config.
774
+ *
775
+ * @returns Array of trace events from the most recent completion
77
776
  */
78
777
  getTraceEvents() {
79
778
  return this.lastTraceEvents;
80
779
  }
780
+ /**
781
+ * Get cache statistics (hits, misses, hit rate).
782
+ *
783
+ * @returns Cache performance statistics
784
+ */
785
+ getCacheStats() {
786
+ return this.cache.getStats();
787
+ }
788
+ /** Clear the completion cache */
789
+ clearCache() {
790
+ this.cache.clear();
791
+ }
792
+ // ─── Validation ──────────────────────────────────────────────────────────
793
+ /**
794
+ * Validate the current configuration without making any API calls.
795
+ * Checks binary existence, config validity, and connectivity hints.
796
+ *
797
+ * @returns Validation result with issues
798
+ *
799
+ * @example
800
+ * ```typescript
801
+ * const issues = rlm.validate();
802
+ * if (!issues.valid) {
803
+ * console.error('Config issues:', issues.issues);
804
+ * }
805
+ * ```
806
+ */
807
+ validate() {
808
+ return (0, config_1.validateConfig)(this.rlmConfig);
809
+ }
810
+ // ─── Result Formatting ───────────────────────────────────────────────────
811
+ /**
812
+ * Create a formatted result wrapper from a completion result.
813
+ *
814
+ * @param result - The completion result to format
815
+ * @returns Formatter with prettyStats(), toJSON(), and toMarkdown() methods
816
+ */
817
+ formatResult(result) {
818
+ return new RLMResultFormatter(typeof result.result === 'string' ? result.result : JSON.stringify(result.result), result.stats, result.cached, result.model, result.trace_events);
819
+ }
820
+ // ─── Cleanup ─────────────────────────────────────────────────────────────
821
+ /**
822
+ * Clean up the bridge connection and free resources.
823
+ * Call this when you're done using the RLM instance.
824
+ */
825
+ cleanup() {
826
+ return __awaiter(this, void 0, void 0, function* () {
827
+ if (this.bridge) {
828
+ yield this.bridge.cleanup();
829
+ this.bridge = null;
830
+ }
831
+ this.events.removeAllListeners();
832
+ });
833
+ }
834
+ /**
835
+ * Support for `Symbol.asyncDispose` (Node 22+ `await using`).
836
+ */
837
+ [Symbol.asyncDispose]() {
838
+ return __awaiter(this, void 0, void 0, function* () {
839
+ yield this.cleanup();
840
+ });
841
+ }
842
+ // ─── Zod to JSON Schema Conversion ───────────────────────────────────────
81
843
  zodToJsonSchema(schema) {
82
844
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
83
845
  const def = schema._def;
@@ -88,7 +850,7 @@ class RLM {
88
850
  if (defType === 'nullable') {
89
851
  return Object.assign(Object.assign({}, inner), { nullable: true });
90
852
  }
91
- return inner; // Optional/Default/Catch don't change the schema, just validation
853
+ return inner;
92
854
  }
93
855
  // Handle effects (refine, transform, preprocess) - unwrap to inner type
94
856
  if (defType === 'effects') {
@@ -100,13 +862,11 @@ class RLM {
100
862
  }
101
863
  // Handle lazy schemas - unwrap the getter
102
864
  if (defType === 'lazy') {
103
- // For lazy schemas, we need to call the getter to get the actual schema
104
865
  try {
105
866
  const actualSchema = def.getter();
106
867
  return this.zodToJsonSchema(actualSchema);
107
868
  }
108
869
  catch (e) {
109
- // If lazy getter fails, fall back to generic object
110
870
  return { type: 'object' };
111
871
  }
112
872
  }
@@ -120,7 +880,6 @@ class RLM {
120
880
  }
121
881
  // Handle literals
122
882
  if (defType === 'literal') {
123
- // Literals in this Zod version use 'values' array
124
883
  if (def.values && def.values.length > 0) {
125
884
  const value = def.values[0];
126
885
  const valueType = typeof value;
@@ -129,7 +888,6 @@ class RLM {
129
888
  enum: [value]
130
889
  };
131
890
  }
132
- // Fallback for other literal formats
133
891
  const value = def.value;
134
892
  if (value !== undefined) {
135
893
  const valueType = typeof value;
@@ -164,7 +922,6 @@ class RLM {
164
922
  const required = [];
165
923
  for (const [key, value] of Object.entries(shape)) {
166
924
  properties[key] = this.zodToJsonSchema(value);
167
- // A field is required if it's not optional and doesn't have a default
168
925
  const valueDef = value._def;
169
926
  const isOptional = (_d = (_c = (_b = value).isOptional) === null || _c === void 0 ? void 0 : _c.call(_b)) !== null && _d !== void 0 ? _d : false;
170
927
  const hasDefault = (valueDef === null || valueDef === void 0 ? void 0 : valueDef.type) === 'default';
@@ -172,30 +929,20 @@ class RLM {
172
929
  required.push(key);
173
930
  }
174
931
  }
175
- const result = {
176
- type: 'object',
177
- properties
178
- };
179
- if (required.length > 0) {
932
+ const result = { type: 'object', properties };
933
+ if (required.length > 0)
180
934
  result.required = required;
181
- }
182
- // Handle unknown keys via catchall
183
935
  if (def.catchall) {
184
936
  const catchallType = (_e = def.catchall._def) === null || _e === void 0 ? void 0 : _e.type;
185
- if (catchallType === 'unknown') {
937
+ if (catchallType === 'unknown')
186
938
  result.additionalProperties = true;
187
- }
188
- else if (catchallType === 'never') {
939
+ else if (catchallType === 'never')
189
940
  result.additionalProperties = false;
190
- }
191
941
  }
192
- // Also check legacy unknownKeys
193
- if (def.unknownKeys === 'passthrough') {
942
+ if (def.unknownKeys === 'passthrough')
194
943
  result.additionalProperties = true;
195
- }
196
- else if (def.unknownKeys === 'strict') {
944
+ else if (def.unknownKeys === 'strict')
197
945
  result.additionalProperties = false;
198
- }
199
946
  return result;
200
947
  }
201
948
  // Handle array type
@@ -205,7 +952,6 @@ class RLM {
205
952
  type: 'array',
206
953
  items: this.zodToJsonSchema(itemSchema)
207
954
  };
208
- // Handle array length constraints from checks
209
955
  if (def.checks && Array.isArray(def.checks)) {
210
956
  for (const check of def.checks) {
211
957
  const checkDef = ((_f = check._zod) === null || _f === void 0 ? void 0 : _f.def) || check.def || check;
@@ -223,7 +969,6 @@ class RLM {
223
969
  }
224
970
  }
225
971
  }
226
- // Also check direct properties (legacy)
227
972
  if (def.minLength)
228
973
  result.minItems = def.minLength.value || def.minLength;
229
974
  if (def.maxLength)
@@ -244,52 +989,31 @@ class RLM {
244
989
  minItems: items.length,
245
990
  maxItems: def.rest ? undefined : items.length
246
991
  };
247
- // Handle rest element
248
- if (def.rest) {
992
+ if (def.rest)
249
993
  result.items = this.zodToJsonSchema(def.rest);
250
- }
251
- else {
252
- result.items = false; // No additional items allowed
253
- }
994
+ else
995
+ result.items = false;
254
996
  return result;
255
997
  }
256
- // Handle set - convert to array
257
998
  if (defType === 'set') {
258
- return {
259
- type: 'array',
260
- uniqueItems: true,
261
- items: def.valueType ? this.zodToJsonSchema(def.valueType) : {}
262
- };
999
+ return { type: 'array', uniqueItems: true, items: def.valueType ? this.zodToJsonSchema(def.valueType) : {} };
263
1000
  }
264
- // Handle map - convert to object
265
1001
  if (defType === 'map') {
266
- return {
267
- type: 'object',
268
- additionalProperties: def.valueType ? this.zodToJsonSchema(def.valueType) : true
269
- };
1002
+ return { type: 'object', additionalProperties: def.valueType ? this.zodToJsonSchema(def.valueType) : true };
270
1003
  }
271
- // Handle record
272
1004
  if (defType === 'record') {
273
- return {
274
- type: 'object',
275
- additionalProperties: def.valueType ? this.zodToJsonSchema(def.valueType) : true
276
- };
1005
+ return { type: 'object', additionalProperties: def.valueType ? this.zodToJsonSchema(def.valueType) : true };
277
1006
  }
278
- // Handle enum
279
1007
  if (defType === 'enum' || defType === 'nativeEnum') {
280
- if (def.values && Array.isArray(def.values)) {
1008
+ if (def.values && Array.isArray(def.values))
281
1009
  return { type: 'string', enum: def.values };
282
- }
283
- if (def.entries) {
1010
+ if (def.entries)
284
1011
  return { type: 'string', enum: Object.keys(def.entries) };
285
- }
286
1012
  }
287
- // Handle string with constraints
288
1013
  if (defType === 'string') {
289
1014
  const result = { type: 'string' };
290
1015
  if (def.checks && Array.isArray(def.checks)) {
291
1016
  for (const check of def.checks) {
292
- // Access the actual check data via _zod.def
293
1017
  const checkDef = ((_h = check._zod) === null || _h === void 0 ? void 0 : _h.def) || check.def || check;
294
1018
  switch (checkDef.check) {
295
1019
  case 'min_length':
@@ -314,9 +1038,8 @@ class RLM {
314
1038
  result.format = 'uuid';
315
1039
  break;
316
1040
  case 'regex':
317
- if (checkDef.pattern) {
1041
+ if (checkDef.pattern)
318
1042
  result.pattern = checkDef.pattern.source || checkDef.pattern;
319
- }
320
1043
  break;
321
1044
  }
322
1045
  break;
@@ -324,7 +1047,6 @@ class RLM {
324
1047
  result.pattern = ((_j = checkDef.pattern) === null || _j === void 0 ? void 0 : _j.source) || checkDef.pattern;
325
1048
  break;
326
1049
  }
327
- // Also check nested def for formats
328
1050
  if (check.def && check.def.format) {
329
1051
  switch (check.def.format) {
330
1052
  case 'email':
@@ -342,144 +1064,55 @@ class RLM {
342
1064
  }
343
1065
  return result;
344
1066
  }
345
- // Handle number/bigint with constraints
346
1067
  if (defType === 'number' || defType === 'bigint') {
347
1068
  const result = { type: defType === 'bigint' ? 'integer' : 'number' };
348
1069
  if (def.checks && Array.isArray(def.checks)) {
349
1070
  for (const check of def.checks) {
350
- // Access the actual check data via _zod.def
351
1071
  const checkDef = ((_k = check._zod) === null || _k === void 0 ? void 0 : _k.def) || check.def || check;
352
1072
  switch (checkDef.check) {
353
1073
  case 'number_format':
354
- if (checkDef.format === 'safeint') {
1074
+ if (checkDef.format === 'safeint')
355
1075
  result.type = 'integer';
356
- }
357
1076
  break;
358
1077
  case 'greater_than':
359
1078
  result.minimum = checkDef.value;
360
- if (!checkDef.inclusive) {
1079
+ if (!checkDef.inclusive)
361
1080
  result.exclusiveMinimum = true;
362
- }
363
1081
  break;
364
1082
  case 'less_than':
365
1083
  result.maximum = checkDef.value;
366
- if (!checkDef.inclusive) {
1084
+ if (!checkDef.inclusive)
367
1085
  result.exclusiveMaximum = true;
368
- }
369
1086
  break;
370
1087
  case 'multiple_of':
371
1088
  result.multipleOf = checkDef.value;
372
1089
  break;
373
1090
  }
374
- // Also check direct properties (legacy support)
375
- if (check.isInt === true) {
1091
+ if (check.isInt === true)
376
1092
  result.type = 'integer';
377
- }
378
1093
  }
379
1094
  }
380
1095
  return result;
381
1096
  }
382
- // Handle boolean
383
- if (defType === 'boolean') {
1097
+ if (defType === 'boolean')
384
1098
  return { type: 'boolean' };
385
- }
386
- // Handle date
387
- if (defType === 'date') {
1099
+ if (defType === 'date')
388
1100
  return { type: 'string', format: 'date-time' };
389
- }
390
- // Handle null
391
- if (defType === 'null') {
1101
+ if (defType === 'null')
392
1102
  return { type: 'null' };
393
- }
394
- // Handle undefined (not really JSON-serializable, but treat as null)
395
- if (defType === 'undefined') {
1103
+ if (defType === 'undefined')
396
1104
  return { type: 'null' };
397
- }
398
- // Handle void (same as undefined)
399
- if (defType === 'void') {
1105
+ if (defType === 'void')
400
1106
  return { type: 'null' };
401
- }
402
- // Handle any/unknown - no constraints
403
- if (defType === 'any' || defType === 'unknown') {
404
- return {}; // Empty schema accepts anything
405
- }
406
- // Handle never - impossible to satisfy
407
- if (defType === 'never') {
408
- return { not: {} }; // Schema that matches nothing
409
- }
410
- // Handle promise - unwrap to inner type
411
- if (defType === 'promise') {
1107
+ if (defType === 'any' || defType === 'unknown')
1108
+ return {};
1109
+ if (defType === 'never')
1110
+ return { not: {} };
1111
+ if (defType === 'promise')
412
1112
  return this.zodToJsonSchema(def.innerType || def.type);
413
- }
414
- // Handle function - not JSON-serializable
415
- if (defType === 'function') {
1113
+ if (defType === 'function')
416
1114
  return { type: 'string', description: 'Function (not serializable)' };
417
- }
418
- // Default fallback
419
- console.warn(`Unknown Zod type: ${defType}, falling back to string`);
420
1115
  return { type: 'string' };
421
1116
  }
422
- escapeRegex(str) {
423
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
424
- }
425
- /**
426
- * Run a completion using files from a folder (local or S3) as context.
427
- * The folder is read recursively, files are filtered and concatenated
428
- * into a structured context string that the LLM can work through.
429
- */
430
- completionFromFiles(query, fileConfig) {
431
- return __awaiter(this, void 0, void 0, function* () {
432
- const builder = new file_storage_1.FileContextBuilder(fileConfig);
433
- const storageResult = yield builder.buildContext();
434
- const result = yield this.completion(query, storageResult.context);
435
- return Object.assign(Object.assign({}, result), { fileStorage: storageResult });
436
- });
437
- }
438
- /**
439
- * Run a structured completion using files from a folder (local or S3) as context.
440
- * The folder is read recursively, files are filtered and concatenated
441
- * into a structured context string that the LLM can work through.
442
- */
443
- structuredCompletionFromFiles(query_1, fileConfig_1, schema_1) {
444
- return __awaiter(this, arguments, void 0, function* (query, fileConfig, schema, options = {}) {
445
- const builder = new file_storage_1.FileContextBuilder(fileConfig);
446
- const storageResult = yield builder.buildContext();
447
- const result = yield this.structuredCompletion(query, storageResult.context, schema, options);
448
- return {
449
- result: result.result,
450
- stats: result.stats,
451
- trace_events: result.trace_events,
452
- fileStorage: storageResult,
453
- };
454
- });
455
- }
456
- /**
457
- * Preview which files would be included from a file storage config
458
- * without actually reading them. Useful for dry-runs.
459
- */
460
- previewFiles(fileConfig) {
461
- return __awaiter(this, void 0, void 0, function* () {
462
- const builder = new file_storage_1.FileContextBuilder(fileConfig);
463
- return builder.listMatchingFiles();
464
- });
465
- }
466
- /**
467
- * Build context from a file storage config without running a completion.
468
- * Useful for inspecting the generated context string.
469
- */
470
- buildFileContext(fileConfig) {
471
- return __awaiter(this, void 0, void 0, function* () {
472
- const builder = new file_storage_1.FileContextBuilder(fileConfig);
473
- return builder.buildContext();
474
- });
475
- }
476
- cleanup() {
477
- return __awaiter(this, void 0, void 0, function* () {
478
- if (this.bridge) {
479
- yield this.bridge.cleanup();
480
- this.bridge = null;
481
- }
482
- });
483
- }
484
1117
  }
485
1118
  exports.RLM = RLM;