@wundr.io/prompt-templates 1.0.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.
- package/dist/engine.d.ts +206 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +506 -0
- package/dist/engine.js.map +1 -0
- package/dist/helpers.d.ts +201 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +566 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +102 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +129 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +415 -0
- package/dist/loader.js.map +1 -0
- package/dist/macros.d.ts +64 -0
- package/dist/macros.d.ts.map +1 -0
- package/dist/macros.js +620 -0
- package/dist/macros.js.map +1 -0
- package/dist/types.d.ts +443 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +62 -0
- package/dist/types.js.map +1 -0
- package/package.json +45 -0
- package/src/engine.ts +616 -0
- package/src/helpers.ts +650 -0
- package/src/index.ts +138 -0
- package/src/loader.ts +468 -0
- package/src/macros.ts +635 -0
- package/src/types.ts +380 -0
package/src/engine.ts
ADDED
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @wundr/prompt-templates - PromptTemplateEngine for dynamic prompt rendering
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import Handlebars, { type HelperDelegate } from 'handlebars';
|
|
6
|
+
|
|
7
|
+
import { getBuiltinHelpers } from './helpers.js';
|
|
8
|
+
import { createLoader } from './loader.js';
|
|
9
|
+
import { getBuiltinMacros } from './macros.js';
|
|
10
|
+
|
|
11
|
+
import type { TemplateLoader } from './loader.js';
|
|
12
|
+
import type {
|
|
13
|
+
PromptTemplateConfig,
|
|
14
|
+
TemplateContext,
|
|
15
|
+
RenderOptions,
|
|
16
|
+
RenderResult,
|
|
17
|
+
RenderMetadata,
|
|
18
|
+
TemplateError,
|
|
19
|
+
MacroDefinition,
|
|
20
|
+
HelperDefinition,
|
|
21
|
+
HelperFunction,
|
|
22
|
+
EngineEvents,
|
|
23
|
+
EngineEventHandler,
|
|
24
|
+
} from './types.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Default render options
|
|
28
|
+
*/
|
|
29
|
+
const DEFAULT_RENDER_OPTIONS: Required<RenderOptions> = {
|
|
30
|
+
strict: false,
|
|
31
|
+
delimiters: ['{{', '}}'],
|
|
32
|
+
escapeHtml: false,
|
|
33
|
+
maxDepth: 10,
|
|
34
|
+
timeout: 5000,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* PromptTemplateEngine provides Jinja2-style templating using Handlebars
|
|
39
|
+
*/
|
|
40
|
+
export class PromptTemplateEngine {
|
|
41
|
+
private readonly handlebars: typeof Handlebars;
|
|
42
|
+
private readonly templates: Map<string, HandlebarsTemplateDelegate> =
|
|
43
|
+
new Map();
|
|
44
|
+
private readonly templateConfigs: Map<string, PromptTemplateConfig> =
|
|
45
|
+
new Map();
|
|
46
|
+
private readonly macros: Map<string, MacroDefinition> = new Map();
|
|
47
|
+
private readonly customHelpers: Map<string, HelperDefinition> = new Map();
|
|
48
|
+
private readonly eventHandlers: Map<
|
|
49
|
+
keyof EngineEvents,
|
|
50
|
+
Set<EngineEventHandler<keyof EngineEvents>>
|
|
51
|
+
> = new Map();
|
|
52
|
+
private readonly loader: TemplateLoader;
|
|
53
|
+
private readonly defaultOptions: Required<RenderOptions>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Create a new PromptTemplateEngine
|
|
57
|
+
*
|
|
58
|
+
* @param options - Default render options
|
|
59
|
+
* @param loaderBaseDir - Base directory for template loading
|
|
60
|
+
*/
|
|
61
|
+
constructor(options?: Partial<RenderOptions>, loaderBaseDir?: string) {
|
|
62
|
+
this.handlebars = Handlebars.create();
|
|
63
|
+
this.defaultOptions = { ...DEFAULT_RENDER_OPTIONS, ...options };
|
|
64
|
+
this.loader = createLoader({ baseDir: loaderBaseDir });
|
|
65
|
+
|
|
66
|
+
// Disable HTML escaping by default for prompts
|
|
67
|
+
if (!this.defaultOptions.escapeHtml) {
|
|
68
|
+
this.handlebars.Utils.escapeExpression = (str: string) => str;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Register built-in helpers
|
|
72
|
+
this.registerBuiltinHelpers();
|
|
73
|
+
|
|
74
|
+
// Register built-in macros
|
|
75
|
+
this.registerBuiltinMacros();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Render a template string with the given context
|
|
80
|
+
*
|
|
81
|
+
* @param template - Template string or template ID
|
|
82
|
+
* @param context - Context data for rendering
|
|
83
|
+
* @param options - Render options
|
|
84
|
+
* @returns Render result with output or error
|
|
85
|
+
*/
|
|
86
|
+
render(
|
|
87
|
+
template: string,
|
|
88
|
+
context: TemplateContext = { variables: {} },
|
|
89
|
+
options?: Partial<RenderOptions>,
|
|
90
|
+
): RenderResult {
|
|
91
|
+
const startTime = Date.now();
|
|
92
|
+
const _mergedOptions = { ...this.defaultOptions, ...options };
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
// Check if this is a template ID
|
|
96
|
+
const compiledTemplate = this.getOrCompileTemplate(template);
|
|
97
|
+
|
|
98
|
+
// Prepare context with system defaults
|
|
99
|
+
const preparedContext = this.prepareContext(context);
|
|
100
|
+
|
|
101
|
+
// Render the template
|
|
102
|
+
const output = compiledTemplate(preparedContext);
|
|
103
|
+
|
|
104
|
+
const metadata: RenderMetadata = {
|
|
105
|
+
renderTime: Date.now() - startTime,
|
|
106
|
+
templateId: this.templateConfigs.has(template) ? template : undefined,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
this.emit('template:rendered', {
|
|
110
|
+
templateId: metadata.templateId || 'inline',
|
|
111
|
+
renderTime: metadata.renderTime,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
success: true,
|
|
116
|
+
output,
|
|
117
|
+
metadata,
|
|
118
|
+
};
|
|
119
|
+
} catch (error) {
|
|
120
|
+
const templateError = this.createTemplateError(error, template);
|
|
121
|
+
|
|
122
|
+
this.emit('template:error', {
|
|
123
|
+
templateId: template,
|
|
124
|
+
error: templateError,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
success: false,
|
|
129
|
+
error: templateError,
|
|
130
|
+
metadata: {
|
|
131
|
+
renderTime: Date.now() - startTime,
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Render a template asynchronously (useful for timeout support)
|
|
139
|
+
*
|
|
140
|
+
* @param template - Template string or template ID
|
|
141
|
+
* @param context - Context data for rendering
|
|
142
|
+
* @param options - Render options
|
|
143
|
+
* @returns Promise resolving to render result
|
|
144
|
+
*/
|
|
145
|
+
async renderAsync(
|
|
146
|
+
template: string,
|
|
147
|
+
context: TemplateContext = { variables: {} },
|
|
148
|
+
options?: Partial<RenderOptions>,
|
|
149
|
+
): Promise<RenderResult> {
|
|
150
|
+
const mergedOptions = { ...this.defaultOptions, ...options };
|
|
151
|
+
|
|
152
|
+
return new Promise(resolve => {
|
|
153
|
+
const timeoutId = setTimeout(() => {
|
|
154
|
+
resolve({
|
|
155
|
+
success: false,
|
|
156
|
+
error: {
|
|
157
|
+
code: 'RENDER_TIMEOUT',
|
|
158
|
+
message: `Template rendering timed out after ${mergedOptions.timeout}ms`,
|
|
159
|
+
},
|
|
160
|
+
metadata: {
|
|
161
|
+
renderTime: mergedOptions.timeout,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
}, mergedOptions.timeout);
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const result = this.render(template, context, options);
|
|
168
|
+
clearTimeout(timeoutId);
|
|
169
|
+
resolve(result);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
clearTimeout(timeoutId);
|
|
172
|
+
resolve({
|
|
173
|
+
success: false,
|
|
174
|
+
error: this.createTemplateError(error, template),
|
|
175
|
+
metadata: {
|
|
176
|
+
renderTime: Date.now(),
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Register a template configuration
|
|
185
|
+
*
|
|
186
|
+
* @param config - Template configuration
|
|
187
|
+
* @returns The engine instance for chaining
|
|
188
|
+
*/
|
|
189
|
+
registerTemplate(config: PromptTemplateConfig): this {
|
|
190
|
+
this.templateConfigs.set(config.id, config);
|
|
191
|
+
|
|
192
|
+
// Pre-compile the template
|
|
193
|
+
try {
|
|
194
|
+
const compiled = this.handlebars.compile(config.template);
|
|
195
|
+
this.templates.set(config.id, compiled);
|
|
196
|
+
this.emit('template:loaded', { templateId: config.id });
|
|
197
|
+
} catch (error) {
|
|
198
|
+
const templateError = this.createTemplateError(error, config.id);
|
|
199
|
+
this.emit('template:error', {
|
|
200
|
+
templateId: config.id,
|
|
201
|
+
error: templateError,
|
|
202
|
+
});
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return this;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Load and register a template from file
|
|
211
|
+
*
|
|
212
|
+
* @param templatePath - Path to the template file
|
|
213
|
+
* @returns The engine instance for chaining
|
|
214
|
+
*/
|
|
215
|
+
loadTemplate(templatePath: string): this {
|
|
216
|
+
const config = this.loader.loadTemplate(templatePath);
|
|
217
|
+
return this.registerTemplate(config);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Load and register a template from file asynchronously
|
|
222
|
+
*
|
|
223
|
+
* @param templatePath - Path to the template file
|
|
224
|
+
* @returns Promise resolving to the engine instance
|
|
225
|
+
*/
|
|
226
|
+
async loadTemplateAsync(templatePath: string): Promise<this> {
|
|
227
|
+
const config = await this.loader.loadTemplateAsync(templatePath);
|
|
228
|
+
return this.registerTemplate(config);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Register a custom macro
|
|
233
|
+
*
|
|
234
|
+
* @param macro - Macro definition
|
|
235
|
+
* @returns The engine instance for chaining
|
|
236
|
+
*/
|
|
237
|
+
registerMacro(macro: MacroDefinition): this {
|
|
238
|
+
this.macros.set(macro.name, macro);
|
|
239
|
+
|
|
240
|
+
// Register as a Handlebars partial
|
|
241
|
+
this.handlebars.registerPartial(macro.name, macro.template);
|
|
242
|
+
|
|
243
|
+
this.emit('macro:registered', { name: macro.name });
|
|
244
|
+
return this;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Register multiple macros
|
|
249
|
+
*
|
|
250
|
+
* @param macros - Array of macro definitions
|
|
251
|
+
* @returns The engine instance for chaining
|
|
252
|
+
*/
|
|
253
|
+
registerMacros(macros: MacroDefinition[]): this {
|
|
254
|
+
for (const macro of macros) {
|
|
255
|
+
this.registerMacro(macro);
|
|
256
|
+
}
|
|
257
|
+
return this;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Register a custom helper function
|
|
262
|
+
*
|
|
263
|
+
* @param name - Helper name
|
|
264
|
+
* @param fn - Helper function
|
|
265
|
+
* @param description - Optional description
|
|
266
|
+
* @returns The engine instance for chaining
|
|
267
|
+
*/
|
|
268
|
+
registerHelper(name: string, fn: HelperFunction, description?: string): this {
|
|
269
|
+
const helper: HelperDefinition = { name, fn, description };
|
|
270
|
+
this.customHelpers.set(name, helper);
|
|
271
|
+
this.handlebars.registerHelper(name, fn as HelperDelegate);
|
|
272
|
+
this.emit('helper:registered', { name });
|
|
273
|
+
return this;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Unregister a helper
|
|
278
|
+
*
|
|
279
|
+
* @param name - Helper name to remove
|
|
280
|
+
* @returns The engine instance for chaining
|
|
281
|
+
*/
|
|
282
|
+
unregisterHelper(name: string): this {
|
|
283
|
+
this.customHelpers.delete(name);
|
|
284
|
+
this.handlebars.unregisterHelper(name);
|
|
285
|
+
return this;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Get a registered template configuration
|
|
290
|
+
*
|
|
291
|
+
* @param id - Template ID
|
|
292
|
+
* @returns Template configuration or undefined
|
|
293
|
+
*/
|
|
294
|
+
getTemplate(id: string): PromptTemplateConfig | undefined {
|
|
295
|
+
return this.templateConfigs.get(id);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Get all registered template IDs
|
|
300
|
+
*
|
|
301
|
+
* @returns Array of template IDs
|
|
302
|
+
*/
|
|
303
|
+
getTemplateIds(): string[] {
|
|
304
|
+
return Array.from(this.templateConfigs.keys());
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Get a registered macro
|
|
309
|
+
*
|
|
310
|
+
* @param name - Macro name
|
|
311
|
+
* @returns Macro definition or undefined
|
|
312
|
+
*/
|
|
313
|
+
getMacro(name: string): MacroDefinition | undefined {
|
|
314
|
+
return this.macros.get(name);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Get all registered macro names
|
|
319
|
+
*
|
|
320
|
+
* @returns Array of macro names
|
|
321
|
+
*/
|
|
322
|
+
getMacroNames(): string[] {
|
|
323
|
+
return Array.from(this.macros.keys());
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Get all registered helper names
|
|
328
|
+
*
|
|
329
|
+
* @returns Array of helper names
|
|
330
|
+
*/
|
|
331
|
+
getHelperNames(): string[] {
|
|
332
|
+
return Array.from(this.customHelpers.keys());
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Check if a template is registered
|
|
337
|
+
*
|
|
338
|
+
* @param id - Template ID
|
|
339
|
+
* @returns True if template is registered
|
|
340
|
+
*/
|
|
341
|
+
hasTemplate(id: string): boolean {
|
|
342
|
+
return this.templateConfigs.has(id);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Remove a registered template
|
|
347
|
+
*
|
|
348
|
+
* @param id - Template ID
|
|
349
|
+
* @returns True if template was removed
|
|
350
|
+
*/
|
|
351
|
+
removeTemplate(id: string): boolean {
|
|
352
|
+
const existed = this.templateConfigs.has(id);
|
|
353
|
+
this.templateConfigs.delete(id);
|
|
354
|
+
this.templates.delete(id);
|
|
355
|
+
return existed;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Clear all registered templates
|
|
360
|
+
*/
|
|
361
|
+
clearTemplates(): void {
|
|
362
|
+
this.templateConfigs.clear();
|
|
363
|
+
this.templates.clear();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Add an event listener
|
|
368
|
+
*
|
|
369
|
+
* @param event - Event name
|
|
370
|
+
* @param handler - Event handler function
|
|
371
|
+
* @returns Function to remove the listener
|
|
372
|
+
*/
|
|
373
|
+
on<K extends keyof EngineEvents>(
|
|
374
|
+
event: K,
|
|
375
|
+
handler: EngineEventHandler<K>,
|
|
376
|
+
): () => void {
|
|
377
|
+
if (!this.eventHandlers.has(event)) {
|
|
378
|
+
this.eventHandlers.set(event, new Set());
|
|
379
|
+
}
|
|
380
|
+
const handlers = this.eventHandlers.get(event);
|
|
381
|
+
handlers?.add(handler as EngineEventHandler<keyof EngineEvents>);
|
|
382
|
+
|
|
383
|
+
return () => {
|
|
384
|
+
handlers?.delete(handler as EngineEventHandler<keyof EngineEvents>);
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Get the underlying Handlebars instance (for advanced usage)
|
|
390
|
+
*
|
|
391
|
+
* @returns Handlebars instance
|
|
392
|
+
*/
|
|
393
|
+
getHandlebars(): typeof Handlebars {
|
|
394
|
+
return this.handlebars;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Get the template loader instance
|
|
399
|
+
*
|
|
400
|
+
* @returns Template loader
|
|
401
|
+
*/
|
|
402
|
+
getLoader(): TemplateLoader {
|
|
403
|
+
return this.loader;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Register all built-in helpers
|
|
408
|
+
*/
|
|
409
|
+
private registerBuiltinHelpers(): void {
|
|
410
|
+
const helpers = getBuiltinHelpers();
|
|
411
|
+
for (const helper of helpers) {
|
|
412
|
+
this.handlebars.registerHelper(helper.name, helper.fn as HelperDelegate);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Register additional Handlebars-style helpers
|
|
416
|
+
this.registerArithmeticHelpers();
|
|
417
|
+
this.registerLogicalHelpers();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Register built-in macros as partials
|
|
422
|
+
*/
|
|
423
|
+
private registerBuiltinMacros(): void {
|
|
424
|
+
const macros = getBuiltinMacros();
|
|
425
|
+
for (const macro of macros) {
|
|
426
|
+
this.macros.set(macro.name, macro);
|
|
427
|
+
this.handlebars.registerPartial(macro.name, macro.template);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Register arithmetic helpers
|
|
433
|
+
*/
|
|
434
|
+
private registerArithmeticHelpers(): void {
|
|
435
|
+
this.handlebars.registerHelper('add', (a: number, b: number) => a + b);
|
|
436
|
+
this.handlebars.registerHelper('subtract', (a: number, b: number) => a - b);
|
|
437
|
+
this.handlebars.registerHelper('multiply', (a: number, b: number) => a * b);
|
|
438
|
+
this.handlebars.registerHelper('divide', (a: number, b: number) =>
|
|
439
|
+
b !== 0 ? a / b : 0,
|
|
440
|
+
);
|
|
441
|
+
this.handlebars.registerHelper('mod', (a: number, b: number) =>
|
|
442
|
+
b !== 0 ? a % b : 0,
|
|
443
|
+
);
|
|
444
|
+
this.handlebars.registerHelper('abs', (a: number) => Math.abs(a));
|
|
445
|
+
this.handlebars.registerHelper('ceil', (a: number) => Math.ceil(a));
|
|
446
|
+
this.handlebars.registerHelper('floor', (a: number) => Math.floor(a));
|
|
447
|
+
this.handlebars.registerHelper('round', (a: number) => Math.round(a));
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Register logical helpers
|
|
452
|
+
*/
|
|
453
|
+
private registerLogicalHelpers(): void {
|
|
454
|
+
this.handlebars.registerHelper(
|
|
455
|
+
'and',
|
|
456
|
+
function (this: unknown, ...args: unknown[]) {
|
|
457
|
+
const options = args.pop() as Handlebars.HelperOptions;
|
|
458
|
+
const values = args;
|
|
459
|
+
const result = values.every(Boolean);
|
|
460
|
+
if (options?.fn) {
|
|
461
|
+
return result
|
|
462
|
+
? options.fn(this)
|
|
463
|
+
: options.inverse
|
|
464
|
+
? options.inverse(this)
|
|
465
|
+
: '';
|
|
466
|
+
}
|
|
467
|
+
return result;
|
|
468
|
+
},
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
this.handlebars.registerHelper(
|
|
472
|
+
'or',
|
|
473
|
+
function (this: unknown, ...args: unknown[]) {
|
|
474
|
+
const options = args.pop() as Handlebars.HelperOptions;
|
|
475
|
+
const values = args;
|
|
476
|
+
const result = values.some(Boolean);
|
|
477
|
+
if (options?.fn) {
|
|
478
|
+
return result
|
|
479
|
+
? options.fn(this)
|
|
480
|
+
: options.inverse
|
|
481
|
+
? options.inverse(this)
|
|
482
|
+
: '';
|
|
483
|
+
}
|
|
484
|
+
return result;
|
|
485
|
+
},
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
this.handlebars.registerHelper('not', (value: unknown) => !value);
|
|
489
|
+
|
|
490
|
+
this.handlebars.registerHelper('eq', (a: unknown, b: unknown) => a === b);
|
|
491
|
+
this.handlebars.registerHelper('ne', (a: unknown, b: unknown) => a !== b);
|
|
492
|
+
this.handlebars.registerHelper('lt', (a: number, b: number) => a < b);
|
|
493
|
+
this.handlebars.registerHelper('lte', (a: number, b: number) => a <= b);
|
|
494
|
+
this.handlebars.registerHelper('gt', (a: number, b: number) => a > b);
|
|
495
|
+
this.handlebars.registerHelper('gte', (a: number, b: number) => a >= b);
|
|
496
|
+
|
|
497
|
+
// Helper to create arrays inline
|
|
498
|
+
this.handlebars.registerHelper('array', (...args: unknown[]) => {
|
|
499
|
+
args.pop(); // Remove options object
|
|
500
|
+
return args;
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
// Helper to create objects inline
|
|
504
|
+
this.handlebars.registerHelper(
|
|
505
|
+
'object',
|
|
506
|
+
(options: Handlebars.HelperOptions) => {
|
|
507
|
+
return options?.hash || {};
|
|
508
|
+
},
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
// Ternary helper
|
|
512
|
+
this.handlebars.registerHelper(
|
|
513
|
+
'ternary',
|
|
514
|
+
(condition: unknown, ifTrue: unknown, ifFalse: unknown) => {
|
|
515
|
+
return condition ? ifTrue : ifFalse;
|
|
516
|
+
},
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
// Default value helper
|
|
520
|
+
this.handlebars.registerHelper(
|
|
521
|
+
'default',
|
|
522
|
+
(value: unknown, defaultValue: unknown) => {
|
|
523
|
+
return value !== undefined && value !== null ? value : defaultValue;
|
|
524
|
+
},
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Get or compile a template
|
|
530
|
+
*/
|
|
531
|
+
private getOrCompileTemplate(template: string): HandlebarsTemplateDelegate {
|
|
532
|
+
// Check if it's a registered template ID
|
|
533
|
+
const cached = this.templates.get(template);
|
|
534
|
+
if (cached) {
|
|
535
|
+
return cached;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Compile as inline template
|
|
539
|
+
return this.handlebars.compile(template);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Prepare context with defaults and system values
|
|
544
|
+
*/
|
|
545
|
+
private prepareContext(context: TemplateContext): Record<string, unknown> {
|
|
546
|
+
const system = {
|
|
547
|
+
timestamp: new Date(),
|
|
548
|
+
...context.system,
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
return {
|
|
552
|
+
...context.variables,
|
|
553
|
+
system,
|
|
554
|
+
memory: context.memory,
|
|
555
|
+
tools: context.tools,
|
|
556
|
+
metadata: context.metadata,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Create a template error from an exception
|
|
562
|
+
*/
|
|
563
|
+
private createTemplateError(
|
|
564
|
+
error: unknown,
|
|
565
|
+
templateId?: string,
|
|
566
|
+
): TemplateError {
|
|
567
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
568
|
+
const errorStack = error instanceof Error ? error.stack : undefined;
|
|
569
|
+
|
|
570
|
+
// Try to parse line/column from Handlebars error
|
|
571
|
+
const lineMatch = errorMessage.match(/line (\d+)/i);
|
|
572
|
+
const columnMatch = errorMessage.match(/column (\d+)/i);
|
|
573
|
+
|
|
574
|
+
return {
|
|
575
|
+
code: 'TEMPLATE_ERROR',
|
|
576
|
+
message: errorMessage,
|
|
577
|
+
line: lineMatch ? parseInt(lineMatch[1], 10) : undefined,
|
|
578
|
+
column: columnMatch ? parseInt(columnMatch[1], 10) : undefined,
|
|
579
|
+
templateId,
|
|
580
|
+
stack: errorStack,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Emit an event to all registered handlers
|
|
586
|
+
*/
|
|
587
|
+
private emit<K extends keyof EngineEvents>(
|
|
588
|
+
event: K,
|
|
589
|
+
data: EngineEvents[K],
|
|
590
|
+
): void {
|
|
591
|
+
const handlers = this.eventHandlers.get(event);
|
|
592
|
+
if (handlers) {
|
|
593
|
+
for (const handler of handlers) {
|
|
594
|
+
try {
|
|
595
|
+
(handler as EngineEventHandler<K>)(data);
|
|
596
|
+
} catch {
|
|
597
|
+
// Ignore handler errors
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Create a new PromptTemplateEngine instance
|
|
606
|
+
*
|
|
607
|
+
* @param options - Default render options
|
|
608
|
+
* @param loaderBaseDir - Base directory for template loading
|
|
609
|
+
* @returns Configured engine instance
|
|
610
|
+
*/
|
|
611
|
+
export function createEngine(
|
|
612
|
+
options?: Partial<RenderOptions>,
|
|
613
|
+
loaderBaseDir?: string,
|
|
614
|
+
): PromptTemplateEngine {
|
|
615
|
+
return new PromptTemplateEngine(options, loaderBaseDir);
|
|
616
|
+
}
|