recursive-llm-ts 5.1.1 → 5.2.3

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