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/README.md +375 -12
- package/bin/rlm-go +0 -0
- package/dist/bridge-interface.d.ts +19 -2
- package/dist/cache.d.ts +78 -0
- package/dist/cache.js +246 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.js +162 -0
- package/dist/errors.d.ts +113 -0
- package/dist/errors.js +219 -0
- package/dist/events.d.ts +126 -0
- package/dist/events.js +77 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +38 -1
- package/dist/retry.d.ts +56 -0
- package/dist/retry.js +185 -0
- package/dist/rlm.d.ts +391 -13
- package/dist/rlm.js +815 -182
- package/dist/streaming.d.ts +96 -0
- package/dist/streaming.js +210 -0
- package/go/README.md +9 -1
- package/go/rlm/context_overflow.go +566 -0
- package/go/rlm/context_overflow_test.go +783 -0
- package/go/rlm/errors.go +161 -1
- package/go/rlm/rlm.go +10 -0
- package/go/rlm/structured.go +53 -0
- package/go/rlm/textrank.go +273 -0
- package/go/rlm/textrank_test.go +335 -0
- package/go/rlm/tfidf.go +225 -0
- package/go/rlm/tfidf_test.go +272 -0
- package/go/rlm/types.go +25 -2
- package/package.json +16 -4
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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:
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
248
|
-
if (def.rest) {
|
|
992
|
+
if (def.rest)
|
|
249
993
|
result.items = this.zodToJsonSchema(def.rest);
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
403
|
-
if (defType === '
|
|
404
|
-
return {
|
|
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;
|