@riotprompt/riotprompt 0.0.8 → 0.0.10
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/.kodrdriv-test-cache.json +6 -0
- package/BUG-ANALYSIS.md +523 -0
- package/CODE-REVIEW-SUMMARY.md +330 -0
- package/FIXES-APPLIED.md +437 -0
- package/README.md +2 -2
- package/dist/builder.js +3 -0
- package/dist/builder.js.map +1 -1
- package/dist/chat.d.ts +1 -1
- package/dist/chat.js +2 -5
- package/dist/chat.js.map +1 -1
- package/dist/constants.js +1 -2
- package/dist/constants.js.map +1 -1
- package/dist/context-manager.d.ts +136 -0
- package/dist/context-manager.js +243 -0
- package/dist/context-manager.js.map +1 -0
- package/dist/conversation-logger.d.ts +285 -0
- package/dist/conversation-logger.js +491 -0
- package/dist/conversation-logger.js.map +1 -0
- package/dist/conversation.d.ts +277 -0
- package/dist/conversation.js +649 -0
- package/dist/conversation.js.map +1 -0
- package/dist/formatter.js.map +1 -1
- package/dist/items/section.js +3 -3
- package/dist/items/section.js.map +1 -1
- package/dist/iteration-strategy.d.ts +233 -0
- package/dist/iteration-strategy.js +520 -0
- package/dist/iteration-strategy.js.map +1 -0
- package/dist/loader.js +21 -3
- package/dist/loader.js.map +1 -1
- package/dist/message-builder.d.ts +156 -0
- package/dist/message-builder.js +256 -0
- package/dist/message-builder.js.map +1 -0
- package/dist/model-config.d.ts +115 -0
- package/dist/model-config.js +205 -0
- package/dist/model-config.js.map +1 -0
- package/dist/override.js +8 -1
- package/dist/override.js.map +1 -1
- package/dist/parser.js +3 -3
- package/dist/parser.js.map +1 -1
- package/dist/recipes.d.ts +42 -0
- package/dist/recipes.js +189 -4
- package/dist/recipes.js.map +1 -1
- package/dist/reflection.d.ts +250 -0
- package/dist/reflection.js +419 -0
- package/dist/reflection.js.map +1 -0
- package/dist/riotprompt.cjs +3854 -178
- package/dist/riotprompt.cjs.map +1 -1
- package/dist/riotprompt.d.ts +20 -2
- package/dist/riotprompt.js +10 -1
- package/dist/riotprompt.js.map +1 -1
- package/dist/token-budget.d.ts +177 -0
- package/dist/token-budget.js +401 -0
- package/dist/token-budget.js.map +1 -0
- package/dist/tools.d.ts +239 -0
- package/dist/tools.js +324 -0
- package/dist/tools.js.map +1 -0
- package/dist/util/general.js +1 -1
- package/dist/util/general.js.map +1 -1
- package/package.json +23 -20
package/dist/riotprompt.cjs
CHANGED
|
@@ -5,9 +5,10 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
5
5
|
const zod = require('zod');
|
|
6
6
|
const fs = require('fs/promises');
|
|
7
7
|
const path = require('path');
|
|
8
|
+
const crypto = require('crypto');
|
|
9
|
+
const tiktoken = require('tiktoken');
|
|
8
10
|
const fs$1 = require('fs');
|
|
9
11
|
const glob = require('glob');
|
|
10
|
-
const crypto = require('crypto');
|
|
11
12
|
|
|
12
13
|
function _interopNamespaceDefault(e) {
|
|
13
14
|
const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } });
|
|
@@ -127,7 +128,7 @@ const create$8 = (options = {})=>{
|
|
|
127
128
|
};
|
|
128
129
|
if (Array.isArray(item)) {
|
|
129
130
|
item.forEach((item)=>{
|
|
130
|
-
append(item);
|
|
131
|
+
append(item, options); // Propagate options to array items
|
|
131
132
|
});
|
|
132
133
|
} else {
|
|
133
134
|
if (typeof item === 'string') {
|
|
@@ -146,7 +147,7 @@ const create$8 = (options = {})=>{
|
|
|
146
147
|
};
|
|
147
148
|
if (Array.isArray(item)) {
|
|
148
149
|
item.forEach((item)=>{
|
|
149
|
-
prepend(item);
|
|
150
|
+
prepend(item, options); // Propagate options to array items
|
|
150
151
|
});
|
|
151
152
|
} else {
|
|
152
153
|
if (typeof item === 'string') {
|
|
@@ -165,7 +166,7 @@ const create$8 = (options = {})=>{
|
|
|
165
166
|
};
|
|
166
167
|
if (Array.isArray(item)) {
|
|
167
168
|
item.forEach((item)=>{
|
|
168
|
-
insert(index, item);
|
|
169
|
+
insert(index, item, options); // Propagate options to array items
|
|
169
170
|
});
|
|
170
171
|
} else {
|
|
171
172
|
if (typeof item === 'string') {
|
|
@@ -242,7 +243,6 @@ const create$6 = ({ persona, instructions, contents, contexts })=>{
|
|
|
242
243
|
|
|
243
244
|
const DEFAULT_CHARACTER_ENCODING = "utf8";
|
|
244
245
|
const LIBRARY_NAME = "riotprompt";
|
|
245
|
-
const DEFAULT_PERSONA_ROLE = "developer";
|
|
246
246
|
const DEFAULT_IGNORE_PATTERNS = [
|
|
247
247
|
"^\\..*",
|
|
248
248
|
"\\.(jpg|jpeg|png|gif|bmp|svg|webp|ico)$",
|
|
@@ -261,29 +261,6 @@ const DEFAULT_FORMAT_OPTIONS = {
|
|
|
261
261
|
sectionDepth: 0
|
|
262
262
|
};
|
|
263
263
|
|
|
264
|
-
const getPersonaRole = (model)=>{
|
|
265
|
-
if (model === "gpt-4o" || model === "gpt-4o-mini") {
|
|
266
|
-
return "system";
|
|
267
|
-
}
|
|
268
|
-
return DEFAULT_PERSONA_ROLE;
|
|
269
|
-
};
|
|
270
|
-
const createRequest = (model)=>{
|
|
271
|
-
const messages = [];
|
|
272
|
-
return {
|
|
273
|
-
model,
|
|
274
|
-
messages,
|
|
275
|
-
addMessage: (message)=>{
|
|
276
|
-
messages.push(message);
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
const chat = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
|
282
|
-
__proto__: null,
|
|
283
|
-
createRequest,
|
|
284
|
-
getPersonaRole
|
|
285
|
-
}, Symbol.toStringTag, { value: 'Module' }));
|
|
286
|
-
|
|
287
264
|
const DEFAULT_LOGGER = {
|
|
288
265
|
name: 'default',
|
|
289
266
|
debug: (message, ...args)=>console.debug(message, ...args),
|
|
@@ -328,6 +305,227 @@ const wrapLogger = (toWrap, name)=>{
|
|
|
328
305
|
};
|
|
329
306
|
};
|
|
330
307
|
|
|
308
|
+
function _define_property$8(obj, key, value) {
|
|
309
|
+
if (key in obj) {
|
|
310
|
+
Object.defineProperty(obj, key, {
|
|
311
|
+
value: value,
|
|
312
|
+
enumerable: true,
|
|
313
|
+
configurable: true,
|
|
314
|
+
writable: true
|
|
315
|
+
});
|
|
316
|
+
} else {
|
|
317
|
+
obj[key] = value;
|
|
318
|
+
}
|
|
319
|
+
return obj;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Model registry for managing model configurations
|
|
323
|
+
*/ class ModelRegistry {
|
|
324
|
+
/**
|
|
325
|
+
* Register default model configurations
|
|
326
|
+
*/ registerDefaults() {
|
|
327
|
+
// GPT-4 family (uses 'system' role)
|
|
328
|
+
this.register({
|
|
329
|
+
pattern: /^gpt-4/i,
|
|
330
|
+
personaRole: 'system',
|
|
331
|
+
encoding: 'gpt-4o',
|
|
332
|
+
supportsToolCalls: true,
|
|
333
|
+
family: 'gpt-4',
|
|
334
|
+
description: 'GPT-4 family models'
|
|
335
|
+
});
|
|
336
|
+
// O-series models (uses 'developer' role)
|
|
337
|
+
this.register({
|
|
338
|
+
pattern: /^o\d+/i,
|
|
339
|
+
personaRole: 'developer',
|
|
340
|
+
encoding: 'gpt-4o',
|
|
341
|
+
supportsToolCalls: true,
|
|
342
|
+
family: 'o-series',
|
|
343
|
+
description: 'O-series reasoning models'
|
|
344
|
+
});
|
|
345
|
+
// Claude family (uses 'system' role)
|
|
346
|
+
this.register({
|
|
347
|
+
pattern: /^claude/i,
|
|
348
|
+
personaRole: 'system',
|
|
349
|
+
encoding: 'cl100k_base',
|
|
350
|
+
supportsToolCalls: true,
|
|
351
|
+
family: 'claude',
|
|
352
|
+
description: 'Claude family models'
|
|
353
|
+
});
|
|
354
|
+
// Default fallback
|
|
355
|
+
this.register({
|
|
356
|
+
pattern: /.*/,
|
|
357
|
+
personaRole: 'system',
|
|
358
|
+
encoding: 'gpt-4o',
|
|
359
|
+
supportsToolCalls: true,
|
|
360
|
+
family: 'unknown',
|
|
361
|
+
description: 'Default fallback configuration'
|
|
362
|
+
});
|
|
363
|
+
this.logger.debug('Registered default model configurations');
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Register a model configuration
|
|
367
|
+
* Configs are checked in registration order (first match wins)
|
|
368
|
+
*/ register(config) {
|
|
369
|
+
var _config_pattern;
|
|
370
|
+
// Validate config
|
|
371
|
+
if (!config.pattern && !config.exactMatch) {
|
|
372
|
+
throw new Error('Model config must have either pattern or exactMatch');
|
|
373
|
+
}
|
|
374
|
+
this.configs.push(config);
|
|
375
|
+
this.cache.clear(); // Clear cache when new config is added
|
|
376
|
+
this.logger.debug('Registered model config', {
|
|
377
|
+
family: config.family,
|
|
378
|
+
pattern: (_config_pattern = config.pattern) === null || _config_pattern === void 0 ? void 0 : _config_pattern.source,
|
|
379
|
+
exactMatch: config.exactMatch
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Get configuration for a model
|
|
384
|
+
*/ getConfig(model) {
|
|
385
|
+
// Check cache first
|
|
386
|
+
if (this.cache.has(model)) {
|
|
387
|
+
return this.cache.get(model);
|
|
388
|
+
}
|
|
389
|
+
// Find matching config (first match wins)
|
|
390
|
+
for (const config of this.configs){
|
|
391
|
+
if (config.exactMatch && config.exactMatch === model) {
|
|
392
|
+
this.cache.set(model, config);
|
|
393
|
+
return config;
|
|
394
|
+
}
|
|
395
|
+
if (config.pattern && config.pattern.test(model)) {
|
|
396
|
+
this.cache.set(model, config);
|
|
397
|
+
return config;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// Should never happen due to default fallback, but just in case
|
|
401
|
+
throw new Error(`No configuration found for model: ${model}`);
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Get persona role for a model
|
|
405
|
+
*/ getPersonaRole(model) {
|
|
406
|
+
return this.getConfig(model).personaRole;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Get tokenizer encoding for a model
|
|
410
|
+
*/ getEncoding(model) {
|
|
411
|
+
return this.getConfig(model).encoding;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Check if model supports tool calls
|
|
415
|
+
*/ supportsToolCalls(model) {
|
|
416
|
+
var _this_getConfig_supportsToolCalls;
|
|
417
|
+
return (_this_getConfig_supportsToolCalls = this.getConfig(model).supportsToolCalls) !== null && _this_getConfig_supportsToolCalls !== void 0 ? _this_getConfig_supportsToolCalls : true;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Get model family
|
|
421
|
+
*/ getFamily(model) {
|
|
422
|
+
return this.getConfig(model).family;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Clear all registered configs and reset to defaults
|
|
426
|
+
*/ reset() {
|
|
427
|
+
this.configs = [];
|
|
428
|
+
this.cache.clear();
|
|
429
|
+
this.registerDefaults();
|
|
430
|
+
this.logger.debug('Reset model configurations to defaults');
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Clear cache (useful if configs are modified)
|
|
434
|
+
*/ clearCache() {
|
|
435
|
+
this.cache.clear();
|
|
436
|
+
this.logger.debug('Cleared model configuration cache');
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Get all registered configurations
|
|
440
|
+
*/ getAllConfigs() {
|
|
441
|
+
return [
|
|
442
|
+
...this.configs
|
|
443
|
+
];
|
|
444
|
+
}
|
|
445
|
+
constructor(logger){
|
|
446
|
+
_define_property$8(this, "configs", void 0);
|
|
447
|
+
_define_property$8(this, "cache", void 0);
|
|
448
|
+
_define_property$8(this, "logger", void 0);
|
|
449
|
+
this.configs = [];
|
|
450
|
+
this.cache = new Map();
|
|
451
|
+
this.logger = wrapLogger(logger || DEFAULT_LOGGER, 'ModelRegistry');
|
|
452
|
+
// Register default configurations
|
|
453
|
+
this.registerDefaults();
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
// Global registry instance
|
|
457
|
+
let globalRegistry = null;
|
|
458
|
+
/**
|
|
459
|
+
* Get the global model registry
|
|
460
|
+
*/ function getModelRegistry(logger) {
|
|
461
|
+
if (!globalRegistry) {
|
|
462
|
+
globalRegistry = new ModelRegistry(logger);
|
|
463
|
+
}
|
|
464
|
+
return globalRegistry;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Reset the global registry (useful for testing)
|
|
468
|
+
*/ function resetModelRegistry() {
|
|
469
|
+
globalRegistry = null;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Helper functions using global registry
|
|
473
|
+
*/ function getPersonaRole$1(model) {
|
|
474
|
+
return getModelRegistry().getPersonaRole(model);
|
|
475
|
+
}
|
|
476
|
+
function getEncoding(model) {
|
|
477
|
+
return getModelRegistry().getEncoding(model);
|
|
478
|
+
}
|
|
479
|
+
function supportsToolCalls(model) {
|
|
480
|
+
return getModelRegistry().supportsToolCalls(model);
|
|
481
|
+
}
|
|
482
|
+
function getModelFamily(model) {
|
|
483
|
+
return getModelRegistry().getFamily(model);
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Configure a custom model
|
|
487
|
+
*
|
|
488
|
+
* @example
|
|
489
|
+
* ```typescript
|
|
490
|
+
* // Add support for a new model family
|
|
491
|
+
* configureModel({
|
|
492
|
+
* pattern: /^gemini/i,
|
|
493
|
+
* personaRole: 'system',
|
|
494
|
+
* encoding: 'cl100k_base',
|
|
495
|
+
* family: 'gemini'
|
|
496
|
+
* });
|
|
497
|
+
*
|
|
498
|
+
* // Add specific model override
|
|
499
|
+
* configureModel({
|
|
500
|
+
* exactMatch: 'custom-model-v1',
|
|
501
|
+
* personaRole: 'developer',
|
|
502
|
+
* encoding: 'gpt-4o'
|
|
503
|
+
* });
|
|
504
|
+
* ```
|
|
505
|
+
*/ function configureModel(config) {
|
|
506
|
+
getModelRegistry().register(config);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const getPersonaRole = (model)=>{
|
|
510
|
+
return getPersonaRole$1(model);
|
|
511
|
+
};
|
|
512
|
+
const createRequest = (model)=>{
|
|
513
|
+
const messages = [];
|
|
514
|
+
return {
|
|
515
|
+
model,
|
|
516
|
+
messages,
|
|
517
|
+
addMessage: (message)=>{
|
|
518
|
+
messages.push(message);
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
const chat = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
|
524
|
+
__proto__: null,
|
|
525
|
+
createRequest,
|
|
526
|
+
getPersonaRole
|
|
527
|
+
}, Symbol.toStringTag, { value: 'Module' }));
|
|
528
|
+
|
|
331
529
|
const clean = (obj)=>{
|
|
332
530
|
return Object.fromEntries(Object.entries(obj).filter(([_, v])=>v !== undefined));
|
|
333
531
|
};
|
|
@@ -342,7 +540,7 @@ const stringifyJSON = function(obj, visited = new Set()) {
|
|
|
342
540
|
return '"(circular)"';
|
|
343
541
|
} else if (Array.isArray(obj)) {
|
|
344
542
|
//check for empty array
|
|
345
|
-
if (obj
|
|
543
|
+
if (obj.length === 0) return '[]';
|
|
346
544
|
else {
|
|
347
545
|
// Add array to visited before processing its elements
|
|
348
546
|
visited.add(obj);
|
|
@@ -806,13 +1004,13 @@ const create$4 = (parserOptions)=>{
|
|
|
806
1004
|
});
|
|
807
1005
|
} catch (error) {
|
|
808
1006
|
// Log the error or handle it appropriately
|
|
809
|
-
logger.error(`Error reading or parsing file
|
|
810
|
-
throw new Error(`Failed to parse
|
|
1007
|
+
logger.error(`Error reading or parsing file at ${filePath}:`, error);
|
|
1008
|
+
throw new Error(`Failed to parse content from ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
811
1009
|
}
|
|
812
1010
|
};
|
|
813
1011
|
/**
|
|
814
1012
|
* Reads Markdown content and parses it into a single Section.
|
|
815
|
-
*
|
|
1013
|
+
*
|
|
816
1014
|
* - If the content starts with a heading, that becomes the title of the returned Section
|
|
817
1015
|
* - If no heading at the start, creates a Section with no title
|
|
818
1016
|
* - Headers within the content create nested sections based on their depth
|
|
@@ -1016,7 +1214,7 @@ const create$2 = (loaderOptions)=>{
|
|
|
1016
1214
|
};
|
|
1017
1215
|
/**
|
|
1018
1216
|
* Loads context from the provided directories and returns instruction sections
|
|
1019
|
-
*
|
|
1217
|
+
*
|
|
1020
1218
|
* @param contextDirectories Directories containing context files
|
|
1021
1219
|
* @returns Array of instruction sections loaded from context directories
|
|
1022
1220
|
*/ const load = async (contextDirectories = [], options = {})=>{
|
|
@@ -1068,8 +1266,22 @@ const create$2 = (loaderOptions)=>{
|
|
|
1068
1266
|
}
|
|
1069
1267
|
// Get all other files in the directory
|
|
1070
1268
|
const files = await storage.listFiles(contextDir);
|
|
1071
|
-
const ignorePatternsRegex = ignorePatterns.map((pattern)=>
|
|
1072
|
-
|
|
1269
|
+
const ignorePatternsRegex = ignorePatterns.map((pattern)=>{
|
|
1270
|
+
try {
|
|
1271
|
+
return new RegExp(pattern, 'i');
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
logger.error(`Invalid ignore pattern: ${pattern}`, {
|
|
1274
|
+
error
|
|
1275
|
+
});
|
|
1276
|
+
// Return a pattern that matches nothing
|
|
1277
|
+
return /(?!)/; // Negative lookahead that always fails
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
const filteredFiles = files.filter((file)=>{
|
|
1281
|
+
const fullPath = path.join(contextDir, file);
|
|
1282
|
+
// Test against both filename and full path for flexibility
|
|
1283
|
+
return !ignorePatternsRegex.some((regex)=>regex.test(file) || regex.test(fullPath));
|
|
1284
|
+
});
|
|
1073
1285
|
for (const file of filteredFiles){
|
|
1074
1286
|
// Skip the context.md file as it's already processed
|
|
1075
1287
|
if (file === 'context.md') continue;
|
|
@@ -1097,6 +1309,7 @@ const create$2 = (loaderOptions)=>{
|
|
|
1097
1309
|
...sectionOptions
|
|
1098
1310
|
});
|
|
1099
1311
|
// Add this file section to the main context section
|
|
1312
|
+
// Type is correct - Section.add() accepts Section<T>
|
|
1100
1313
|
mainContextSection.add(fileSection, {
|
|
1101
1314
|
...sectionOptions
|
|
1102
1315
|
});
|
|
@@ -1212,7 +1425,11 @@ const create$1 = (overrideOptions = {})=>{
|
|
|
1212
1425
|
finalSection = finalSection.prepend(prepend);
|
|
1213
1426
|
}
|
|
1214
1427
|
// Apply appends in reverse order (furthest layers first, then closest)
|
|
1215
|
-
|
|
1428
|
+
// Create a copy to avoid mutating the original array
|
|
1429
|
+
const reversedAppends = [
|
|
1430
|
+
...appends
|
|
1431
|
+
].reverse();
|
|
1432
|
+
for (const append of reversedAppends){
|
|
1216
1433
|
logger.silly('Append found, adding to content from file %s', append);
|
|
1217
1434
|
finalSection = finalSection.append(append);
|
|
1218
1435
|
}
|
|
@@ -1376,153 +1593,3522 @@ const builder = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
|
|
1376
1593
|
create
|
|
1377
1594
|
}, Symbol.toStringTag, { value: 'Module' }));
|
|
1378
1595
|
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
zod.z.object({
|
|
1393
|
-
directories: zod.z.array(zod.z.string()),
|
|
1394
|
-
title: zod.z.string().optional(),
|
|
1395
|
-
weight: zod.z.number().optional()
|
|
1396
|
-
})
|
|
1397
|
-
]);
|
|
1398
|
-
const RecipeConfigSchema = zod.z.object({
|
|
1399
|
-
// Core settings
|
|
1400
|
-
basePath: zod.z.string(),
|
|
1401
|
-
logger: zod.z.any().optional().default(DEFAULT_LOGGER),
|
|
1402
|
-
overridePaths: zod.z.array(zod.z.string()).optional().default([
|
|
1403
|
-
"./"
|
|
1404
|
-
]),
|
|
1405
|
-
overrides: zod.z.boolean().optional().default(false),
|
|
1406
|
-
parameters: ParametersSchema.optional().default({}),
|
|
1407
|
-
// Content sections
|
|
1408
|
-
persona: ContentItemSchema.optional(),
|
|
1409
|
-
instructions: zod.z.array(ContentItemSchema).optional().default([]),
|
|
1410
|
-
content: zod.z.array(ContentItemSchema).optional().default([]),
|
|
1411
|
-
context: zod.z.array(ContentItemSchema).optional().default([]),
|
|
1412
|
-
// Templates and inheritance
|
|
1413
|
-
extends: zod.z.string().optional(),
|
|
1414
|
-
template: zod.z.string().optional()
|
|
1415
|
-
});
|
|
1416
|
-
// User-customizable template registry
|
|
1417
|
-
let TEMPLATES = {};
|
|
1596
|
+
function _define_property$7(obj, key, value) {
|
|
1597
|
+
if (key in obj) {
|
|
1598
|
+
Object.defineProperty(obj, key, {
|
|
1599
|
+
value: value,
|
|
1600
|
+
enumerable: true,
|
|
1601
|
+
configurable: true,
|
|
1602
|
+
writable: true
|
|
1603
|
+
});
|
|
1604
|
+
} else {
|
|
1605
|
+
obj[key] = value;
|
|
1606
|
+
}
|
|
1607
|
+
return obj;
|
|
1608
|
+
}
|
|
1418
1609
|
/**
|
|
1419
|
-
*
|
|
1420
|
-
*
|
|
1610
|
+
* ContextManager tracks and manages dynamically injected context.
|
|
1611
|
+
*
|
|
1612
|
+
* Features:
|
|
1613
|
+
* - Track all injected context with metadata
|
|
1614
|
+
* - Deduplication by ID, hash, or content
|
|
1615
|
+
* - Category-based organization
|
|
1616
|
+
* - Query context state
|
|
1617
|
+
* - Context statistics
|
|
1618
|
+
*
|
|
1421
1619
|
* @example
|
|
1422
1620
|
* ```typescript
|
|
1423
|
-
*
|
|
1424
|
-
*
|
|
1425
|
-
*
|
|
1426
|
-
*
|
|
1427
|
-
*
|
|
1428
|
-
*
|
|
1429
|
-
*
|
|
1430
|
-
*
|
|
1431
|
-
*
|
|
1432
|
-
*
|
|
1433
|
-
*
|
|
1621
|
+
* const manager = new ContextManager();
|
|
1622
|
+
*
|
|
1623
|
+
* // Track injected context
|
|
1624
|
+
* manager.track({
|
|
1625
|
+
* id: 'file:main.ts',
|
|
1626
|
+
* content: fileContent,
|
|
1627
|
+
* title: 'Main File',
|
|
1628
|
+
* category: 'source-code'
|
|
1629
|
+
* }, 5);
|
|
1630
|
+
*
|
|
1631
|
+
* // Check for duplicates
|
|
1632
|
+
* if (manager.hasContext('file:main.ts')) {
|
|
1633
|
+
* console.log('Already provided');
|
|
1634
|
+
* }
|
|
1635
|
+
*
|
|
1636
|
+
* // Query by category
|
|
1637
|
+
* const sourceFiles = manager.getByCategory('source-code');
|
|
1434
1638
|
* ```
|
|
1435
|
-
*/
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
/**
|
|
1447
|
-
* Clear all registered templates
|
|
1448
|
-
*/ const clearTemplates = ()=>{
|
|
1449
|
-
TEMPLATES = {};
|
|
1450
|
-
};
|
|
1451
|
-
// ===== CORE RECIPE ENGINE =====
|
|
1452
|
-
const cook = async (config)=>{
|
|
1453
|
-
// Parse and validate configuration with defaults
|
|
1454
|
-
const validatedConfig = RecipeConfigSchema.parse({
|
|
1455
|
-
overridePaths: [
|
|
1456
|
-
"./"
|
|
1457
|
-
],
|
|
1458
|
-
overrides: false,
|
|
1459
|
-
parameters: {},
|
|
1460
|
-
instructions: [],
|
|
1461
|
-
content: [],
|
|
1462
|
-
context: [],
|
|
1463
|
-
...config
|
|
1464
|
-
});
|
|
1465
|
-
// Handle template inheritance
|
|
1466
|
-
let finalConfig = {
|
|
1467
|
-
...validatedConfig
|
|
1468
|
-
};
|
|
1469
|
-
if (validatedConfig.template) {
|
|
1470
|
-
const template = TEMPLATES[validatedConfig.template];
|
|
1471
|
-
if (template) {
|
|
1472
|
-
finalConfig = {
|
|
1473
|
-
...validatedConfig,
|
|
1474
|
-
persona: validatedConfig.persona || template.persona,
|
|
1475
|
-
instructions: [
|
|
1476
|
-
...template.instructions || [],
|
|
1477
|
-
...validatedConfig.instructions || []
|
|
1478
|
-
],
|
|
1479
|
-
content: [
|
|
1480
|
-
...template.content || [],
|
|
1481
|
-
...validatedConfig.content || []
|
|
1482
|
-
],
|
|
1483
|
-
context: [
|
|
1484
|
-
...template.context || [],
|
|
1485
|
-
...validatedConfig.context || []
|
|
1486
|
-
]
|
|
1487
|
-
};
|
|
1639
|
+
*/ class ContextManager {
|
|
1640
|
+
/**
|
|
1641
|
+
* Track a context item (with deduplication by content hash for items without ID)
|
|
1642
|
+
*/ track(item, position) {
|
|
1643
|
+
const hash = this.hashContent(item.content);
|
|
1644
|
+
// If item has no ID and we already have this content hash, skip tracking
|
|
1645
|
+
if (!item.id && this.hashes.has(hash)) {
|
|
1646
|
+
this.logger.debug('Skipping duplicate context item by hash', {
|
|
1647
|
+
hash
|
|
1648
|
+
});
|
|
1649
|
+
return;
|
|
1488
1650
|
}
|
|
1651
|
+
const id = item.id || this.generateId();
|
|
1652
|
+
const trackedItem = {
|
|
1653
|
+
...item,
|
|
1654
|
+
id,
|
|
1655
|
+
hash,
|
|
1656
|
+
position,
|
|
1657
|
+
injectedAt: new Date(),
|
|
1658
|
+
timestamp: item.timestamp || new Date(),
|
|
1659
|
+
priority: item.priority || 'medium'
|
|
1660
|
+
};
|
|
1661
|
+
this.items.set(id, trackedItem);
|
|
1662
|
+
this.hashes.add(hash);
|
|
1663
|
+
this.logger.debug('Tracked context item', {
|
|
1664
|
+
id,
|
|
1665
|
+
category: item.category,
|
|
1666
|
+
position
|
|
1667
|
+
});
|
|
1489
1668
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1669
|
+
/**
|
|
1670
|
+
* Check if context with given ID exists
|
|
1671
|
+
*/ hasContext(id) {
|
|
1672
|
+
return this.items.has(id);
|
|
1673
|
+
}
|
|
1674
|
+
/**
|
|
1675
|
+
* Check if content with given hash exists
|
|
1676
|
+
*/ hasContentHash(content) {
|
|
1677
|
+
const hash = this.hashContent(content);
|
|
1678
|
+
return this.hashes.has(hash);
|
|
1679
|
+
}
|
|
1680
|
+
/**
|
|
1681
|
+
* Check if similar content exists (fuzzy match)
|
|
1682
|
+
* Uses similarity threshold to avoid overly aggressive deduplication
|
|
1683
|
+
*/ hasSimilarContent(content, similarityThreshold = 0.9) {
|
|
1684
|
+
// Warn if checking against a large number of items (performance consideration)
|
|
1685
|
+
const MAX_ITEMS_WARNING = 1000;
|
|
1686
|
+
if (this.items.size > MAX_ITEMS_WARNING) {
|
|
1687
|
+
this.logger.warn('Large number of context items, similarity check may be slow', {
|
|
1688
|
+
count: this.items.size,
|
|
1689
|
+
threshold: MAX_ITEMS_WARNING
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
const normalized = this.normalizeContent(content);
|
|
1693
|
+
for (const item of this.items.values()){
|
|
1694
|
+
const itemNormalized = this.normalizeContent(item.content || '');
|
|
1695
|
+
// Exact match
|
|
1696
|
+
if (normalized === itemNormalized) {
|
|
1697
|
+
return true;
|
|
1698
|
+
}
|
|
1699
|
+
// Calculate similarity ratio (Jaccard-like)
|
|
1700
|
+
const longer = normalized.length > itemNormalized.length ? normalized : itemNormalized;
|
|
1701
|
+
const shorter = normalized.length <= itemNormalized.length ? normalized : itemNormalized;
|
|
1702
|
+
// Only consider substring match if the shorter is at least 90% of longer
|
|
1703
|
+
const lengthRatio = shorter.length / longer.length;
|
|
1704
|
+
if (lengthRatio >= similarityThreshold) {
|
|
1705
|
+
// Check if one is contained in the other
|
|
1706
|
+
if (longer.includes(shorter)) {
|
|
1707
|
+
return true;
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
return false;
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Get context item by ID
|
|
1715
|
+
*/ get(id) {
|
|
1716
|
+
return this.items.get(id);
|
|
1717
|
+
}
|
|
1718
|
+
/**
|
|
1719
|
+
* Get all tracked context items
|
|
1720
|
+
*/ getAll() {
|
|
1721
|
+
return Array.from(this.items.values());
|
|
1722
|
+
}
|
|
1723
|
+
/**
|
|
1724
|
+
* Get context items by category
|
|
1725
|
+
*/ getByCategory(category) {
|
|
1726
|
+
return this.getAll().filter((item)=>item.category === category);
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Get context items by priority
|
|
1730
|
+
*/ getByPriority(priority) {
|
|
1731
|
+
return this.getAll().filter((item)=>item.priority === priority);
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* Get context items by source
|
|
1735
|
+
*/ getBySource(source) {
|
|
1736
|
+
return this.getAll().filter((item)=>item.source === source);
|
|
1737
|
+
}
|
|
1738
|
+
/**
|
|
1739
|
+
* Get all categories
|
|
1740
|
+
*/ getCategories() {
|
|
1741
|
+
const categories = new Set();
|
|
1742
|
+
this.items.forEach((item)=>{
|
|
1743
|
+
if (item.category) {
|
|
1744
|
+
categories.add(item.category);
|
|
1745
|
+
}
|
|
1746
|
+
});
|
|
1747
|
+
return Array.from(categories).sort();
|
|
1748
|
+
}
|
|
1749
|
+
/**
|
|
1750
|
+
* Get context statistics
|
|
1751
|
+
*/ getStats() {
|
|
1752
|
+
const byCategory = new Map();
|
|
1753
|
+
const byPriority = new Map();
|
|
1754
|
+
const bySource = new Map();
|
|
1755
|
+
let oldestTimestamp;
|
|
1756
|
+
let newestTimestamp;
|
|
1757
|
+
this.items.forEach((item)=>{
|
|
1758
|
+
// Category stats
|
|
1759
|
+
if (item.category) {
|
|
1760
|
+
byCategory.set(item.category, (byCategory.get(item.category) || 0) + 1);
|
|
1761
|
+
}
|
|
1762
|
+
// Priority stats
|
|
1763
|
+
const priority = item.priority || 'medium';
|
|
1764
|
+
byPriority.set(priority, (byPriority.get(priority) || 0) + 1);
|
|
1765
|
+
// Source stats
|
|
1766
|
+
if (item.source) {
|
|
1767
|
+
bySource.set(item.source, (bySource.get(item.source) || 0) + 1);
|
|
1768
|
+
}
|
|
1769
|
+
// Timestamp stats
|
|
1770
|
+
if (item.timestamp) {
|
|
1771
|
+
if (!oldestTimestamp || item.timestamp < oldestTimestamp) {
|
|
1772
|
+
oldestTimestamp = item.timestamp;
|
|
1773
|
+
}
|
|
1774
|
+
if (!newestTimestamp || item.timestamp > newestTimestamp) {
|
|
1775
|
+
newestTimestamp = item.timestamp;
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
});
|
|
1779
|
+
return {
|
|
1780
|
+
totalItems: this.items.size,
|
|
1781
|
+
byCategory,
|
|
1782
|
+
byPriority,
|
|
1783
|
+
bySource,
|
|
1784
|
+
oldestTimestamp,
|
|
1785
|
+
newestTimestamp
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Remove context item by ID
|
|
1790
|
+
*/ remove(id) {
|
|
1791
|
+
const item = this.items.get(id);
|
|
1792
|
+
if (item) {
|
|
1793
|
+
this.items.delete(id);
|
|
1794
|
+
this.hashes.delete(item.hash);
|
|
1795
|
+
this.logger.debug('Removed context item', {
|
|
1796
|
+
id
|
|
1797
|
+
});
|
|
1798
|
+
return true;
|
|
1799
|
+
}
|
|
1800
|
+
return false;
|
|
1801
|
+
}
|
|
1802
|
+
/**
|
|
1803
|
+
* Clear all tracked context
|
|
1804
|
+
*/ clear() {
|
|
1805
|
+
this.items.clear();
|
|
1806
|
+
this.hashes.clear();
|
|
1807
|
+
this.logger.debug('Cleared all context');
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
* Generate unique ID for context item
|
|
1811
|
+
*/ generateId() {
|
|
1812
|
+
return `ctx-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
1813
|
+
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Hash content for deduplication
|
|
1816
|
+
*/ hashContent(content) {
|
|
1817
|
+
return crypto.createHash('sha256').update(content).digest('hex').substring(0, 16);
|
|
1818
|
+
}
|
|
1819
|
+
/**
|
|
1820
|
+
* Normalize content for comparison
|
|
1821
|
+
*/ normalizeContent(content) {
|
|
1822
|
+
return content.replace(/\s+/g, ' ').trim().toLowerCase();
|
|
1823
|
+
}
|
|
1824
|
+
constructor(logger){
|
|
1825
|
+
_define_property$7(this, "items", void 0);
|
|
1826
|
+
_define_property$7(this, "hashes", void 0);
|
|
1827
|
+
_define_property$7(this, "logger", void 0);
|
|
1828
|
+
this.items = new Map();
|
|
1829
|
+
this.hashes = new Set();
|
|
1830
|
+
this.logger = wrapLogger(logger || DEFAULT_LOGGER, 'ContextManager');
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
function _define_property$6(obj, key, value) {
|
|
1835
|
+
if (key in obj) {
|
|
1836
|
+
Object.defineProperty(obj, key, {
|
|
1837
|
+
value: value,
|
|
1838
|
+
enumerable: true,
|
|
1839
|
+
configurable: true,
|
|
1840
|
+
writable: true
|
|
1841
|
+
});
|
|
1842
|
+
} else {
|
|
1843
|
+
obj[key] = value;
|
|
1844
|
+
}
|
|
1845
|
+
return obj;
|
|
1846
|
+
}
|
|
1847
|
+
// ===== CONVERSATION LOGGER =====
|
|
1848
|
+
/**
|
|
1849
|
+
* ConversationLogger logs conversations to various formats.
|
|
1850
|
+
*
|
|
1851
|
+
* Features:
|
|
1852
|
+
* - Multiple formats (JSON, Markdown, JSONL)
|
|
1853
|
+
* - Automatic timestamping
|
|
1854
|
+
* - Metadata tracking
|
|
1855
|
+
* - Sensitive data redaction
|
|
1856
|
+
* - Streaming support (JSONL)
|
|
1857
|
+
*
|
|
1858
|
+
* @example
|
|
1859
|
+
* ```typescript
|
|
1860
|
+
* const logger = new ConversationLogger({
|
|
1861
|
+
* enabled: true,
|
|
1862
|
+
* outputPath: 'logs/conversations',
|
|
1863
|
+
* format: 'json',
|
|
1864
|
+
* includeMetadata: true
|
|
1865
|
+
* });
|
|
1866
|
+
*
|
|
1867
|
+
* logger.onConversationStart({ model: 'gpt-4o', startTime: new Date() });
|
|
1868
|
+
* logger.onMessageAdded(message);
|
|
1869
|
+
* const path = await logger.save();
|
|
1870
|
+
* ```
|
|
1871
|
+
*/ class ConversationLogger {
|
|
1872
|
+
/**
|
|
1873
|
+
* Start conversation logging
|
|
1874
|
+
*/ onConversationStart(metadata) {
|
|
1875
|
+
this.metadata = {
|
|
1876
|
+
...this.metadata,
|
|
1877
|
+
...metadata,
|
|
1878
|
+
startTime: this.startTime
|
|
1879
|
+
};
|
|
1880
|
+
// Reset cached output path to prevent file collision if logger is reused
|
|
1881
|
+
this.cachedOutputPath = undefined;
|
|
1882
|
+
this.logger.debug('Conversation logging started', {
|
|
1883
|
+
id: this.conversationId
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
/**
|
|
1887
|
+
* Log a message
|
|
1888
|
+
*/ onMessageAdded(message, metadata) {
|
|
1889
|
+
let content = message.content;
|
|
1890
|
+
// Redact sensitive data if enabled
|
|
1891
|
+
if (this.config.redactSensitive && content && typeof content === 'string') {
|
|
1892
|
+
content = this.redactContent(content);
|
|
1893
|
+
}
|
|
1894
|
+
const loggedMessage = {
|
|
1895
|
+
index: this.messageIndex++,
|
|
1896
|
+
timestamp: new Date().toISOString(),
|
|
1897
|
+
role: message.role,
|
|
1898
|
+
content,
|
|
1899
|
+
tool_calls: message.tool_calls,
|
|
1900
|
+
tool_call_id: message.tool_call_id,
|
|
1901
|
+
metadata
|
|
1902
|
+
};
|
|
1903
|
+
this.messages.push(loggedMessage);
|
|
1904
|
+
// For JSONL format, append immediately with write queue
|
|
1905
|
+
if (this.config.format === 'jsonl') {
|
|
1906
|
+
this.writeQueue = this.writeQueue.then(()=>this.appendToJSONL(loggedMessage)).catch((error)=>{
|
|
1907
|
+
this.logger.error('Failed to write JSONL message', {
|
|
1908
|
+
error
|
|
1909
|
+
});
|
|
1910
|
+
try {
|
|
1911
|
+
var _this_config_onError, _this_config;
|
|
1912
|
+
(_this_config_onError = (_this_config = this.config).onError) === null || _this_config_onError === void 0 ? void 0 : _this_config_onError.call(_this_config, error);
|
|
1913
|
+
} catch (callbackError) {
|
|
1914
|
+
this.logger.error('onError callback failed', {
|
|
1915
|
+
callbackError
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
/**
|
|
1922
|
+
* Log a tool call
|
|
1923
|
+
*/ onToolCall(callId, toolName, iteration, args, result, duration, success, error) {
|
|
1924
|
+
this.toolCalls.push({
|
|
1925
|
+
callId,
|
|
1926
|
+
toolName,
|
|
1927
|
+
timestamp: new Date().toISOString(),
|
|
1928
|
+
iteration,
|
|
1929
|
+
arguments: args,
|
|
1930
|
+
result,
|
|
1931
|
+
duration,
|
|
1932
|
+
success,
|
|
1933
|
+
error
|
|
1934
|
+
});
|
|
1935
|
+
}
|
|
1936
|
+
/**
|
|
1937
|
+
* End conversation logging
|
|
1938
|
+
*/ onConversationEnd(_summary) {
|
|
1939
|
+
this.metadata.endTime = new Date();
|
|
1940
|
+
this.metadata.duration = this.metadata.endTime.getTime() - this.startTime.getTime();
|
|
1941
|
+
this.logger.debug('Conversation logging ended', {
|
|
1942
|
+
messages: this.messages.length,
|
|
1943
|
+
duration: this.metadata.duration
|
|
1944
|
+
});
|
|
1945
|
+
}
|
|
1946
|
+
/**
|
|
1947
|
+
* Save conversation to disk
|
|
1948
|
+
*/ async save() {
|
|
1949
|
+
if (!this.config.enabled) {
|
|
1950
|
+
return '';
|
|
1951
|
+
}
|
|
1952
|
+
try {
|
|
1953
|
+
const outputPath = await this.getOutputPath();
|
|
1954
|
+
switch(this.config.format){
|
|
1955
|
+
case 'json':
|
|
1956
|
+
await this.saveAsJSON(outputPath);
|
|
1957
|
+
break;
|
|
1958
|
+
case 'markdown':
|
|
1959
|
+
await this.saveAsMarkdown(outputPath);
|
|
1960
|
+
break;
|
|
1961
|
+
case 'jsonl':
|
|
1962
|
+
break;
|
|
1963
|
+
}
|
|
1964
|
+
this.config.onSaved(outputPath);
|
|
1965
|
+
this.logger.info('Conversation saved', {
|
|
1966
|
+
path: outputPath
|
|
1967
|
+
});
|
|
1968
|
+
return outputPath;
|
|
1969
|
+
} catch (error) {
|
|
1970
|
+
this.config.onError(error);
|
|
1971
|
+
this.logger.error('Failed to save conversation', {
|
|
1972
|
+
error
|
|
1973
|
+
});
|
|
1974
|
+
throw error;
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
/**
|
|
1978
|
+
* Get logged conversation object
|
|
1979
|
+
*/ getConversation() {
|
|
1980
|
+
return {
|
|
1981
|
+
id: this.conversationId,
|
|
1982
|
+
metadata: this.metadata,
|
|
1983
|
+
messages: this.messages,
|
|
1984
|
+
summary: {
|
|
1985
|
+
totalMessages: this.messages.length,
|
|
1986
|
+
toolCallsExecuted: this.toolCalls.length,
|
|
1987
|
+
iterations: 0,
|
|
1988
|
+
success: true
|
|
1989
|
+
}
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Generate unique conversation ID
|
|
1994
|
+
*/ generateId() {
|
|
1995
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
1996
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
1997
|
+
return `conv-${timestamp}-${random}`;
|
|
1998
|
+
}
|
|
1999
|
+
/**
|
|
2000
|
+
* Get output file path (cached for JSONL to avoid recalculation)
|
|
2001
|
+
*/ async getOutputPath() {
|
|
2002
|
+
if (this.cachedOutputPath) {
|
|
2003
|
+
return this.cachedOutputPath;
|
|
2004
|
+
}
|
|
2005
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
2006
|
+
const filename = this.config.filenameTemplate.replace('{timestamp}', timestamp).replace('{id}', this.conversationId).replace('{template}', this.metadata.template || 'default');
|
|
2007
|
+
const ext = this.config.format === 'markdown' ? '.md' : '.json';
|
|
2008
|
+
const fullPath = path.join(this.config.outputPath, filename + ext);
|
|
2009
|
+
// Ensure directory exists
|
|
2010
|
+
await fs.mkdir(path.dirname(fullPath), {
|
|
2011
|
+
recursive: true
|
|
2012
|
+
});
|
|
2013
|
+
// Cache path for JSONL format to ensure consistent file writes
|
|
2014
|
+
if (this.config.format === 'jsonl') {
|
|
2015
|
+
this.cachedOutputPath = fullPath;
|
|
2016
|
+
}
|
|
2017
|
+
return fullPath;
|
|
2018
|
+
}
|
|
2019
|
+
/**
|
|
2020
|
+
* Save as JSON
|
|
2021
|
+
*/ async saveAsJSON(outputPath) {
|
|
2022
|
+
const data = {
|
|
2023
|
+
id: this.conversationId,
|
|
2024
|
+
metadata: this.metadata,
|
|
2025
|
+
messages: this.messages,
|
|
2026
|
+
summary: {
|
|
2027
|
+
totalMessages: this.messages.length,
|
|
2028
|
+
toolCallsExecuted: this.toolCalls.length,
|
|
2029
|
+
iterations: 0,
|
|
2030
|
+
success: true
|
|
2031
|
+
}
|
|
2032
|
+
};
|
|
2033
|
+
await fs.writeFile(outputPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
2034
|
+
}
|
|
2035
|
+
/**
|
|
2036
|
+
* Save as Markdown
|
|
2037
|
+
*/ async saveAsMarkdown(outputPath) {
|
|
2038
|
+
let markdown = `# Conversation Log\n\n`;
|
|
2039
|
+
markdown += `**ID**: ${this.conversationId}\n`;
|
|
2040
|
+
markdown += `**Started**: ${this.metadata.startTime.toISOString()}\n`;
|
|
2041
|
+
if (this.metadata.duration) {
|
|
2042
|
+
markdown += `**Duration**: ${(this.metadata.duration / 1000).toFixed(1)}s\n`;
|
|
2043
|
+
}
|
|
2044
|
+
markdown += `**Model**: ${this.metadata.model}\n`;
|
|
2045
|
+
if (this.metadata.template) {
|
|
2046
|
+
markdown += `**Template**: ${this.metadata.template}\n`;
|
|
2047
|
+
}
|
|
2048
|
+
markdown += `\n## Conversation\n\n`;
|
|
2049
|
+
for (const msg of this.messages){
|
|
2050
|
+
const time = new Date(msg.timestamp).toLocaleTimeString();
|
|
2051
|
+
markdown += `### Message ${msg.index + 1} (${time}) - ${msg.role}\n\n`;
|
|
2052
|
+
if (msg.content) {
|
|
2053
|
+
markdown += `\`\`\`\n${msg.content}\n\`\`\`\n\n`;
|
|
2054
|
+
}
|
|
2055
|
+
if (msg.tool_calls) {
|
|
2056
|
+
markdown += `**Tool Calls:**\n`;
|
|
2057
|
+
for (const call of msg.tool_calls){
|
|
2058
|
+
markdown += `- ${call.function.name}: \`${call.function.arguments}\`\n`;
|
|
2059
|
+
}
|
|
2060
|
+
markdown += `\n`;
|
|
2061
|
+
}
|
|
2062
|
+
if (msg.metadata) {
|
|
2063
|
+
markdown += `*Metadata: ${JSON.stringify(msg.metadata)}*\n\n`;
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
markdown += `## Summary\n\n`;
|
|
2067
|
+
markdown += `- **Total Messages**: ${this.messages.length}\n`;
|
|
2068
|
+
markdown += `- **Tool Calls**: ${this.toolCalls.length}\n`;
|
|
2069
|
+
await fs.writeFile(outputPath, markdown, 'utf-8');
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Append to JSONL file (streaming)
|
|
2073
|
+
*/ async appendToJSONL(message) {
|
|
2074
|
+
const outputPath = await this.getOutputPath();
|
|
2075
|
+
const line = JSON.stringify(message) + '\n';
|
|
2076
|
+
await fs.appendFile(outputPath, line, 'utf-8');
|
|
2077
|
+
}
|
|
2078
|
+
/**
|
|
2079
|
+
* Redact sensitive content
|
|
2080
|
+
*/ redactContent(content) {
|
|
2081
|
+
let redacted = content;
|
|
2082
|
+
// Apply custom patterns
|
|
2083
|
+
for (const pattern of this.config.redactPatterns){
|
|
2084
|
+
redacted = redacted.replace(pattern, '[REDACTED]');
|
|
2085
|
+
}
|
|
2086
|
+
// Default patterns
|
|
2087
|
+
const defaultPatterns = [
|
|
2088
|
+
/api[_-]?key[\s:="']+[\w-]+/gi,
|
|
2089
|
+
/password[\s:="']+[\w-]+/gi,
|
|
2090
|
+
/Bearer\s+[\w-]+/gi,
|
|
2091
|
+
/sk-[a-zA-Z0-9]{48}/g
|
|
2092
|
+
];
|
|
2093
|
+
for (const pattern of defaultPatterns){
|
|
2094
|
+
redacted = redacted.replace(pattern, '[REDACTED]');
|
|
2095
|
+
}
|
|
2096
|
+
return redacted;
|
|
2097
|
+
}
|
|
2098
|
+
constructor(config, logger){
|
|
2099
|
+
_define_property$6(this, "config", void 0);
|
|
2100
|
+
_define_property$6(this, "conversationId", void 0);
|
|
2101
|
+
_define_property$6(this, "metadata", void 0);
|
|
2102
|
+
_define_property$6(this, "messages", void 0);
|
|
2103
|
+
_define_property$6(this, "toolCalls", void 0);
|
|
2104
|
+
_define_property$6(this, "startTime", void 0);
|
|
2105
|
+
_define_property$6(this, "logger", void 0);
|
|
2106
|
+
_define_property$6(this, "messageIndex", void 0);
|
|
2107
|
+
_define_property$6(this, "cachedOutputPath", void 0);
|
|
2108
|
+
_define_property$6(this, "writeQueue", Promise.resolve());
|
|
2109
|
+
this.config = {
|
|
2110
|
+
outputPath: 'logs/conversations',
|
|
2111
|
+
format: 'json',
|
|
2112
|
+
filenameTemplate: 'conversation-{timestamp}',
|
|
2113
|
+
includeMetadata: true,
|
|
2114
|
+
includePrompt: false,
|
|
2115
|
+
redactSensitive: false,
|
|
2116
|
+
redactPatterns: [],
|
|
2117
|
+
onSaved: ()=>{},
|
|
2118
|
+
onError: ()=>{},
|
|
2119
|
+
...config
|
|
2120
|
+
};
|
|
2121
|
+
this.conversationId = this.generateId();
|
|
2122
|
+
this.messages = [];
|
|
2123
|
+
this.toolCalls = [];
|
|
2124
|
+
this.startTime = new Date();
|
|
2125
|
+
this.messageIndex = 0;
|
|
2126
|
+
this.logger = wrapLogger(logger || DEFAULT_LOGGER, 'ConversationLogger');
|
|
2127
|
+
this.metadata = {
|
|
2128
|
+
startTime: this.startTime,
|
|
2129
|
+
model: 'unknown'
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
/**
|
|
2134
|
+
* ConversationReplayer loads and replays logged conversations.
|
|
2135
|
+
*
|
|
2136
|
+
* Features:
|
|
2137
|
+
* - Load from various formats
|
|
2138
|
+
* - Replay conversations
|
|
2139
|
+
* - Compare replays with originals
|
|
2140
|
+
* - Export to different formats
|
|
2141
|
+
*
|
|
2142
|
+
* @example
|
|
2143
|
+
* ```typescript
|
|
2144
|
+
* const replayer = await ConversationReplayer.load('logs/conv.json');
|
|
2145
|
+
*
|
|
2146
|
+
* console.log('Messages:', replayer.messages.length);
|
|
2147
|
+
* console.log('Tool calls:', replayer.getToolCalls().length);
|
|
2148
|
+
*
|
|
2149
|
+
* const timeline = replayer.getTimeline();
|
|
2150
|
+
* console.log('Events:', timeline.length);
|
|
2151
|
+
* ```
|
|
2152
|
+
*/ class ConversationReplayer {
|
|
2153
|
+
/**
|
|
2154
|
+
* Load conversation from file
|
|
2155
|
+
*/ static async load(filePath, logger) {
|
|
2156
|
+
const wlogger = wrapLogger(logger || DEFAULT_LOGGER, 'ConversationReplayer');
|
|
2157
|
+
wlogger.debug('Loading conversation', {
|
|
2158
|
+
path: filePath
|
|
2159
|
+
});
|
|
2160
|
+
try {
|
|
2161
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
2162
|
+
// Determine format by extension
|
|
2163
|
+
if (filePath.endsWith('.json')) {
|
|
2164
|
+
const data = JSON.parse(content);
|
|
2165
|
+
return new ConversationReplayer(data, logger);
|
|
2166
|
+
} else if (filePath.endsWith('.jsonl')) {
|
|
2167
|
+
const lines = content.trim().split('\n');
|
|
2168
|
+
const messages = lines.map((line)=>JSON.parse(line));
|
|
2169
|
+
const conversation = {
|
|
2170
|
+
id: `replayer-${Date.now()}`,
|
|
2171
|
+
metadata: {
|
|
2172
|
+
startTime: new Date(),
|
|
2173
|
+
model: 'unknown'
|
|
2174
|
+
},
|
|
2175
|
+
messages,
|
|
2176
|
+
summary: {
|
|
2177
|
+
totalMessages: messages.length,
|
|
2178
|
+
toolCallsExecuted: 0,
|
|
2179
|
+
iterations: 0,
|
|
2180
|
+
success: true
|
|
2181
|
+
}
|
|
2182
|
+
};
|
|
2183
|
+
return new ConversationReplayer(conversation, logger);
|
|
2184
|
+
} else {
|
|
2185
|
+
throw new Error(`Unsupported format: ${filePath}`);
|
|
2186
|
+
}
|
|
2187
|
+
} catch (error) {
|
|
2188
|
+
wlogger.error('Failed to load conversation', {
|
|
2189
|
+
path: filePath,
|
|
2190
|
+
error
|
|
2191
|
+
});
|
|
2192
|
+
throw error;
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
/**
|
|
2196
|
+
* Load latest conversation from directory
|
|
2197
|
+
*/ static async loadLatest(directory, logger) {
|
|
2198
|
+
const files = await fs.readdir(directory);
|
|
2199
|
+
const jsonFiles = files.filter((f)=>f.endsWith('.json')).sort().reverse();
|
|
2200
|
+
if (jsonFiles.length === 0) {
|
|
2201
|
+
throw new Error(`No conversation logs found in ${directory}`);
|
|
2202
|
+
}
|
|
2203
|
+
const latestPath = path.join(directory, jsonFiles[0]);
|
|
2204
|
+
return ConversationReplayer.load(latestPath, logger);
|
|
2205
|
+
}
|
|
2206
|
+
/**
|
|
2207
|
+
* Get all messages
|
|
2208
|
+
*/ get messages() {
|
|
2209
|
+
return this.conversation.messages;
|
|
2210
|
+
}
|
|
2211
|
+
/**
|
|
2212
|
+
* Get conversation metadata
|
|
2213
|
+
*/ getMetadata() {
|
|
2214
|
+
return {
|
|
2215
|
+
...this.conversation.metadata
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
/**
|
|
2219
|
+
* Get tool calls
|
|
2220
|
+
*/ getToolCalls() {
|
|
2221
|
+
const toolCalls = [];
|
|
2222
|
+
for (const msg of this.conversation.messages){
|
|
2223
|
+
if (msg.tool_calls) {
|
|
2224
|
+
for (const call of msg.tool_calls){
|
|
2225
|
+
// Parse arguments with error handling
|
|
2226
|
+
let parsedArgs;
|
|
2227
|
+
try {
|
|
2228
|
+
parsedArgs = JSON.parse(call.function.arguments);
|
|
2229
|
+
} catch (error) {
|
|
2230
|
+
this.logger.warn('Failed to parse tool call arguments', {
|
|
2231
|
+
callId: call.id,
|
|
2232
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2233
|
+
});
|
|
2234
|
+
parsedArgs = {
|
|
2235
|
+
__parse_error: true,
|
|
2236
|
+
raw: call.function.arguments
|
|
2237
|
+
};
|
|
2238
|
+
}
|
|
2239
|
+
toolCalls.push({
|
|
2240
|
+
callId: call.id,
|
|
2241
|
+
toolName: call.function.name,
|
|
2242
|
+
timestamp: msg.timestamp,
|
|
2243
|
+
iteration: 0,
|
|
2244
|
+
arguments: parsedArgs,
|
|
2245
|
+
result: null,
|
|
2246
|
+
duration: 0,
|
|
2247
|
+
success: true
|
|
2248
|
+
});
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
return toolCalls;
|
|
2253
|
+
}
|
|
2254
|
+
/**
|
|
2255
|
+
* Get message at index
|
|
2256
|
+
*/ getMessageAt(index) {
|
|
2257
|
+
return this.conversation.messages[index];
|
|
2258
|
+
}
|
|
2259
|
+
/**
|
|
2260
|
+
* Get timeline of events
|
|
2261
|
+
*/ getTimeline() {
|
|
2262
|
+
const events = [];
|
|
2263
|
+
for (const msg of this.conversation.messages){
|
|
2264
|
+
events.push({
|
|
2265
|
+
timestamp: msg.timestamp,
|
|
2266
|
+
iteration: 0,
|
|
2267
|
+
type: 'message',
|
|
2268
|
+
description: `${msg.role} message`
|
|
2269
|
+
});
|
|
2270
|
+
}
|
|
2271
|
+
return events;
|
|
2272
|
+
}
|
|
2273
|
+
/**
|
|
2274
|
+
* Export to format
|
|
2275
|
+
*/ async exportToFormat(format, outputPath) {
|
|
2276
|
+
this.logger.debug('Exporting to format', {
|
|
2277
|
+
format,
|
|
2278
|
+
path: outputPath
|
|
2279
|
+
});
|
|
2280
|
+
switch(format){
|
|
2281
|
+
case 'json':
|
|
2282
|
+
await fs.writeFile(outputPath, JSON.stringify(this.conversation, null, 2), 'utf-8');
|
|
2283
|
+
break;
|
|
2284
|
+
case 'markdown':
|
|
2285
|
+
await this.exportMarkdown(outputPath);
|
|
2286
|
+
break;
|
|
2287
|
+
case 'jsonl':
|
|
2288
|
+
{
|
|
2289
|
+
const lines = this.messages.map((m)=>JSON.stringify(m)).join('\n');
|
|
2290
|
+
await fs.writeFile(outputPath, lines, 'utf-8');
|
|
2291
|
+
break;
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
return outputPath;
|
|
2295
|
+
}
|
|
2296
|
+
/**
|
|
2297
|
+
* Export as markdown
|
|
2298
|
+
*/ async exportMarkdown(outputPath) {
|
|
2299
|
+
let markdown = `# Conversation Log\n\n`;
|
|
2300
|
+
markdown += `**ID**: ${this.conversation.id}\n`;
|
|
2301
|
+
const startTime = typeof this.conversation.metadata.startTime === 'string' ? this.conversation.metadata.startTime : this.conversation.metadata.startTime.toISOString();
|
|
2302
|
+
markdown += `**Started**: ${startTime}\n\n`;
|
|
2303
|
+
for (const msg of this.conversation.messages){
|
|
2304
|
+
markdown += `## ${msg.role.toUpperCase()} (${msg.index})\n\n`;
|
|
2305
|
+
if (msg.content) {
|
|
2306
|
+
markdown += `${msg.content}\n\n`;
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
await fs.writeFile(outputPath, markdown, 'utf-8');
|
|
2310
|
+
}
|
|
2311
|
+
constructor(conversation, logger){
|
|
2312
|
+
_define_property$6(this, "conversation", void 0);
|
|
2313
|
+
_define_property$6(this, "logger", void 0);
|
|
2314
|
+
this.conversation = conversation;
|
|
2315
|
+
this.logger = wrapLogger(logger || DEFAULT_LOGGER, 'ConversationReplayer');
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
function _define_property$5(obj, key, value) {
|
|
2320
|
+
if (key in obj) {
|
|
2321
|
+
Object.defineProperty(obj, key, {
|
|
2322
|
+
value: value,
|
|
2323
|
+
enumerable: true,
|
|
2324
|
+
configurable: true,
|
|
2325
|
+
writable: true
|
|
2326
|
+
});
|
|
2327
|
+
} else {
|
|
2328
|
+
obj[key] = value;
|
|
2329
|
+
}
|
|
2330
|
+
return obj;
|
|
2331
|
+
}
|
|
2332
|
+
/**
|
|
2333
|
+
* MessageBuilder provides semantic, type-safe message construction.
|
|
2334
|
+
*
|
|
2335
|
+
* Features:
|
|
2336
|
+
* - Semantic message types (system, user, assistant, tool)
|
|
2337
|
+
* - Model-specific role handling (system vs developer)
|
|
2338
|
+
* - Structured content composition
|
|
2339
|
+
* - Metadata attachment
|
|
2340
|
+
* - Format-aware building
|
|
2341
|
+
*
|
|
2342
|
+
* @example
|
|
2343
|
+
* ```typescript
|
|
2344
|
+
* const message = MessageBuilder.system()
|
|
2345
|
+
* .withContent('You are a helpful assistant')
|
|
2346
|
+
* .withInstructions(instructionSection)
|
|
2347
|
+
* .buildForModel('gpt-4o');
|
|
2348
|
+
*
|
|
2349
|
+
* const toolMessage = MessageBuilder.tool('call_123')
|
|
2350
|
+
* .withResult(result)
|
|
2351
|
+
* .withMetadata({ duration: 45 })
|
|
2352
|
+
* .build();
|
|
2353
|
+
* ```
|
|
2354
|
+
*/ class MessageBuilder {
|
|
2355
|
+
/**
|
|
2356
|
+
* Create system message builder
|
|
2357
|
+
*/ static system(logger) {
|
|
2358
|
+
return new MessageBuilder('system', logger);
|
|
2359
|
+
}
|
|
2360
|
+
/**
|
|
2361
|
+
* Create user message builder
|
|
2362
|
+
*/ static user(logger) {
|
|
2363
|
+
return new MessageBuilder('user', logger);
|
|
2364
|
+
}
|
|
2365
|
+
/**
|
|
2366
|
+
* Create assistant message builder
|
|
2367
|
+
*/ static assistant(logger) {
|
|
2368
|
+
return new MessageBuilder('assistant', logger);
|
|
2369
|
+
}
|
|
2370
|
+
/**
|
|
2371
|
+
* Create tool message builder
|
|
2372
|
+
*/ static tool(callId, logger) {
|
|
2373
|
+
const builder = new MessageBuilder('tool', logger);
|
|
2374
|
+
builder.toolCallId = callId;
|
|
2375
|
+
return builder;
|
|
2376
|
+
}
|
|
2377
|
+
/**
|
|
2378
|
+
* Create developer message builder (for o1 models)
|
|
2379
|
+
*/ static developer(logger) {
|
|
2380
|
+
return new MessageBuilder('developer', logger);
|
|
2381
|
+
}
|
|
2382
|
+
/**
|
|
2383
|
+
* Add content to message
|
|
2384
|
+
*/ withContent(content) {
|
|
2385
|
+
if (typeof content === 'string') {
|
|
2386
|
+
this.contentParts.push(content);
|
|
2387
|
+
} else {
|
|
2388
|
+
// Format section
|
|
2389
|
+
const formatter$1 = this.formatter || create$5();
|
|
2390
|
+
this.contentParts.push(formatter$1.format(content));
|
|
2391
|
+
}
|
|
2392
|
+
return this;
|
|
2393
|
+
}
|
|
2394
|
+
/**
|
|
2395
|
+
* Add persona section (typically for system messages)
|
|
2396
|
+
*/ withPersona(persona) {
|
|
2397
|
+
const formatter$1 = this.formatter || create$5();
|
|
2398
|
+
this.contentParts.push(formatter$1.format(persona));
|
|
2399
|
+
return this;
|
|
2400
|
+
}
|
|
2401
|
+
/**
|
|
2402
|
+
* Add instructions section
|
|
2403
|
+
*/ withInstructions(instructions) {
|
|
2404
|
+
if (Array.isArray(instructions)) {
|
|
2405
|
+
this.contentParts.push(instructions.join('\n'));
|
|
2406
|
+
} else {
|
|
2407
|
+
const formatter$1 = this.formatter || create$5();
|
|
2408
|
+
this.contentParts.push(formatter$1.format(instructions));
|
|
2409
|
+
}
|
|
2410
|
+
return this;
|
|
2411
|
+
}
|
|
2412
|
+
/**
|
|
2413
|
+
* Add context section
|
|
2414
|
+
*/ withContext(context) {
|
|
2415
|
+
if (Array.isArray(context)) {
|
|
2416
|
+
const contextStr = context.map((c)=>c.title ? `## ${c.title}\n\n${c.content}` : c.content).join('\n\n');
|
|
2417
|
+
this.contentParts.push(contextStr);
|
|
2418
|
+
} else {
|
|
2419
|
+
const formatter$1 = this.formatter || create$5();
|
|
2420
|
+
this.contentParts.push(formatter$1.format(context));
|
|
2421
|
+
}
|
|
2422
|
+
return this;
|
|
2423
|
+
}
|
|
2424
|
+
/**
|
|
2425
|
+
* Set tool call ID (for tool messages)
|
|
2426
|
+
*/ withCallId(id) {
|
|
2427
|
+
this.toolCallId = id;
|
|
2428
|
+
return this;
|
|
2429
|
+
}
|
|
2430
|
+
/**
|
|
2431
|
+
* Set tool result (for tool messages)
|
|
2432
|
+
*/ withResult(result) {
|
|
2433
|
+
const resultStr = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
2434
|
+
this.contentParts.push(resultStr);
|
|
2435
|
+
return this;
|
|
2436
|
+
}
|
|
2437
|
+
/**
|
|
2438
|
+
* Add tool calls (for assistant messages)
|
|
2439
|
+
*/ withToolCalls(calls) {
|
|
2440
|
+
this.toolCalls = calls;
|
|
2441
|
+
return this;
|
|
2442
|
+
}
|
|
2443
|
+
/**
|
|
2444
|
+
* Add metadata
|
|
2445
|
+
*/ withMetadata(metadata) {
|
|
2446
|
+
this.metadata = {
|
|
2447
|
+
...this.metadata,
|
|
2448
|
+
...metadata
|
|
2449
|
+
};
|
|
2450
|
+
return this;
|
|
2451
|
+
}
|
|
2452
|
+
/**
|
|
2453
|
+
* Add timestamp to metadata
|
|
2454
|
+
*/ withTimestamp() {
|
|
2455
|
+
this.metadata.timestamp = new Date();
|
|
2456
|
+
return this;
|
|
2457
|
+
}
|
|
2458
|
+
/**
|
|
2459
|
+
* Set priority in metadata
|
|
2460
|
+
*/ withPriority(priority) {
|
|
2461
|
+
this.metadata.priority = priority;
|
|
2462
|
+
return this;
|
|
2463
|
+
}
|
|
2464
|
+
/**
|
|
2465
|
+
* Set formatter for section rendering
|
|
2466
|
+
*/ withFormatter(formatter) {
|
|
2467
|
+
this.formatter = formatter;
|
|
2468
|
+
return this;
|
|
2469
|
+
}
|
|
2470
|
+
/**
|
|
2471
|
+
* Build message with semantic role
|
|
2472
|
+
*/ build() {
|
|
2473
|
+
const content = this.contentParts.join('\n\n');
|
|
2474
|
+
const message = {
|
|
2475
|
+
role: this.semanticRole,
|
|
2476
|
+
content: content || null
|
|
2477
|
+
};
|
|
2478
|
+
// Add tool-specific fields
|
|
2479
|
+
if (this.semanticRole === 'tool' && this.toolCallId) {
|
|
2480
|
+
message.tool_call_id = this.toolCallId;
|
|
2481
|
+
}
|
|
2482
|
+
if (this.toolCalls) {
|
|
2483
|
+
message.tool_calls = this.toolCalls;
|
|
2484
|
+
}
|
|
2485
|
+
return message;
|
|
2486
|
+
}
|
|
2487
|
+
/**
|
|
2488
|
+
* Build message with model-specific role
|
|
2489
|
+
*/ buildForModel(model) {
|
|
2490
|
+
const message = this.build();
|
|
2491
|
+
// Handle model-specific role requirements
|
|
2492
|
+
if (this.semanticRole === 'system') {
|
|
2493
|
+
// Use model registry to determine correct role
|
|
2494
|
+
const personaRole = getPersonaRole$1(model);
|
|
2495
|
+
if (personaRole === 'developer') {
|
|
2496
|
+
message.role = 'developer';
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
return message;
|
|
2500
|
+
}
|
|
2501
|
+
constructor(role, logger){
|
|
2502
|
+
_define_property$5(this, "semanticRole", void 0);
|
|
2503
|
+
_define_property$5(this, "contentParts", void 0);
|
|
2504
|
+
_define_property$5(this, "metadata", void 0);
|
|
2505
|
+
_define_property$5(this, "formatter", void 0);
|
|
2506
|
+
_define_property$5(this, "toolCallId", void 0);
|
|
2507
|
+
_define_property$5(this, "toolCalls", void 0);
|
|
2508
|
+
_define_property$5(this, "logger", void 0);
|
|
2509
|
+
this.semanticRole = role;
|
|
2510
|
+
this.contentParts = [];
|
|
2511
|
+
this.metadata = {};
|
|
2512
|
+
this.logger = wrapLogger(logger || DEFAULT_LOGGER, 'MessageBuilder');
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
/**
|
|
2516
|
+
* Message template functions for common patterns
|
|
2517
|
+
*/ const MessageTemplates = {
|
|
2518
|
+
/**
|
|
2519
|
+
* System message for agentic tasks
|
|
2520
|
+
*/ agenticSystem: (persona, instructions)=>{
|
|
2521
|
+
const builder = MessageBuilder.system();
|
|
2522
|
+
if (persona) {
|
|
2523
|
+
builder.withContent(persona);
|
|
2524
|
+
}
|
|
2525
|
+
if (instructions) {
|
|
2526
|
+
builder.withInstructions(instructions);
|
|
2527
|
+
}
|
|
2528
|
+
return builder;
|
|
2529
|
+
},
|
|
2530
|
+
/**
|
|
2531
|
+
* User query with optional context
|
|
2532
|
+
*/ userQuery: (query, context)=>{
|
|
2533
|
+
const builder = MessageBuilder.user().withContent(query);
|
|
2534
|
+
if (context) {
|
|
2535
|
+
builder.withContext(context);
|
|
2536
|
+
}
|
|
2537
|
+
return builder;
|
|
2538
|
+
},
|
|
2539
|
+
/**
|
|
2540
|
+
* Tool result with metadata
|
|
2541
|
+
*/ toolResult: (callId, result, metadata)=>{
|
|
2542
|
+
const builder = MessageBuilder.tool(callId).withResult(result).withTimestamp();
|
|
2543
|
+
if (metadata) {
|
|
2544
|
+
builder.withMetadata(metadata);
|
|
2545
|
+
}
|
|
2546
|
+
return builder;
|
|
2547
|
+
},
|
|
2548
|
+
/**
|
|
2549
|
+
* Tool success result
|
|
2550
|
+
*/ toolSuccess: (callId, result, duration)=>{
|
|
2551
|
+
return MessageBuilder.tool(callId).withResult(result).withMetadata({
|
|
2552
|
+
success: true,
|
|
2553
|
+
duration
|
|
2554
|
+
}).withTimestamp();
|
|
2555
|
+
},
|
|
2556
|
+
/**
|
|
2557
|
+
* Tool failure result
|
|
2558
|
+
*/ toolFailure: (callId, error)=>{
|
|
2559
|
+
return MessageBuilder.tool(callId).withResult({
|
|
2560
|
+
error: error.message,
|
|
2561
|
+
stack: error.stack
|
|
2562
|
+
}).withMetadata({
|
|
2563
|
+
success: false,
|
|
2564
|
+
errorName: error.name
|
|
2565
|
+
}).withTimestamp();
|
|
2566
|
+
}
|
|
2567
|
+
};
|
|
2568
|
+
|
|
2569
|
+
function _define_property$4(obj, key, value) {
|
|
2570
|
+
if (key in obj) {
|
|
2571
|
+
Object.defineProperty(obj, key, {
|
|
2572
|
+
value: value,
|
|
2573
|
+
enumerable: true,
|
|
2574
|
+
configurable: true,
|
|
2575
|
+
writable: true
|
|
2576
|
+
});
|
|
2577
|
+
} else {
|
|
2578
|
+
obj[key] = value;
|
|
2579
|
+
}
|
|
2580
|
+
return obj;
|
|
2581
|
+
}
|
|
2582
|
+
// ===== TOKEN COUNTER =====
|
|
2583
|
+
/**
|
|
2584
|
+
* TokenCounter counts tokens using tiktoken for accurate model-specific counting.
|
|
2585
|
+
*
|
|
2586
|
+
* Features:
|
|
2587
|
+
* - Model-specific token counting
|
|
2588
|
+
* - Message overhead calculation
|
|
2589
|
+
* - Tool call token estimation
|
|
2590
|
+
* - Response token estimation
|
|
2591
|
+
*
|
|
2592
|
+
* @example
|
|
2593
|
+
* ```typescript
|
|
2594
|
+
* const counter = new TokenCounter('gpt-4o');
|
|
2595
|
+
*
|
|
2596
|
+
* const tokens = counter.count('Hello, world!');
|
|
2597
|
+
* console.log(`Text uses ${tokens} tokens`);
|
|
2598
|
+
*
|
|
2599
|
+
* const messageTokens = counter.countMessage({
|
|
2600
|
+
* role: 'user',
|
|
2601
|
+
* content: 'What is the weather?'
|
|
2602
|
+
* });
|
|
2603
|
+
* ```
|
|
2604
|
+
*/ class TokenCounter {
|
|
2605
|
+
/**
|
|
2606
|
+
* Count tokens in text
|
|
2607
|
+
*/ count(text) {
|
|
2608
|
+
if (!text) return 0;
|
|
2609
|
+
return this.encoder.encode(text).length;
|
|
2610
|
+
}
|
|
2611
|
+
/**
|
|
2612
|
+
* Count tokens in a single message
|
|
2613
|
+
*/ countMessage(message) {
|
|
2614
|
+
let tokens = 4; // Base overhead per message
|
|
2615
|
+
// Content tokens
|
|
2616
|
+
if (message.content) {
|
|
2617
|
+
tokens += this.count(message.content);
|
|
2618
|
+
}
|
|
2619
|
+
// Role tokens
|
|
2620
|
+
tokens += 1;
|
|
2621
|
+
// Tool call tokens
|
|
2622
|
+
if (message.tool_calls) {
|
|
2623
|
+
for (const toolCall of message.tool_calls){
|
|
2624
|
+
tokens += this.count(JSON.stringify(toolCall));
|
|
2625
|
+
tokens += 3; // Tool call overhead
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
// Tool result tokens
|
|
2629
|
+
if (message.tool_call_id) {
|
|
2630
|
+
tokens += this.count(message.tool_call_id);
|
|
2631
|
+
tokens += 2; // Tool result overhead
|
|
2632
|
+
}
|
|
2633
|
+
return tokens;
|
|
2634
|
+
}
|
|
2635
|
+
/**
|
|
2636
|
+
* Count tokens in entire conversation
|
|
2637
|
+
*/ countConversation(messages) {
|
|
2638
|
+
let total = 3; // Conversation start overhead
|
|
2639
|
+
for (const message of messages){
|
|
2640
|
+
total += this.countMessage(message);
|
|
2641
|
+
}
|
|
2642
|
+
return total;
|
|
2643
|
+
}
|
|
2644
|
+
/**
|
|
2645
|
+
* Count with additional overhead estimation
|
|
2646
|
+
*/ countWithOverhead(messages, includeToolOverhead = false) {
|
|
2647
|
+
let total = this.countConversation(messages);
|
|
2648
|
+
// Add tool definition overhead if tools are present
|
|
2649
|
+
if (includeToolOverhead) {
|
|
2650
|
+
const hasTools = messages.some((m)=>m.tool_calls && m.tool_calls.length > 0);
|
|
2651
|
+
if (hasTools) {
|
|
2652
|
+
total += 100; // Estimated tool definition overhead
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
return total;
|
|
2656
|
+
}
|
|
2657
|
+
/**
|
|
2658
|
+
* Estimate tokens needed for response
|
|
2659
|
+
*/ estimateResponseTokens(messages) {
|
|
2660
|
+
// Heuristic: average response is about 20% of input
|
|
2661
|
+
const inputTokens = this.countConversation(messages);
|
|
2662
|
+
return Math.max(500, Math.floor(inputTokens * 0.2));
|
|
2663
|
+
}
|
|
2664
|
+
/**
|
|
2665
|
+
* Map RiotPrompt model to Tiktoken model using model registry
|
|
2666
|
+
*/ mapToTiktokenModel(model) {
|
|
2667
|
+
const encoding = getEncoding(model);
|
|
2668
|
+
// Map our encoding types to tiktoken models
|
|
2669
|
+
switch(encoding){
|
|
2670
|
+
case 'gpt-4o':
|
|
2671
|
+
case 'o200k_base':
|
|
2672
|
+
return 'gpt-4o';
|
|
2673
|
+
case 'cl100k_base':
|
|
2674
|
+
return 'gpt-3.5-turbo';
|
|
2675
|
+
default:
|
|
2676
|
+
return 'gpt-4o';
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
/**
|
|
2680
|
+
* Free encoder resources
|
|
2681
|
+
*/ dispose() {
|
|
2682
|
+
this.encoder.free();
|
|
2683
|
+
}
|
|
2684
|
+
constructor(model, logger){
|
|
2685
|
+
_define_property$4(this, "encoder", void 0);
|
|
2686
|
+
_define_property$4(this, "model", void 0);
|
|
2687
|
+
_define_property$4(this, "logger", void 0);
|
|
2688
|
+
this.model = model;
|
|
2689
|
+
this.logger = wrapLogger(logger || DEFAULT_LOGGER, 'TokenCounter');
|
|
2690
|
+
// Map RiotPrompt models to Tiktoken models
|
|
2691
|
+
const tiktokenModel = this.mapToTiktokenModel(model);
|
|
2692
|
+
this.encoder = tiktoken.encoding_for_model(tiktokenModel);
|
|
2693
|
+
this.logger.debug('Created TokenCounter', {
|
|
2694
|
+
model
|
|
2695
|
+
});
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
// ===== TOKEN BUDGET MANAGER =====
|
|
2699
|
+
/**
|
|
2700
|
+
* TokenBudgetManager manages token budgets and compression strategies.
|
|
2701
|
+
*
|
|
2702
|
+
* Features:
|
|
2703
|
+
* - Monitor token usage
|
|
2704
|
+
* - Automatic compression when budget exceeded
|
|
2705
|
+
* - Multiple compression strategies
|
|
2706
|
+
* - Priority-based message retention
|
|
2707
|
+
* - Usage statistics and callbacks
|
|
2708
|
+
*
|
|
2709
|
+
* @example
|
|
2710
|
+
* ```typescript
|
|
2711
|
+
* const manager = new TokenBudgetManager({
|
|
2712
|
+
* max: 8000,
|
|
2713
|
+
* reserveForResponse: 1000,
|
|
2714
|
+
* strategy: 'priority-based',
|
|
2715
|
+
* onBudgetExceeded: 'compress'
|
|
2716
|
+
* }, 'gpt-4o');
|
|
2717
|
+
*
|
|
2718
|
+
* // Check if message can be added
|
|
2719
|
+
* if (manager.canAddMessage(message)) {
|
|
2720
|
+
* messages.push(message);
|
|
2721
|
+
* } else {
|
|
2722
|
+
* // Compress conversation
|
|
2723
|
+
* messages = manager.compress(messages);
|
|
2724
|
+
* messages.push(message);
|
|
2725
|
+
* }
|
|
2726
|
+
* ```
|
|
2727
|
+
*/ class TokenBudgetManager {
|
|
2728
|
+
/**
|
|
2729
|
+
* Get current token usage
|
|
2730
|
+
*/ getCurrentUsage(messages) {
|
|
2731
|
+
const used = this.counter.countConversation(messages);
|
|
2732
|
+
const max = this.config.max;
|
|
2733
|
+
const remaining = Math.max(0, max - used - this.config.reserveForResponse);
|
|
2734
|
+
const percentage = used / max * 100;
|
|
2735
|
+
return {
|
|
2736
|
+
used,
|
|
2737
|
+
max,
|
|
2738
|
+
remaining,
|
|
2739
|
+
percentage
|
|
2740
|
+
};
|
|
2741
|
+
}
|
|
2742
|
+
/**
|
|
2743
|
+
* Get remaining tokens available
|
|
2744
|
+
*/ getRemainingTokens(messages) {
|
|
2745
|
+
return this.getCurrentUsage(messages).remaining;
|
|
2746
|
+
}
|
|
2747
|
+
/**
|
|
2748
|
+
* Check if near token limit
|
|
2749
|
+
*/ isNearLimit(messages, threshold) {
|
|
2750
|
+
const usage = this.getCurrentUsage(messages);
|
|
2751
|
+
const checkThreshold = threshold !== null && threshold !== void 0 ? threshold : this.config.warningThreshold;
|
|
2752
|
+
const isNear = usage.percentage >= checkThreshold * 100;
|
|
2753
|
+
if (isNear) {
|
|
2754
|
+
var _this_config_onWarning, _this_config;
|
|
2755
|
+
(_this_config_onWarning = (_this_config = this.config).onWarning) === null || _this_config_onWarning === void 0 ? void 0 : _this_config_onWarning.call(_this_config, usage);
|
|
2756
|
+
}
|
|
2757
|
+
return isNear;
|
|
2758
|
+
}
|
|
2759
|
+
/**
|
|
2760
|
+
* Check if a message can be added without exceeding budget
|
|
2761
|
+
*/ canAddMessage(message, currentMessages) {
|
|
2762
|
+
const currentTokens = this.counter.countConversation(currentMessages);
|
|
2763
|
+
const messageTokens = this.counter.countMessage(message);
|
|
2764
|
+
const total = currentTokens + messageTokens + this.config.reserveForResponse;
|
|
2765
|
+
return total <= this.config.max;
|
|
2766
|
+
}
|
|
2767
|
+
/**
|
|
2768
|
+
* Compress messages according to strategy
|
|
2769
|
+
*/ compress(messages) {
|
|
2770
|
+
var _this_config_onCompression, _this_config;
|
|
2771
|
+
const before = messages.length;
|
|
2772
|
+
const tokensBefore = this.counter.countConversation(messages);
|
|
2773
|
+
const targetTokens = this.config.max - this.config.reserveForResponse;
|
|
2774
|
+
this.logger.debug('Compressing messages', {
|
|
2775
|
+
before,
|
|
2776
|
+
tokensBefore,
|
|
2777
|
+
targetTokens,
|
|
2778
|
+
strategy: this.config.strategy
|
|
2779
|
+
});
|
|
2780
|
+
// No compression needed
|
|
2781
|
+
if (tokensBefore <= targetTokens) {
|
|
2782
|
+
return messages;
|
|
2783
|
+
}
|
|
2784
|
+
let compressed;
|
|
2785
|
+
switch(this.config.strategy){
|
|
2786
|
+
case 'priority-based':
|
|
2787
|
+
compressed = this.compressByPriority(messages, targetTokens);
|
|
2788
|
+
break;
|
|
2789
|
+
case 'fifo':
|
|
2790
|
+
compressed = this.compressFIFO(messages, targetTokens);
|
|
2791
|
+
break;
|
|
2792
|
+
case 'adaptive':
|
|
2793
|
+
compressed = this.compressAdaptive(messages, targetTokens);
|
|
2794
|
+
break;
|
|
2795
|
+
case 'summarize':
|
|
2796
|
+
// For now, fall back to FIFO (summarization would require LLM call)
|
|
2797
|
+
compressed = this.compressFIFO(messages, targetTokens);
|
|
2798
|
+
break;
|
|
2799
|
+
default:
|
|
2800
|
+
compressed = this.compressFIFO(messages, targetTokens);
|
|
2801
|
+
}
|
|
2802
|
+
const tokensAfter = this.counter.countConversation(compressed);
|
|
2803
|
+
const stats = {
|
|
2804
|
+
messagesBefore: before,
|
|
2805
|
+
messagesAfter: compressed.length,
|
|
2806
|
+
tokensBefore,
|
|
2807
|
+
tokensAfter,
|
|
2808
|
+
tokensSaved: tokensBefore - tokensAfter,
|
|
2809
|
+
strategy: this.config.strategy
|
|
2810
|
+
};
|
|
2811
|
+
(_this_config_onCompression = (_this_config = this.config).onCompression) === null || _this_config_onCompression === void 0 ? void 0 : _this_config_onCompression.call(_this_config, stats);
|
|
2812
|
+
this.logger.info('Compressed conversation', stats);
|
|
2813
|
+
return compressed;
|
|
2814
|
+
}
|
|
2815
|
+
/**
|
|
2816
|
+
* Compress by priority (keep high-priority messages)
|
|
2817
|
+
*/ compressByPriority(messages, targetTokens) {
|
|
2818
|
+
// Calculate priority for each message
|
|
2819
|
+
const withPriority = messages.map((msg, idx)=>({
|
|
2820
|
+
message: msg,
|
|
2821
|
+
priority: this.calculatePriority(msg, idx, messages.length),
|
|
2822
|
+
tokens: this.counter.countMessage(msg),
|
|
2823
|
+
index: idx
|
|
2824
|
+
}));
|
|
2825
|
+
// Sort by priority (descending)
|
|
2826
|
+
withPriority.sort((a, b)=>b.priority - a.priority);
|
|
2827
|
+
// Keep highest priority messages that fit in budget
|
|
2828
|
+
const kept = [];
|
|
2829
|
+
let totalTokens = 0;
|
|
2830
|
+
for (const item of withPriority){
|
|
2831
|
+
if (totalTokens + item.tokens <= targetTokens) {
|
|
2832
|
+
kept.push(item);
|
|
2833
|
+
totalTokens += item.tokens;
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
// Sort back to original order
|
|
2837
|
+
kept.sort((a, b)=>a.index - b.index);
|
|
2838
|
+
return kept.map((item)=>item.message);
|
|
2839
|
+
}
|
|
2840
|
+
/**
|
|
2841
|
+
* Compress using FIFO (remove oldest first) - optimized with Set
|
|
2842
|
+
*/ compressFIFO(messages, targetTokens) {
|
|
2843
|
+
var _this_config_preserveRecent;
|
|
2844
|
+
const preservedSet = new Set();
|
|
2845
|
+
let totalTokens = 0;
|
|
2846
|
+
// Always preserve system messages if configured
|
|
2847
|
+
const systemMessages = messages.filter((m)=>m.role === 'system');
|
|
2848
|
+
if (this.config.preserveSystem) {
|
|
2849
|
+
for (const msg of systemMessages){
|
|
2850
|
+
preservedSet.add(msg);
|
|
2851
|
+
totalTokens += this.counter.countMessage(msg);
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
// Preserve recent messages
|
|
2855
|
+
const recentCount = (_this_config_preserveRecent = this.config.preserveRecent) !== null && _this_config_preserveRecent !== void 0 ? _this_config_preserveRecent : 3;
|
|
2856
|
+
const recentMessages = messages.slice(-recentCount).filter((m)=>m.role !== 'system');
|
|
2857
|
+
for (const msg of recentMessages){
|
|
2858
|
+
if (!preservedSet.has(msg)) {
|
|
2859
|
+
const tokens = this.counter.countMessage(msg);
|
|
2860
|
+
if (totalTokens + tokens <= targetTokens) {
|
|
2861
|
+
preservedSet.add(msg);
|
|
2862
|
+
totalTokens += tokens;
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
// Add older messages if space available
|
|
2867
|
+
const otherMessages = messages.filter((m)=>!preservedSet.has(m) && m.role !== 'system');
|
|
2868
|
+
for(let i = otherMessages.length - 1; i >= 0; i--){
|
|
2869
|
+
const msg = otherMessages[i];
|
|
2870
|
+
const tokens = this.counter.countMessage(msg);
|
|
2871
|
+
if (totalTokens + tokens <= targetTokens) {
|
|
2872
|
+
preservedSet.add(msg);
|
|
2873
|
+
totalTokens += tokens;
|
|
2874
|
+
} else {
|
|
2875
|
+
break;
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
// Sort to maintain conversation order - use Set for O(1) lookup
|
|
2879
|
+
return messages.filter((m)=>preservedSet.has(m));
|
|
2880
|
+
}
|
|
2881
|
+
/**
|
|
2882
|
+
* Adaptive compression based on conversation phase
|
|
2883
|
+
*/ compressAdaptive(messages, targetTokens) {
|
|
2884
|
+
const messageCount = messages.length;
|
|
2885
|
+
// Early phase: minimal compression (keep most messages)
|
|
2886
|
+
if (messageCount <= 5) {
|
|
2887
|
+
return this.compressFIFO(messages, targetTokens);
|
|
2888
|
+
}
|
|
2889
|
+
// Mid phase: moderate compression
|
|
2890
|
+
if (messageCount <= 15) {
|
|
2891
|
+
// Temporarily modify preserveRecent, then restore
|
|
2892
|
+
const originalPreserveRecent = this.config.preserveRecent;
|
|
2893
|
+
this.config.preserveRecent = 5;
|
|
2894
|
+
const result = this.compressFIFO(messages, targetTokens);
|
|
2895
|
+
this.config.preserveRecent = originalPreserveRecent;
|
|
2896
|
+
return result;
|
|
2897
|
+
}
|
|
2898
|
+
// Late phase: aggressive compression (priority-based)
|
|
2899
|
+
return this.compressByPriority(messages, targetTokens);
|
|
2900
|
+
}
|
|
2901
|
+
/**
|
|
2902
|
+
* Calculate message priority for compression
|
|
2903
|
+
*/ calculatePriority(message, index, total) {
|
|
2904
|
+
let priority = 1.0;
|
|
2905
|
+
// System messages: highest priority
|
|
2906
|
+
if (message.role === 'system') {
|
|
2907
|
+
priority = 10.0;
|
|
2908
|
+
}
|
|
2909
|
+
// Recent messages: higher priority
|
|
2910
|
+
const recencyBonus = index / total;
|
|
2911
|
+
priority += recencyBonus * 2;
|
|
2912
|
+
// Tool results: moderate priority
|
|
2913
|
+
if (message.role === 'tool') {
|
|
2914
|
+
priority += 0.5;
|
|
2915
|
+
}
|
|
2916
|
+
// Messages with tool calls: keep for context
|
|
2917
|
+
if (message.tool_calls && message.tool_calls.length > 0) {
|
|
2918
|
+
priority += 0.8;
|
|
2919
|
+
}
|
|
2920
|
+
return priority;
|
|
2921
|
+
}
|
|
2922
|
+
/**
|
|
2923
|
+
* Truncate to exact number of messages
|
|
2924
|
+
*/ truncate(messages, maxMessages) {
|
|
2925
|
+
if (messages.length <= maxMessages) {
|
|
2926
|
+
return messages;
|
|
2927
|
+
}
|
|
2928
|
+
// Keep system messages + recent messages
|
|
2929
|
+
const systemMessages = messages.filter((m)=>m.role === 'system');
|
|
2930
|
+
const otherMessages = messages.filter((m)=>m.role !== 'system');
|
|
2931
|
+
const recentOther = otherMessages.slice(-(maxMessages - systemMessages.length));
|
|
2932
|
+
return [
|
|
2933
|
+
...systemMessages,
|
|
2934
|
+
...recentOther
|
|
2935
|
+
];
|
|
2936
|
+
}
|
|
2937
|
+
/**
|
|
2938
|
+
* Dispose resources
|
|
2939
|
+
*/ dispose() {
|
|
2940
|
+
this.counter.dispose();
|
|
2941
|
+
}
|
|
2942
|
+
constructor(config, model, logger){
|
|
2943
|
+
_define_property$4(this, "config", void 0);
|
|
2944
|
+
_define_property$4(this, "counter", void 0);
|
|
2945
|
+
_define_property$4(this, "logger", void 0);
|
|
2946
|
+
this.config = {
|
|
2947
|
+
warningThreshold: 0.8,
|
|
2948
|
+
preserveRecent: 3,
|
|
2949
|
+
preserveSystem: true,
|
|
2950
|
+
preserveHighPriority: true,
|
|
2951
|
+
onWarning: ()=>{},
|
|
2952
|
+
onCompression: ()=>{},
|
|
2953
|
+
...config
|
|
2954
|
+
};
|
|
2955
|
+
this.counter = new TokenCounter(model, logger);
|
|
2956
|
+
this.logger = wrapLogger(logger || DEFAULT_LOGGER, 'TokenBudgetManager');
|
|
2957
|
+
this.logger.debug('Created TokenBudgetManager', {
|
|
2958
|
+
max: this.config.max,
|
|
2959
|
+
strategy: this.config.strategy
|
|
2960
|
+
});
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
function _define_property$3(obj, key, value) {
|
|
2965
|
+
if (key in obj) {
|
|
2966
|
+
Object.defineProperty(obj, key, {
|
|
2967
|
+
value: value,
|
|
2968
|
+
enumerable: true,
|
|
2969
|
+
configurable: true,
|
|
2970
|
+
writable: true
|
|
2971
|
+
});
|
|
2972
|
+
} else {
|
|
2973
|
+
obj[key] = value;
|
|
2974
|
+
}
|
|
2975
|
+
return obj;
|
|
2976
|
+
}
|
|
2977
|
+
// ===== SCHEMAS =====
|
|
2978
|
+
const ConversationBuilderConfigSchema = zod.z.object({
|
|
2979
|
+
model: zod.z.string(),
|
|
2980
|
+
formatter: zod.z.any().optional(),
|
|
2981
|
+
trackContext: zod.z.boolean().optional().default(true),
|
|
2982
|
+
deduplicateContext: zod.z.boolean().optional().default(true)
|
|
2983
|
+
});
|
|
2984
|
+
// ===== CONVERSATION BUILDER =====
|
|
2985
|
+
/**
|
|
2986
|
+
* ConversationBuilder manages multi-turn conversations with full lifecycle support.
|
|
2987
|
+
*
|
|
2988
|
+
* Features:
|
|
2989
|
+
* - Initialize from RiotPrompt prompts
|
|
2990
|
+
* - Add messages of any type (system, user, assistant, tool)
|
|
2991
|
+
* - Handle tool calls and results
|
|
2992
|
+
* - Inject dynamic context
|
|
2993
|
+
* - Clone for parallel exploration
|
|
2994
|
+
* - Serialize/deserialize for persistence
|
|
2995
|
+
*
|
|
2996
|
+
* @example
|
|
2997
|
+
* ```typescript
|
|
2998
|
+
* // Create from prompt
|
|
2999
|
+
* const conversation = ConversationBuilder.create()
|
|
3000
|
+
* .fromPrompt(prompt, 'gpt-4o')
|
|
3001
|
+
* .build();
|
|
3002
|
+
*
|
|
3003
|
+
* // Add messages
|
|
3004
|
+
* conversation.addUserMessage('Analyze this code');
|
|
3005
|
+
*
|
|
3006
|
+
* // Handle tool calls
|
|
3007
|
+
* conversation.addAssistantWithToolCalls(null, toolCalls);
|
|
3008
|
+
* conversation.addToolResult(toolCallId, result);
|
|
3009
|
+
*
|
|
3010
|
+
* // Export
|
|
3011
|
+
* const messages = conversation.toMessages();
|
|
3012
|
+
* ```
|
|
3013
|
+
*/ class ConversationBuilder {
|
|
3014
|
+
/**
|
|
3015
|
+
* Create a new ConversationBuilder instance
|
|
3016
|
+
*/ static create(config, logger) {
|
|
3017
|
+
const defaultConfig = {
|
|
3018
|
+
model: 'gpt-4o',
|
|
3019
|
+
trackContext: true,
|
|
3020
|
+
deduplicateContext: true,
|
|
3021
|
+
...config
|
|
3022
|
+
};
|
|
3023
|
+
return new ConversationBuilder(defaultConfig, logger);
|
|
3024
|
+
}
|
|
3025
|
+
/**
|
|
3026
|
+
* Initialize conversation from a RiotPrompt prompt
|
|
3027
|
+
*/ fromPrompt(prompt, model) {
|
|
3028
|
+
const targetModel = model || this.config.model;
|
|
3029
|
+
this.logger.debug('Initializing from prompt', {
|
|
3030
|
+
model: targetModel
|
|
3031
|
+
});
|
|
3032
|
+
// Use formatter (provided or create new one)
|
|
3033
|
+
const formatter$1 = this.config.formatter || create$5();
|
|
3034
|
+
const request = formatter$1.formatPrompt(targetModel, prompt);
|
|
3035
|
+
// Add all messages from formatted request
|
|
3036
|
+
request.messages.forEach((msg)=>{
|
|
3037
|
+
this.state.messages.push(msg);
|
|
3038
|
+
});
|
|
3039
|
+
this.updateMetadata();
|
|
3040
|
+
this.logger.debug('Initialized from prompt', {
|
|
3041
|
+
messageCount: this.state.messages.length
|
|
3042
|
+
});
|
|
3043
|
+
return this;
|
|
3044
|
+
}
|
|
3045
|
+
/**
|
|
3046
|
+
* Add a system message
|
|
3047
|
+
*/ addSystemMessage(content) {
|
|
3048
|
+
this.logger.debug('Adding system message');
|
|
3049
|
+
let messageContent;
|
|
3050
|
+
if (typeof content === 'string') {
|
|
3051
|
+
messageContent = content;
|
|
3052
|
+
} else {
|
|
3053
|
+
// Format section using formatter
|
|
3054
|
+
const formatter$1 = this.config.formatter || create$5();
|
|
3055
|
+
messageContent = formatter$1.format(content);
|
|
3056
|
+
}
|
|
3057
|
+
this.state.messages.push({
|
|
3058
|
+
role: 'system',
|
|
3059
|
+
content: messageContent
|
|
3060
|
+
});
|
|
3061
|
+
this.updateMetadata();
|
|
3062
|
+
return this;
|
|
3063
|
+
}
|
|
3064
|
+
/**
|
|
3065
|
+
* Add a user message (with automatic budget management)
|
|
3066
|
+
*/ addUserMessage(content) {
|
|
3067
|
+
this.logger.debug('Adding user message');
|
|
3068
|
+
let messageContent;
|
|
3069
|
+
if (typeof content === 'string') {
|
|
3070
|
+
messageContent = content;
|
|
3071
|
+
} else {
|
|
3072
|
+
// Format section using formatter
|
|
3073
|
+
const formatter$1 = this.config.formatter || create$5();
|
|
3074
|
+
messageContent = formatter$1.format(content);
|
|
3075
|
+
}
|
|
3076
|
+
const message = {
|
|
3077
|
+
role: 'user',
|
|
3078
|
+
content: messageContent
|
|
3079
|
+
};
|
|
3080
|
+
// Check budget if enabled
|
|
3081
|
+
if (this.budgetManager) {
|
|
3082
|
+
if (!this.budgetManager.canAddMessage(message, this.state.messages)) {
|
|
3083
|
+
this.logger.warn('Budget exceeded, compressing conversation');
|
|
3084
|
+
this.state.messages = this.budgetManager.compress(this.state.messages);
|
|
3085
|
+
// Re-check after compression and warn if still over budget
|
|
3086
|
+
if (!this.budgetManager.canAddMessage(message, this.state.messages)) {
|
|
3087
|
+
this.logger.warn('Token budget still exceeded after compression, adding message anyway');
|
|
3088
|
+
// Note: We add the message anyway to maintain backward compatibility
|
|
3089
|
+
// Consider setting onBudgetExceeded: 'error' in config if strict enforcement is needed
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
this.state.messages.push(message);
|
|
3094
|
+
this.updateMetadata();
|
|
3095
|
+
return this;
|
|
3096
|
+
}
|
|
3097
|
+
/**
|
|
3098
|
+
* Add an assistant message
|
|
3099
|
+
*/ addAssistantMessage(content) {
|
|
3100
|
+
this.logger.debug('Adding assistant message');
|
|
3101
|
+
this.state.messages.push({
|
|
3102
|
+
role: 'assistant',
|
|
3103
|
+
content: content || ''
|
|
3104
|
+
});
|
|
3105
|
+
this.updateMetadata();
|
|
3106
|
+
return this;
|
|
3107
|
+
}
|
|
3108
|
+
/**
|
|
3109
|
+
* Add an assistant message with tool calls
|
|
3110
|
+
*/ addAssistantWithToolCalls(content, toolCalls) {
|
|
3111
|
+
this.logger.debug('Adding assistant message with tool calls', {
|
|
3112
|
+
toolCount: toolCalls.length
|
|
3113
|
+
});
|
|
3114
|
+
this.state.messages.push({
|
|
3115
|
+
role: 'assistant',
|
|
3116
|
+
content: content,
|
|
3117
|
+
tool_calls: toolCalls
|
|
3118
|
+
});
|
|
3119
|
+
this.state.metadata.toolCallCount += toolCalls.length;
|
|
3120
|
+
this.updateMetadata();
|
|
3121
|
+
return this;
|
|
3122
|
+
}
|
|
3123
|
+
/**
|
|
3124
|
+
* Add a tool result message
|
|
3125
|
+
*/ addToolResult(toolCallId, content, toolName) {
|
|
3126
|
+
this.logger.debug('Adding tool result', {
|
|
3127
|
+
toolCallId,
|
|
3128
|
+
toolName
|
|
3129
|
+
});
|
|
3130
|
+
const message = {
|
|
3131
|
+
role: 'tool',
|
|
3132
|
+
tool_call_id: toolCallId,
|
|
3133
|
+
content: content
|
|
3134
|
+
};
|
|
3135
|
+
if (toolName) {
|
|
3136
|
+
message.name = toolName;
|
|
3137
|
+
}
|
|
3138
|
+
this.state.messages.push(message);
|
|
3139
|
+
this.updateMetadata();
|
|
3140
|
+
return this;
|
|
3141
|
+
}
|
|
3142
|
+
/**
|
|
3143
|
+
* Alias for addToolResult (more intuitive naming)
|
|
3144
|
+
*/ addToolMessage(toolCallId, content, toolName) {
|
|
3145
|
+
return this.addToolResult(toolCallId, content, toolName);
|
|
3146
|
+
}
|
|
3147
|
+
/**
|
|
3148
|
+
* Inject context into the conversation with advanced options
|
|
3149
|
+
*
|
|
3150
|
+
* @param context - Array of content items to inject
|
|
3151
|
+
* @param options - Injection options (position, format, deduplication, etc.)
|
|
3152
|
+
*/ injectContext(context, options) {
|
|
3153
|
+
var _this_config_deduplicateContext;
|
|
3154
|
+
const opts = {
|
|
3155
|
+
position: 'end',
|
|
3156
|
+
format: 'structured',
|
|
3157
|
+
deduplicate: (_this_config_deduplicateContext = this.config.deduplicateContext) !== null && _this_config_deduplicateContext !== void 0 ? _this_config_deduplicateContext : true,
|
|
3158
|
+
deduplicateBy: 'id',
|
|
3159
|
+
priority: 'medium',
|
|
3160
|
+
weight: 1.0,
|
|
3161
|
+
category: undefined,
|
|
3162
|
+
source: undefined,
|
|
3163
|
+
...options
|
|
3164
|
+
};
|
|
3165
|
+
this.logger.debug('Injecting context', {
|
|
3166
|
+
itemCount: context.length,
|
|
3167
|
+
options: opts
|
|
3168
|
+
});
|
|
3169
|
+
// Filter out duplicates if enabled
|
|
3170
|
+
const itemsToAdd = [];
|
|
3171
|
+
for (const item of context){
|
|
3172
|
+
const enrichedItem = {
|
|
3173
|
+
...item,
|
|
3174
|
+
priority: item.priority || opts.priority,
|
|
3175
|
+
weight: item.weight || opts.weight,
|
|
3176
|
+
category: item.category || opts.category,
|
|
3177
|
+
source: item.source || opts.source,
|
|
3178
|
+
timestamp: item.timestamp || new Date()
|
|
3179
|
+
};
|
|
3180
|
+
// Check deduplication
|
|
3181
|
+
if (opts.deduplicate) {
|
|
3182
|
+
let skip = false;
|
|
3183
|
+
switch(opts.deduplicateBy){
|
|
3184
|
+
case 'id':
|
|
3185
|
+
if (enrichedItem.id && this.state.contextManager.hasContext(enrichedItem.id)) {
|
|
3186
|
+
this.logger.debug('Skipping duplicate context by ID', {
|
|
3187
|
+
id: enrichedItem.id
|
|
3188
|
+
});
|
|
3189
|
+
skip = true;
|
|
3190
|
+
}
|
|
3191
|
+
break;
|
|
3192
|
+
case 'hash':
|
|
3193
|
+
if (this.state.contextManager.hasContentHash(enrichedItem.content)) {
|
|
3194
|
+
this.logger.debug('Skipping duplicate context by hash');
|
|
3195
|
+
skip = true;
|
|
3196
|
+
}
|
|
3197
|
+
break;
|
|
3198
|
+
case 'content':
|
|
3199
|
+
if (this.state.contextManager.hasSimilarContent(enrichedItem.content)) {
|
|
3200
|
+
this.logger.debug('Skipping duplicate context by content');
|
|
3201
|
+
skip = true;
|
|
3202
|
+
}
|
|
3203
|
+
break;
|
|
3204
|
+
}
|
|
3205
|
+
if (skip) {
|
|
3206
|
+
continue;
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
itemsToAdd.push(enrichedItem);
|
|
3210
|
+
}
|
|
3211
|
+
// Only proceed if we have items to add
|
|
3212
|
+
if (itemsToAdd.length === 0) {
|
|
3213
|
+
return this;
|
|
3214
|
+
}
|
|
3215
|
+
// Calculate position
|
|
3216
|
+
const position = this.calculatePosition(opts.position);
|
|
3217
|
+
// Format and inject
|
|
3218
|
+
for(let i = 0; i < itemsToAdd.length; i++){
|
|
3219
|
+
const item = itemsToAdd[i];
|
|
3220
|
+
const formatted = this.formatContextItem(item, opts.format);
|
|
3221
|
+
const contextMessage = {
|
|
3222
|
+
role: 'user',
|
|
3223
|
+
content: formatted
|
|
3224
|
+
};
|
|
3225
|
+
// Each item is inserted at position + index to maintain order
|
|
3226
|
+
const actualPosition = position + i;
|
|
3227
|
+
this.state.messages.splice(actualPosition, 0, contextMessage);
|
|
3228
|
+
// Track in context manager with correct position
|
|
3229
|
+
this.state.contextManager.track(item, actualPosition);
|
|
3230
|
+
}
|
|
3231
|
+
this.updateMetadata();
|
|
3232
|
+
return this;
|
|
3233
|
+
}
|
|
3234
|
+
/**
|
|
3235
|
+
* Inject system-level context
|
|
3236
|
+
*/ injectSystemContext(context) {
|
|
3237
|
+
this.logger.debug('Injecting system context');
|
|
3238
|
+
let messageContent;
|
|
3239
|
+
if (typeof context === 'string') {
|
|
3240
|
+
messageContent = context;
|
|
3241
|
+
} else {
|
|
3242
|
+
const formatter$1 = this.config.formatter || create$5();
|
|
3243
|
+
messageContent = formatter$1.format(context);
|
|
3244
|
+
}
|
|
3245
|
+
this.state.messages.push({
|
|
3246
|
+
role: 'system',
|
|
3247
|
+
content: messageContent
|
|
3248
|
+
});
|
|
3249
|
+
this.updateMetadata();
|
|
3250
|
+
return this;
|
|
3251
|
+
}
|
|
3252
|
+
/**
|
|
3253
|
+
* Get the number of messages in the conversation
|
|
3254
|
+
*/ getMessageCount() {
|
|
3255
|
+
return this.state.messages.length;
|
|
3256
|
+
}
|
|
3257
|
+
/**
|
|
3258
|
+
* Get the last message in the conversation
|
|
3259
|
+
*/ getLastMessage() {
|
|
3260
|
+
return this.state.messages[this.state.messages.length - 1];
|
|
3261
|
+
}
|
|
3262
|
+
/**
|
|
3263
|
+
* Get all messages
|
|
3264
|
+
*/ getMessages() {
|
|
3265
|
+
return [
|
|
3266
|
+
...this.state.messages
|
|
3267
|
+
];
|
|
3268
|
+
}
|
|
3269
|
+
/**
|
|
3270
|
+
* Check if conversation has any tool calls
|
|
3271
|
+
*/ hasToolCalls() {
|
|
3272
|
+
return this.state.metadata.toolCallCount > 0;
|
|
3273
|
+
}
|
|
3274
|
+
/**
|
|
3275
|
+
* Get conversation metadata
|
|
3276
|
+
*/ getMetadata() {
|
|
3277
|
+
return {
|
|
3278
|
+
...this.state.metadata
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
/**
|
|
3282
|
+
* Export messages in OpenAI format (deep copy to prevent shared state)
|
|
3283
|
+
*/ toMessages() {
|
|
3284
|
+
return this.state.messages.map((msg)=>({
|
|
3285
|
+
...msg,
|
|
3286
|
+
tool_calls: msg.tool_calls ? msg.tool_calls.map((tc)=>({
|
|
3287
|
+
...tc,
|
|
3288
|
+
function: {
|
|
3289
|
+
...tc.function
|
|
3290
|
+
}
|
|
3291
|
+
})) : undefined
|
|
3292
|
+
}));
|
|
3293
|
+
}
|
|
3294
|
+
/**
|
|
3295
|
+
* Serialize conversation to JSON
|
|
3296
|
+
*/ toJSON() {
|
|
3297
|
+
const serialized = {
|
|
3298
|
+
messages: this.state.messages,
|
|
3299
|
+
metadata: {
|
|
3300
|
+
...this.state.metadata,
|
|
3301
|
+
created: this.state.metadata.created.toISOString(),
|
|
3302
|
+
lastModified: this.state.metadata.lastModified.toISOString()
|
|
3303
|
+
},
|
|
3304
|
+
contextProvided: Array.from(this.state.contextProvided)
|
|
3305
|
+
};
|
|
3306
|
+
return JSON.stringify(serialized, null, 2);
|
|
3307
|
+
}
|
|
3308
|
+
/**
|
|
3309
|
+
* Restore conversation from JSON
|
|
3310
|
+
*/ static fromJSON(json, config, logger) {
|
|
3311
|
+
const parsed = JSON.parse(json);
|
|
3312
|
+
const builder = ConversationBuilder.create({
|
|
3313
|
+
model: parsed.metadata.model,
|
|
3314
|
+
...config
|
|
3315
|
+
}, logger);
|
|
3316
|
+
// Restore state
|
|
3317
|
+
builder.state.messages = parsed.messages;
|
|
3318
|
+
builder.state.metadata = {
|
|
3319
|
+
...parsed.metadata,
|
|
3320
|
+
created: new Date(parsed.metadata.created),
|
|
3321
|
+
lastModified: new Date(parsed.metadata.lastModified)
|
|
3322
|
+
};
|
|
3323
|
+
builder.state.contextProvided = new Set(parsed.contextProvided);
|
|
3324
|
+
return builder;
|
|
3325
|
+
}
|
|
3326
|
+
/**
|
|
3327
|
+
* Clone the conversation for parallel exploration (deep copy to prevent shared state)
|
|
3328
|
+
*/ clone() {
|
|
3329
|
+
this.logger.debug('Cloning conversation');
|
|
3330
|
+
const cloned = ConversationBuilder.create({
|
|
3331
|
+
...this.config
|
|
3332
|
+
}, this.logger);
|
|
3333
|
+
// Deep copy state (note: contextManager is already created in constructor)
|
|
3334
|
+
cloned.state.messages = this.state.messages.map((msg)=>({
|
|
3335
|
+
...msg,
|
|
3336
|
+
tool_calls: msg.tool_calls ? msg.tool_calls.map((tc)=>({
|
|
3337
|
+
...tc,
|
|
3338
|
+
function: {
|
|
3339
|
+
...tc.function
|
|
3340
|
+
}
|
|
3341
|
+
})) : undefined
|
|
3342
|
+
}));
|
|
3343
|
+
cloned.state.metadata = {
|
|
3344
|
+
...this.state.metadata
|
|
3345
|
+
};
|
|
3346
|
+
cloned.state.contextProvided = new Set(this.state.contextProvided);
|
|
3347
|
+
// Copy context manager state
|
|
3348
|
+
const allContext = this.state.contextManager.getAll();
|
|
3349
|
+
allContext.forEach((item)=>{
|
|
3350
|
+
cloned.state.contextManager.track(item, item.position);
|
|
3351
|
+
});
|
|
3352
|
+
return cloned;
|
|
3353
|
+
}
|
|
3354
|
+
/**
|
|
3355
|
+
* Truncate conversation to last N messages
|
|
3356
|
+
*/ truncate(maxMessages) {
|
|
3357
|
+
this.logger.debug('Truncating conversation', {
|
|
3358
|
+
maxMessages,
|
|
3359
|
+
current: this.state.messages.length
|
|
3360
|
+
});
|
|
3361
|
+
if (this.state.messages.length > maxMessages) {
|
|
3362
|
+
this.state.messages = this.state.messages.slice(-maxMessages);
|
|
3363
|
+
this.updateMetadata();
|
|
3364
|
+
}
|
|
3365
|
+
return this;
|
|
3366
|
+
}
|
|
3367
|
+
/**
|
|
3368
|
+
* Remove all messages of a specific type
|
|
3369
|
+
*/ removeMessagesOfType(role) {
|
|
3370
|
+
this.logger.debug('Removing messages of type', {
|
|
3371
|
+
role
|
|
3372
|
+
});
|
|
3373
|
+
this.state.messages = this.state.messages.filter((msg)=>msg.role !== role);
|
|
3374
|
+
this.updateMetadata();
|
|
3375
|
+
return this;
|
|
3376
|
+
}
|
|
3377
|
+
/**
|
|
3378
|
+
* Get the context manager
|
|
3379
|
+
*/ getContextManager() {
|
|
3380
|
+
return this.state.contextManager;
|
|
3381
|
+
}
|
|
3382
|
+
/**
|
|
3383
|
+
* Get conversation state (for conditional injection)
|
|
3384
|
+
*/ getState() {
|
|
3385
|
+
return {
|
|
3386
|
+
messages: [
|
|
3387
|
+
...this.state.messages
|
|
3388
|
+
],
|
|
3389
|
+
metadata: {
|
|
3390
|
+
...this.state.metadata
|
|
3391
|
+
},
|
|
3392
|
+
contextProvided: new Set(this.state.contextProvided),
|
|
3393
|
+
contextManager: this.state.contextManager
|
|
3394
|
+
};
|
|
3395
|
+
}
|
|
3396
|
+
// ===== SEMANTIC MESSAGE METHODS (Feature 5) =====
|
|
3397
|
+
/**
|
|
3398
|
+
* Add a system message using semantic builder
|
|
3399
|
+
*/ asSystem(content) {
|
|
3400
|
+
const message = MessageBuilder.system(this.logger).withContent(content).withFormatter(this.config.formatter || create$5()).buildForModel(this.config.model);
|
|
3401
|
+
this.state.messages.push(message);
|
|
3402
|
+
this.updateMetadata();
|
|
3403
|
+
return this;
|
|
3404
|
+
}
|
|
3405
|
+
/**
|
|
3406
|
+
* Add a user message using semantic builder
|
|
3407
|
+
*/ asUser(content) {
|
|
3408
|
+
const message = MessageBuilder.user(this.logger).withContent(content).withFormatter(this.config.formatter || create$5()).buildForModel(this.config.model);
|
|
3409
|
+
// Check budget if enabled
|
|
3410
|
+
if (this.budgetManager) {
|
|
3411
|
+
if (!this.budgetManager.canAddMessage(message, this.state.messages)) {
|
|
3412
|
+
this.logger.warn('Budget exceeded, compressing conversation');
|
|
3413
|
+
this.state.messages = this.budgetManager.compress(this.state.messages);
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
this.state.messages.push(message);
|
|
3417
|
+
this.updateMetadata();
|
|
3418
|
+
return this;
|
|
3419
|
+
}
|
|
3420
|
+
/**
|
|
3421
|
+
* Add an assistant message using semantic builder
|
|
3422
|
+
*/ asAssistant(content, toolCalls) {
|
|
3423
|
+
const builder = MessageBuilder.assistant(this.logger).withFormatter(this.config.formatter || create$5());
|
|
3424
|
+
if (content) {
|
|
3425
|
+
builder.withContent(content);
|
|
3426
|
+
}
|
|
3427
|
+
if (toolCalls) {
|
|
3428
|
+
builder.withToolCalls(toolCalls);
|
|
3429
|
+
}
|
|
3430
|
+
const message = builder.buildForModel(this.config.model);
|
|
3431
|
+
if (toolCalls) {
|
|
3432
|
+
this.state.metadata.toolCallCount += toolCalls.length;
|
|
3433
|
+
}
|
|
3434
|
+
this.state.messages.push(message);
|
|
3435
|
+
this.updateMetadata();
|
|
3436
|
+
return this;
|
|
3437
|
+
}
|
|
3438
|
+
/**
|
|
3439
|
+
* Add a tool result message using semantic builder
|
|
3440
|
+
*/ asTool(callId, result, metadata) {
|
|
3441
|
+
const builder = MessageBuilder.tool(callId, this.logger).withResult(result);
|
|
3442
|
+
if (metadata) {
|
|
3443
|
+
builder.withMetadata(metadata);
|
|
3444
|
+
}
|
|
3445
|
+
const message = builder.buildForModel(this.config.model);
|
|
3446
|
+
this.state.messages.push(message);
|
|
3447
|
+
this.updateMetadata();
|
|
3448
|
+
return this;
|
|
3449
|
+
}
|
|
3450
|
+
/**
|
|
3451
|
+
* Configure token budget
|
|
3452
|
+
*/ withTokenBudget(config) {
|
|
3453
|
+
this.logger.debug('Configuring token budget', {
|
|
3454
|
+
max: config.max
|
|
3455
|
+
});
|
|
3456
|
+
this.budgetManager = new TokenBudgetManager(config, this.config.model, this.logger);
|
|
3457
|
+
return this;
|
|
3458
|
+
}
|
|
3459
|
+
/**
|
|
3460
|
+
* Configure conversation logging
|
|
3461
|
+
*/ withLogging(config) {
|
|
3462
|
+
this.logger.debug('Configuring conversation logging');
|
|
3463
|
+
this.conversationLogger = new ConversationLogger(config, this.logger);
|
|
3464
|
+
this.conversationLogger.onConversationStart({
|
|
3465
|
+
model: this.config.model,
|
|
3466
|
+
startTime: new Date()
|
|
3467
|
+
});
|
|
3468
|
+
return this;
|
|
3469
|
+
}
|
|
3470
|
+
/**
|
|
3471
|
+
* Save conversation log
|
|
3472
|
+
*/ async saveLog() {
|
|
3473
|
+
if (!this.conversationLogger) {
|
|
3474
|
+
throw new Error('Logging not enabled. Call withLogging() first.');
|
|
3475
|
+
}
|
|
3476
|
+
this.conversationLogger.onConversationEnd({
|
|
3477
|
+
totalMessages: this.state.messages.length,
|
|
3478
|
+
toolCallsExecuted: this.state.metadata.toolCallCount,
|
|
3479
|
+
iterations: 0,
|
|
3480
|
+
success: true
|
|
3481
|
+
});
|
|
3482
|
+
return await this.conversationLogger.save();
|
|
3483
|
+
}
|
|
3484
|
+
/**
|
|
3485
|
+
* Get current token usage
|
|
3486
|
+
*/ getTokenUsage() {
|
|
3487
|
+
if (!this.budgetManager) {
|
|
3488
|
+
return {
|
|
3489
|
+
used: 0,
|
|
3490
|
+
max: Infinity,
|
|
3491
|
+
remaining: Infinity,
|
|
3492
|
+
percentage: 0
|
|
3493
|
+
};
|
|
3494
|
+
}
|
|
3495
|
+
return this.budgetManager.getCurrentUsage(this.state.messages);
|
|
3496
|
+
}
|
|
3497
|
+
/**
|
|
3498
|
+
* Manually compress conversation
|
|
3499
|
+
*/ compress(_strategy) {
|
|
3500
|
+
if (this.budgetManager) {
|
|
3501
|
+
this.state.messages = this.budgetManager.compress(this.state.messages);
|
|
3502
|
+
}
|
|
3503
|
+
return this;
|
|
3504
|
+
}
|
|
3505
|
+
/**
|
|
3506
|
+
* Build and return the builder (for fluent API compatibility)
|
|
3507
|
+
*/ build() {
|
|
3508
|
+
return this;
|
|
3509
|
+
}
|
|
3510
|
+
/**
|
|
3511
|
+
* Calculate position for context injection
|
|
3512
|
+
*
|
|
3513
|
+
* Positions:
|
|
3514
|
+
* - 'end': After all messages
|
|
3515
|
+
* - 'before-last': Before the last message
|
|
3516
|
+
* - 'after-system': After the LAST system message (useful for models with multiple system messages)
|
|
3517
|
+
* - number: Specific index (clamped to valid range)
|
|
3518
|
+
*/ calculatePosition(position) {
|
|
3519
|
+
if (typeof position === 'number') {
|
|
3520
|
+
return Math.max(0, Math.min(position, this.state.messages.length));
|
|
3521
|
+
}
|
|
3522
|
+
switch(position){
|
|
3523
|
+
case 'end':
|
|
3524
|
+
return this.state.messages.length;
|
|
3525
|
+
case 'before-last':
|
|
3526
|
+
return Math.max(0, this.state.messages.length - 1);
|
|
3527
|
+
case 'after-system':
|
|
3528
|
+
{
|
|
3529
|
+
// Find last system message (uses reverse search to find most recent system message)
|
|
3530
|
+
let lastSystemIdx = -1;
|
|
3531
|
+
for(let i = this.state.messages.length - 1; i >= 0; i--){
|
|
3532
|
+
if (this.state.messages[i].role === 'system') {
|
|
3533
|
+
lastSystemIdx = i;
|
|
3534
|
+
break;
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
return lastSystemIdx >= 0 ? lastSystemIdx + 1 : 0;
|
|
3538
|
+
}
|
|
3539
|
+
default:
|
|
3540
|
+
return this.state.messages.length;
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
/**
|
|
3544
|
+
* Format context item based on format option
|
|
3545
|
+
*/ formatContextItem(item, format) {
|
|
3546
|
+
switch(format){
|
|
3547
|
+
case 'structured':
|
|
3548
|
+
{
|
|
3549
|
+
let result = `## ${item.title || 'Context'}\n\n${item.content}`;
|
|
3550
|
+
// Add metadata if available
|
|
3551
|
+
const metadata = [];
|
|
3552
|
+
if (item.source) {
|
|
3553
|
+
metadata.push(`Source: ${item.source}`);
|
|
3554
|
+
}
|
|
3555
|
+
if (item.timestamp) {
|
|
3556
|
+
metadata.push(`Timestamp: ${item.timestamp.toISOString()}`);
|
|
3557
|
+
}
|
|
3558
|
+
if (metadata.length > 0) {
|
|
3559
|
+
result += `\n\n_${metadata.join(' | ')}_`;
|
|
3560
|
+
}
|
|
3561
|
+
return result;
|
|
3562
|
+
}
|
|
3563
|
+
case 'inline':
|
|
3564
|
+
return `Note: ${item.title ? `${item.title}: ` : ''}${item.content}`;
|
|
3565
|
+
case 'reference':
|
|
3566
|
+
return `[Context Reference: ${item.id || 'unknown'}]\nSee attached context${item.title ? ` for ${item.title}` : ''}`;
|
|
3567
|
+
default:
|
|
3568
|
+
return item.content;
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
/**
|
|
3572
|
+
* Update metadata after state changes
|
|
3573
|
+
*/ updateMetadata() {
|
|
3574
|
+
this.state.metadata.messageCount = this.state.messages.length;
|
|
3575
|
+
this.state.metadata.lastModified = new Date();
|
|
3576
|
+
}
|
|
3577
|
+
constructor(config, logger){
|
|
3578
|
+
_define_property$3(this, "state", void 0);
|
|
3579
|
+
_define_property$3(this, "config", void 0);
|
|
3580
|
+
_define_property$3(this, "logger", void 0);
|
|
3581
|
+
_define_property$3(this, "budgetManager", void 0);
|
|
3582
|
+
_define_property$3(this, "conversationLogger", void 0);
|
|
3583
|
+
this.config = ConversationBuilderConfigSchema.parse(config);
|
|
3584
|
+
this.logger = wrapLogger(logger || DEFAULT_LOGGER, 'ConversationBuilder');
|
|
3585
|
+
this.state = {
|
|
3586
|
+
messages: [],
|
|
3587
|
+
metadata: {
|
|
3588
|
+
model: this.config.model,
|
|
3589
|
+
created: new Date(),
|
|
3590
|
+
lastModified: new Date(),
|
|
3591
|
+
messageCount: 0,
|
|
3592
|
+
toolCallCount: 0
|
|
3593
|
+
},
|
|
3594
|
+
contextProvided: new Set(),
|
|
3595
|
+
contextManager: new ContextManager(logger)
|
|
3596
|
+
};
|
|
3597
|
+
this.logger.debug('Created ConversationBuilder', {
|
|
3598
|
+
model: this.config.model
|
|
3599
|
+
});
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3603
|
+
function _define_property$2(obj, key, value) {
|
|
3604
|
+
if (key in obj) {
|
|
3605
|
+
Object.defineProperty(obj, key, {
|
|
3606
|
+
value: value,
|
|
3607
|
+
enumerable: true,
|
|
3608
|
+
configurable: true,
|
|
3609
|
+
writable: true
|
|
3610
|
+
});
|
|
3611
|
+
} else {
|
|
3612
|
+
obj[key] = value;
|
|
3613
|
+
}
|
|
3614
|
+
return obj;
|
|
3615
|
+
}
|
|
3616
|
+
// ===== VALIDATION SCHEMAS =====
|
|
3617
|
+
// Simplified parameter schema - just validate structure, not deep nesting
|
|
3618
|
+
const ToolSchema = zod.z.object({
|
|
3619
|
+
name: zod.z.string().min(1),
|
|
3620
|
+
description: zod.z.string().min(1),
|
|
3621
|
+
parameters: zod.z.object({
|
|
3622
|
+
type: zod.z.literal('object'),
|
|
3623
|
+
properties: zod.z.record(zod.z.string(), zod.z.any()).default({}),
|
|
3624
|
+
required: zod.z.array(zod.z.string()).optional()
|
|
3625
|
+
}).passthrough(),
|
|
3626
|
+
execute: zod.z.custom((val)=>typeof val === 'function', {
|
|
3627
|
+
message: 'execute must be a function'
|
|
3628
|
+
}),
|
|
3629
|
+
category: zod.z.string().optional(),
|
|
3630
|
+
cost: zod.z.enum([
|
|
3631
|
+
'cheap',
|
|
3632
|
+
'moderate',
|
|
3633
|
+
'expensive'
|
|
3634
|
+
]).optional(),
|
|
3635
|
+
examples: zod.z.array(zod.z.object({
|
|
3636
|
+
scenario: zod.z.string(),
|
|
3637
|
+
params: zod.z.any(),
|
|
3638
|
+
expectedResult: zod.z.string()
|
|
3639
|
+
})).optional()
|
|
3640
|
+
}).passthrough(); // Allow additional fields at tool level too
|
|
3641
|
+
// ===== TOOL REGISTRY =====
|
|
3642
|
+
/**
|
|
3643
|
+
* ToolRegistry manages tool definitions and execution.
|
|
3644
|
+
*
|
|
3645
|
+
* Features:
|
|
3646
|
+
* - Register and manage tools
|
|
3647
|
+
* - Execute tools with context
|
|
3648
|
+
* - Track usage statistics
|
|
3649
|
+
* - Export to different formats (OpenAI, Anthropic)
|
|
3650
|
+
* - Filter by category
|
|
3651
|
+
*
|
|
3652
|
+
* @example
|
|
3653
|
+
* ```typescript
|
|
3654
|
+
* const registry = ToolRegistry.create({
|
|
3655
|
+
* workingDirectory: process.cwd(),
|
|
3656
|
+
* logger: myLogger
|
|
3657
|
+
* });
|
|
3658
|
+
*
|
|
3659
|
+
* registry.register({
|
|
3660
|
+
* name: 'read_file',
|
|
3661
|
+
* description: 'Read a file',
|
|
3662
|
+
* parameters: {
|
|
3663
|
+
* type: 'object',
|
|
3664
|
+
* properties: {
|
|
3665
|
+
* path: { type: 'string', description: 'File path' }
|
|
3666
|
+
* },
|
|
3667
|
+
* required: ['path']
|
|
3668
|
+
* },
|
|
3669
|
+
* execute: async ({ path }) => {
|
|
3670
|
+
* return await fs.readFile(path, 'utf-8');
|
|
3671
|
+
* }
|
|
3672
|
+
* });
|
|
3673
|
+
*
|
|
3674
|
+
* const result = await registry.execute('read_file', { path: 'test.txt' });
|
|
3675
|
+
* ```
|
|
3676
|
+
*/ class ToolRegistry {
|
|
3677
|
+
/**
|
|
3678
|
+
* Create a new ToolRegistry instance
|
|
3679
|
+
*/ static create(context, logger) {
|
|
3680
|
+
return new ToolRegistry(context, logger);
|
|
3681
|
+
}
|
|
3682
|
+
/**
|
|
3683
|
+
* Register a single tool
|
|
3684
|
+
*/ register(tool) {
|
|
3685
|
+
// Validate tool
|
|
3686
|
+
try {
|
|
3687
|
+
ToolSchema.parse(tool);
|
|
3688
|
+
} catch (error) {
|
|
3689
|
+
throw new Error(`Invalid tool definition for "${tool.name}": ${error}`);
|
|
3690
|
+
}
|
|
3691
|
+
if (this.tools.has(tool.name)) {
|
|
3692
|
+
this.logger.warn(`Tool "${tool.name}" already registered, overwriting`);
|
|
3693
|
+
}
|
|
3694
|
+
this.tools.set(tool.name, tool);
|
|
3695
|
+
this.usageStats.set(tool.name, {
|
|
3696
|
+
calls: 0,
|
|
3697
|
+
failures: 0,
|
|
3698
|
+
totalDuration: 0
|
|
3699
|
+
});
|
|
3700
|
+
this.logger.debug('Registered tool', {
|
|
3701
|
+
name: tool.name,
|
|
3702
|
+
category: tool.category
|
|
3703
|
+
});
|
|
3704
|
+
}
|
|
3705
|
+
/**
|
|
3706
|
+
* Register multiple tools at once
|
|
3707
|
+
*/ registerAll(tools) {
|
|
3708
|
+
this.logger.debug('Registering multiple tools', {
|
|
3709
|
+
count: tools.length
|
|
3710
|
+
});
|
|
3711
|
+
tools.forEach((tool)=>this.register(tool));
|
|
3712
|
+
}
|
|
3713
|
+
/**
|
|
3714
|
+
* Get a tool by name
|
|
3715
|
+
*/ get(name) {
|
|
3716
|
+
return this.tools.get(name);
|
|
3717
|
+
}
|
|
3718
|
+
/**
|
|
3719
|
+
* Get all registered tools
|
|
3720
|
+
*/ getAll() {
|
|
3721
|
+
return Array.from(this.tools.values());
|
|
3722
|
+
}
|
|
3723
|
+
/**
|
|
3724
|
+
* Get tools by category
|
|
3725
|
+
*/ getByCategory(category) {
|
|
3726
|
+
return this.getAll().filter((tool)=>tool.category === category);
|
|
3727
|
+
}
|
|
3728
|
+
/**
|
|
3729
|
+
* Check if a tool is registered
|
|
3730
|
+
*/ has(name) {
|
|
3731
|
+
return this.tools.has(name);
|
|
3732
|
+
}
|
|
3733
|
+
/**
|
|
3734
|
+
* Get number of registered tools
|
|
3735
|
+
*/ count() {
|
|
3736
|
+
return this.tools.size;
|
|
3737
|
+
}
|
|
3738
|
+
/**
|
|
3739
|
+
* Execute a tool by name
|
|
3740
|
+
*/ async execute(name, params) {
|
|
3741
|
+
const tool = this.tools.get(name);
|
|
3742
|
+
if (!tool) {
|
|
3743
|
+
throw new Error(`Tool "${name}" not found`);
|
|
3744
|
+
}
|
|
3745
|
+
this.logger.debug('Executing tool', {
|
|
3746
|
+
name,
|
|
3747
|
+
params
|
|
3748
|
+
});
|
|
3749
|
+
const startTime = Date.now();
|
|
3750
|
+
const stats = this.usageStats.get(name);
|
|
3751
|
+
stats.calls++;
|
|
3752
|
+
try {
|
|
3753
|
+
const result = await tool.execute(params, this.context);
|
|
3754
|
+
const duration = Date.now() - startTime;
|
|
3755
|
+
stats.totalDuration += duration;
|
|
3756
|
+
this.logger.debug('Tool execution succeeded', {
|
|
3757
|
+
name,
|
|
3758
|
+
duration
|
|
3759
|
+
});
|
|
3760
|
+
return result;
|
|
3761
|
+
} catch (error) {
|
|
3762
|
+
stats.failures++;
|
|
3763
|
+
this.logger.error('Tool execution failed', {
|
|
3764
|
+
name,
|
|
3765
|
+
error
|
|
3766
|
+
});
|
|
3767
|
+
throw error;
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
/**
|
|
3771
|
+
* Execute multiple tools in sequence
|
|
3772
|
+
*/ async executeBatch(calls) {
|
|
3773
|
+
this.logger.debug('Executing batch', {
|
|
3774
|
+
count: calls.length
|
|
3775
|
+
});
|
|
3776
|
+
const results = [];
|
|
3777
|
+
for (const call of calls){
|
|
3778
|
+
try {
|
|
3779
|
+
const result = await this.execute(call.name, call.params);
|
|
3780
|
+
results.push(result);
|
|
3781
|
+
} catch (error) {
|
|
3782
|
+
results.push({
|
|
3783
|
+
error: String(error)
|
|
3784
|
+
});
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
3787
|
+
return results;
|
|
3788
|
+
}
|
|
3789
|
+
/**
|
|
3790
|
+
* Export tools in OpenAI format
|
|
3791
|
+
*/ toOpenAIFormat() {
|
|
3792
|
+
return this.getAll().map((tool)=>({
|
|
3793
|
+
type: 'function',
|
|
3794
|
+
function: {
|
|
3795
|
+
name: tool.name,
|
|
3796
|
+
description: tool.description,
|
|
3797
|
+
parameters: {
|
|
3798
|
+
type: 'object',
|
|
3799
|
+
properties: tool.parameters.properties,
|
|
3800
|
+
required: tool.parameters.required
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3803
|
+
}));
|
|
3804
|
+
}
|
|
3805
|
+
/**
|
|
3806
|
+
* Export tools in Anthropic format
|
|
3807
|
+
*/ toAnthropicFormat() {
|
|
3808
|
+
return this.getAll().map((tool)=>({
|
|
3809
|
+
name: tool.name,
|
|
3810
|
+
description: tool.description,
|
|
3811
|
+
input_schema: {
|
|
3812
|
+
type: 'object',
|
|
3813
|
+
properties: tool.parameters.properties,
|
|
3814
|
+
required: tool.parameters.required
|
|
3815
|
+
}
|
|
3816
|
+
}));
|
|
3817
|
+
}
|
|
3818
|
+
/**
|
|
3819
|
+
* Get tool definitions (without execute function)
|
|
3820
|
+
*/ getDefinitions() {
|
|
3821
|
+
return this.getAll().map((tool)=>({
|
|
3822
|
+
name: tool.name,
|
|
3823
|
+
description: tool.description,
|
|
3824
|
+
parameters: tool.parameters,
|
|
3825
|
+
category: tool.category,
|
|
3826
|
+
cost: tool.cost,
|
|
3827
|
+
examples: tool.examples
|
|
3828
|
+
}));
|
|
3829
|
+
}
|
|
3830
|
+
/**
|
|
3831
|
+
* Get usage statistics for all tools
|
|
3832
|
+
*/ getUsageStats() {
|
|
3833
|
+
const stats = new Map();
|
|
3834
|
+
this.usageStats.forEach((rawStats, name)=>{
|
|
3835
|
+
stats.set(name, {
|
|
3836
|
+
calls: rawStats.calls,
|
|
3837
|
+
failures: rawStats.failures,
|
|
3838
|
+
successRate: rawStats.calls > 0 ? (rawStats.calls - rawStats.failures) / rawStats.calls : 0,
|
|
3839
|
+
averageDuration: rawStats.calls > 0 ? rawStats.totalDuration / rawStats.calls : undefined
|
|
3840
|
+
});
|
|
3841
|
+
});
|
|
3842
|
+
return stats;
|
|
3843
|
+
}
|
|
3844
|
+
/**
|
|
3845
|
+
* Get most frequently used tools
|
|
3846
|
+
*/ getMostUsed(limit = 5) {
|
|
3847
|
+
const sorted = Array.from(this.usageStats.entries()).sort((a, b)=>b[1].calls - a[1].calls).slice(0, limit).map(([name])=>this.tools.get(name)).filter((tool)=>tool !== undefined);
|
|
3848
|
+
return sorted;
|
|
3849
|
+
}
|
|
3850
|
+
/**
|
|
3851
|
+
* Get list of all categories
|
|
3852
|
+
*/ getCategories() {
|
|
3853
|
+
const categories = new Set();
|
|
3854
|
+
this.getAll().forEach((tool)=>{
|
|
3855
|
+
if (tool.category) {
|
|
3856
|
+
categories.add(tool.category);
|
|
3857
|
+
}
|
|
3858
|
+
});
|
|
3859
|
+
return Array.from(categories).sort();
|
|
3860
|
+
}
|
|
3861
|
+
/**
|
|
3862
|
+
* Update execution context
|
|
3863
|
+
*/ updateContext(context) {
|
|
3864
|
+
this.context = {
|
|
3865
|
+
...this.context,
|
|
3866
|
+
...context
|
|
3867
|
+
};
|
|
3868
|
+
this.logger.debug('Updated context', {
|
|
3869
|
+
keys: Object.keys(context)
|
|
3870
|
+
});
|
|
3871
|
+
}
|
|
3872
|
+
/**
|
|
3873
|
+
* Get current context
|
|
3874
|
+
*/ getContext() {
|
|
3875
|
+
return {
|
|
3876
|
+
...this.context
|
|
3877
|
+
};
|
|
3878
|
+
}
|
|
3879
|
+
/**
|
|
3880
|
+
* Clear all tools
|
|
3881
|
+
*/ clear() {
|
|
3882
|
+
this.logger.debug('Clearing all tools');
|
|
3883
|
+
this.tools.clear();
|
|
3884
|
+
this.usageStats.clear();
|
|
3885
|
+
}
|
|
3886
|
+
/**
|
|
3887
|
+
* Unregister a specific tool
|
|
3888
|
+
*/ unregister(name) {
|
|
3889
|
+
if (this.tools.has(name)) {
|
|
3890
|
+
this.tools.delete(name);
|
|
3891
|
+
this.usageStats.delete(name);
|
|
3892
|
+
this.logger.debug('Unregistered tool', {
|
|
3893
|
+
name
|
|
3894
|
+
});
|
|
3895
|
+
return true;
|
|
3896
|
+
}
|
|
3897
|
+
return false;
|
|
3898
|
+
}
|
|
3899
|
+
/**
|
|
3900
|
+
* Reset usage statistics
|
|
3901
|
+
*/ resetStats() {
|
|
3902
|
+
this.logger.debug('Resetting usage statistics');
|
|
3903
|
+
this.usageStats.forEach((stats)=>{
|
|
3904
|
+
stats.calls = 0;
|
|
3905
|
+
stats.failures = 0;
|
|
3906
|
+
stats.totalDuration = 0;
|
|
3907
|
+
});
|
|
3908
|
+
}
|
|
3909
|
+
constructor(context = {}, logger){
|
|
3910
|
+
_define_property$2(this, "tools", void 0);
|
|
3911
|
+
_define_property$2(this, "context", void 0);
|
|
3912
|
+
_define_property$2(this, "logger", void 0);
|
|
3913
|
+
_define_property$2(this, "usageStats", void 0);
|
|
3914
|
+
this.tools = new Map();
|
|
3915
|
+
this.context = context;
|
|
3916
|
+
this.logger = wrapLogger(logger || DEFAULT_LOGGER, 'ToolRegistry');
|
|
3917
|
+
this.usageStats = new Map();
|
|
3918
|
+
this.logger.debug('Created ToolRegistry');
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
|
|
3922
|
+
function _define_property$1(obj, key, value) {
|
|
3923
|
+
if (key in obj) {
|
|
3924
|
+
Object.defineProperty(obj, key, {
|
|
3925
|
+
value: value,
|
|
3926
|
+
enumerable: true,
|
|
3927
|
+
configurable: true,
|
|
3928
|
+
writable: true
|
|
3929
|
+
});
|
|
3930
|
+
} else {
|
|
3931
|
+
obj[key] = value;
|
|
3932
|
+
}
|
|
3933
|
+
return obj;
|
|
3934
|
+
}
|
|
3935
|
+
// ===== METRICS COLLECTOR =====
|
|
3936
|
+
/**
|
|
3937
|
+
* MetricsCollector gathers execution metrics during agentic execution.
|
|
3938
|
+
*
|
|
3939
|
+
* @example
|
|
3940
|
+
* ```typescript
|
|
3941
|
+
* const collector = new MetricsCollector();
|
|
3942
|
+
*
|
|
3943
|
+
* collector.recordToolCall('read_file', iteration, duration, true);
|
|
3944
|
+
* collector.recordToolCall('search_code', iteration, duration, false, error);
|
|
3945
|
+
*
|
|
3946
|
+
* const metrics = collector.getMetrics(messages);
|
|
3947
|
+
* ```
|
|
3948
|
+
*/ class MetricsCollector {
|
|
3949
|
+
/**
|
|
3950
|
+
* Record a tool execution
|
|
3951
|
+
*/ recordToolCall(name, iteration, duration, success, error, inputSize, outputSize) {
|
|
3952
|
+
this.toolMetrics.push({
|
|
3953
|
+
name,
|
|
3954
|
+
iteration,
|
|
3955
|
+
timestamp: new Date().toISOString(),
|
|
3956
|
+
duration,
|
|
3957
|
+
success,
|
|
3958
|
+
error,
|
|
3959
|
+
inputSize,
|
|
3960
|
+
outputSize
|
|
3961
|
+
});
|
|
3962
|
+
}
|
|
3963
|
+
/**
|
|
3964
|
+
* Increment iteration count
|
|
3965
|
+
*/ incrementIteration() {
|
|
3966
|
+
this.iterationCount++;
|
|
3967
|
+
}
|
|
3968
|
+
/**
|
|
3969
|
+
* Get complete metrics
|
|
3970
|
+
*/ getMetrics(messages, model) {
|
|
3971
|
+
const endTime = new Date();
|
|
3972
|
+
const totalDuration = endTime.getTime() - this.startTime.getTime();
|
|
3973
|
+
// Calculate tool statistics
|
|
3974
|
+
const toolStats = this.calculateToolStats();
|
|
3975
|
+
// Count unique tools
|
|
3976
|
+
const uniqueTools = new Set(this.toolMetrics.map((m)=>m.name));
|
|
3977
|
+
// Calculate investigation depth
|
|
3978
|
+
const totalTools = this.toolMetrics.length;
|
|
3979
|
+
const investigationDepth = totalTools < 3 ? 'shallow' : totalTools < 8 ? 'moderate' : 'deep';
|
|
3980
|
+
// Calculate iteration efficiency
|
|
3981
|
+
const iterationEfficiency = this.iterationCount > 0 ? totalTools / this.iterationCount : 0;
|
|
3982
|
+
// Calculate token usage if model provided
|
|
3983
|
+
let tokenUsage;
|
|
3984
|
+
if (model) {
|
|
3985
|
+
let counter;
|
|
3986
|
+
try {
|
|
3987
|
+
counter = new TokenCounter(model);
|
|
3988
|
+
const total = counter.countConversation(messages);
|
|
3989
|
+
tokenUsage = {
|
|
3990
|
+
total,
|
|
3991
|
+
systemPrompt: 0,
|
|
3992
|
+
userContent: 0,
|
|
3993
|
+
toolResults: 0,
|
|
3994
|
+
conversation: total
|
|
3995
|
+
};
|
|
3996
|
+
} catch (error) {
|
|
3997
|
+
this.logger.warn('Could not calculate token usage', {
|
|
3998
|
+
error
|
|
3999
|
+
});
|
|
4000
|
+
} finally{
|
|
4001
|
+
// Always dispose of the counter to prevent resource leaks
|
|
4002
|
+
counter === null || counter === void 0 ? void 0 : counter.dispose();
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
return {
|
|
4006
|
+
startTime: this.startTime,
|
|
4007
|
+
endTime,
|
|
4008
|
+
totalDuration,
|
|
4009
|
+
iterations: this.iterationCount,
|
|
4010
|
+
toolCallsExecuted: this.toolMetrics.length,
|
|
4011
|
+
toolMetrics: this.toolMetrics,
|
|
4012
|
+
toolStats,
|
|
4013
|
+
messageCount: messages.length,
|
|
4014
|
+
tokenUsage,
|
|
4015
|
+
investigationDepth,
|
|
4016
|
+
toolDiversity: uniqueTools.size,
|
|
4017
|
+
iterationEfficiency
|
|
4018
|
+
};
|
|
4019
|
+
}
|
|
4020
|
+
/**
|
|
4021
|
+
* Calculate aggregated tool statistics
|
|
4022
|
+
*/ calculateToolStats() {
|
|
4023
|
+
const stats = new Map();
|
|
4024
|
+
// Group by tool name
|
|
4025
|
+
const byTool = new Map();
|
|
4026
|
+
for (const metric of this.toolMetrics){
|
|
4027
|
+
if (!byTool.has(metric.name)) {
|
|
4028
|
+
byTool.set(metric.name, []);
|
|
4029
|
+
}
|
|
4030
|
+
byTool.get(metric.name).push(metric);
|
|
4031
|
+
}
|
|
4032
|
+
// Calculate stats for each tool
|
|
4033
|
+
for (const [name, metrics] of byTool){
|
|
4034
|
+
const total = metrics.length;
|
|
4035
|
+
const success = metrics.filter((m)=>m.success).length;
|
|
4036
|
+
const failures = total - success;
|
|
4037
|
+
const totalDuration = metrics.reduce((sum, m)=>sum + m.duration, 0);
|
|
4038
|
+
const avgDuration = totalDuration / total;
|
|
4039
|
+
const successRate = total > 0 ? success / total : 0;
|
|
4040
|
+
stats.set(name, {
|
|
4041
|
+
name,
|
|
4042
|
+
total,
|
|
4043
|
+
success,
|
|
4044
|
+
failures,
|
|
4045
|
+
totalDuration,
|
|
4046
|
+
avgDuration,
|
|
4047
|
+
successRate
|
|
4048
|
+
});
|
|
4049
|
+
}
|
|
4050
|
+
return stats;
|
|
4051
|
+
}
|
|
4052
|
+
constructor(logger){
|
|
4053
|
+
_define_property$1(this, "startTime", void 0);
|
|
4054
|
+
_define_property$1(this, "toolMetrics", void 0);
|
|
4055
|
+
_define_property$1(this, "iterationCount", void 0);
|
|
4056
|
+
_define_property$1(this, "logger", void 0);
|
|
4057
|
+
this.startTime = new Date();
|
|
4058
|
+
this.toolMetrics = [];
|
|
4059
|
+
this.iterationCount = 0;
|
|
4060
|
+
this.logger = wrapLogger(logger || DEFAULT_LOGGER, 'MetricsCollector');
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
// ===== REFLECTION REPORT GENERATOR =====
|
|
4064
|
+
/**
|
|
4065
|
+
* ReflectionReportGenerator generates analysis reports from execution metrics.
|
|
4066
|
+
*
|
|
4067
|
+
* @example
|
|
4068
|
+
* ```typescript
|
|
4069
|
+
* const generator = new ReflectionReportGenerator();
|
|
4070
|
+
* const report = generator.generate(metrics, result);
|
|
4071
|
+
*
|
|
4072
|
+
* console.log('Success rate:', report.toolEffectiveness.overallSuccessRate);
|
|
4073
|
+
* console.log('Recommendations:', report.recommendations.length);
|
|
4074
|
+
* ```
|
|
4075
|
+
*/ class ReflectionReportGenerator {
|
|
4076
|
+
/**
|
|
4077
|
+
* Generate reflection report
|
|
4078
|
+
*/ generate(metrics, result) {
|
|
4079
|
+
var _result_finalMessage;
|
|
4080
|
+
this.logger.debug('Generating reflection report');
|
|
4081
|
+
const report = {
|
|
4082
|
+
id: `reflection-${Date.now()}`,
|
|
4083
|
+
generated: new Date(),
|
|
4084
|
+
summary: this.generateSummary(metrics),
|
|
4085
|
+
toolEffectiveness: this.analyzeToolEffectiveness(metrics),
|
|
4086
|
+
performanceInsights: this.analyzePerformance(metrics),
|
|
4087
|
+
timeline: this.buildTimeline(metrics),
|
|
4088
|
+
tokenUsage: metrics.tokenUsage,
|
|
4089
|
+
qualityAssessment: this.assessQuality(metrics),
|
|
4090
|
+
recommendations: this.generateRecommendations(metrics, result),
|
|
4091
|
+
conversationHistory: result.conversation.getMessages(),
|
|
4092
|
+
output: ((_result_finalMessage = result.finalMessage) === null || _result_finalMessage === void 0 ? void 0 : _result_finalMessage.content) || undefined
|
|
4093
|
+
};
|
|
4094
|
+
this.logger.info('Generated reflection report', {
|
|
4095
|
+
recommendations: report.recommendations.length,
|
|
4096
|
+
toolsAnalyzed: metrics.toolStats.size
|
|
4097
|
+
});
|
|
4098
|
+
return report;
|
|
4099
|
+
}
|
|
4100
|
+
/**
|
|
4101
|
+
* Generate execution summary
|
|
4102
|
+
*/ generateSummary(metrics) {
|
|
4103
|
+
const successfulTools = metrics.toolMetrics.filter((m)=>m.success).length;
|
|
4104
|
+
const successRate = metrics.toolMetrics.length > 0 ? successfulTools / metrics.toolMetrics.length : 0;
|
|
4105
|
+
return {
|
|
4106
|
+
startTime: metrics.startTime,
|
|
4107
|
+
endTime: metrics.endTime || new Date(),
|
|
4108
|
+
totalDuration: metrics.totalDuration,
|
|
4109
|
+
iterations: metrics.iterations,
|
|
4110
|
+
toolCallsExecuted: metrics.toolCallsExecuted,
|
|
4111
|
+
uniqueToolsUsed: metrics.toolDiversity,
|
|
4112
|
+
successRate
|
|
4113
|
+
};
|
|
4114
|
+
}
|
|
4115
|
+
/**
|
|
4116
|
+
* Analyze tool effectiveness
|
|
4117
|
+
*/ analyzeToolEffectiveness(metrics) {
|
|
4118
|
+
const successfulTools = metrics.toolMetrics.filter((m)=>m.success).length;
|
|
4119
|
+
const overallSuccessRate = metrics.toolMetrics.length > 0 ? successfulTools / metrics.toolMetrics.length : 1;
|
|
4120
|
+
// Find failed tools
|
|
4121
|
+
const failedTools = Array.from(metrics.toolStats.values()).filter((stats)=>stats.failures > 0).map((stats)=>({
|
|
4122
|
+
name: stats.name,
|
|
4123
|
+
failures: stats.failures,
|
|
4124
|
+
rate: stats.successRate
|
|
4125
|
+
})).sort((a, b)=>b.failures - a.failures);
|
|
4126
|
+
// Find slow tools (>1s average)
|
|
4127
|
+
const slowTools = Array.from(metrics.toolStats.values()).filter((stats)=>stats.avgDuration > 1000).map((stats)=>({
|
|
4128
|
+
name: stats.name,
|
|
4129
|
+
avgDuration: stats.avgDuration
|
|
4130
|
+
})).sort((a, b)=>b.avgDuration - a.avgDuration);
|
|
4131
|
+
// Most used tools
|
|
4132
|
+
const mostUsedTools = Array.from(metrics.toolStats.values()).map((stats)=>({
|
|
4133
|
+
name: stats.name,
|
|
4134
|
+
count: stats.total
|
|
4135
|
+
})).sort((a, b)=>b.count - a.count).slice(0, 5);
|
|
4136
|
+
return {
|
|
4137
|
+
overallSuccessRate,
|
|
4138
|
+
toolStats: metrics.toolStats,
|
|
4139
|
+
failedTools,
|
|
4140
|
+
slowTools,
|
|
4141
|
+
mostUsedTools
|
|
4142
|
+
};
|
|
4143
|
+
}
|
|
4144
|
+
/**
|
|
4145
|
+
* Analyze performance
|
|
4146
|
+
*/ analyzePerformance(metrics) {
|
|
4147
|
+
const avgIterationDuration = metrics.iterations > 0 ? metrics.totalDuration / metrics.iterations : 0;
|
|
4148
|
+
// Find slowest and fastest tools
|
|
4149
|
+
const toolsBySpeed = Array.from(metrics.toolStats.values()).sort((a, b)=>b.avgDuration - a.avgDuration);
|
|
4150
|
+
const slowestTool = toolsBySpeed[0] ? {
|
|
4151
|
+
name: toolsBySpeed[0].name,
|
|
4152
|
+
duration: toolsBySpeed[0].avgDuration
|
|
4153
|
+
} : undefined;
|
|
4154
|
+
const fastestTool = toolsBySpeed[toolsBySpeed.length - 1] ? {
|
|
4155
|
+
name: toolsBySpeed[toolsBySpeed.length - 1].name,
|
|
4156
|
+
duration: toolsBySpeed[toolsBySpeed.length - 1].avgDuration
|
|
4157
|
+
} : undefined;
|
|
4158
|
+
// Identify bottlenecks
|
|
4159
|
+
const bottlenecks = [];
|
|
4160
|
+
if (slowestTool && slowestTool.duration > 1000) {
|
|
4161
|
+
bottlenecks.push(`${slowestTool.name} averaging ${slowestTool.duration}ms`);
|
|
4162
|
+
}
|
|
4163
|
+
if (avgIterationDuration > 10000) {
|
|
4164
|
+
bottlenecks.push(`Slow iterations averaging ${avgIterationDuration.toFixed(0)}ms`);
|
|
4165
|
+
}
|
|
4166
|
+
return {
|
|
4167
|
+
totalDuration: metrics.totalDuration,
|
|
4168
|
+
avgIterationDuration,
|
|
4169
|
+
slowestTool,
|
|
4170
|
+
fastestTool,
|
|
4171
|
+
bottlenecks
|
|
4172
|
+
};
|
|
4173
|
+
}
|
|
4174
|
+
/**
|
|
4175
|
+
* Build execution timeline
|
|
4176
|
+
*/ buildTimeline(metrics) {
|
|
4177
|
+
const events = [];
|
|
4178
|
+
for (const metric of metrics.toolMetrics){
|
|
4179
|
+
events.push({
|
|
4180
|
+
timestamp: metric.timestamp,
|
|
4181
|
+
iteration: metric.iteration,
|
|
4182
|
+
type: 'tool-call',
|
|
4183
|
+
description: `${metric.name}(${metric.success ? 'success' : 'failure'})`,
|
|
4184
|
+
duration: metric.duration,
|
|
4185
|
+
success: metric.success
|
|
4186
|
+
});
|
|
4187
|
+
}
|
|
4188
|
+
return events.sort((a, b)=>new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
4189
|
+
}
|
|
4190
|
+
/**
|
|
4191
|
+
* Assess investigation quality
|
|
4192
|
+
*/ assessQuality(metrics) {
|
|
4193
|
+
const toolDiversity = metrics.toolDiversity;
|
|
4194
|
+
const iterationEfficiency = metrics.iterationEfficiency;
|
|
4195
|
+
// Calculate coverage (tools / iterations - aim for 1-2)
|
|
4196
|
+
const coverage = metrics.iterations > 0 ? Math.min(1, metrics.toolCallsExecuted / (metrics.iterations * 2)) : 0;
|
|
4197
|
+
// Overall quality score (0-1)
|
|
4198
|
+
const depthScore = metrics.investigationDepth === 'deep' ? 1 : metrics.investigationDepth === 'moderate' ? 0.7 : 0.3;
|
|
4199
|
+
const diversityScore = Math.min(1, toolDiversity / 5); // 5+ tools = max score
|
|
4200
|
+
const efficiencyScore = Math.min(1, iterationEfficiency / 2); // 2 tools/iteration = max
|
|
4201
|
+
const overall = (depthScore + diversityScore + efficiencyScore) / 3;
|
|
4202
|
+
return {
|
|
4203
|
+
investigationDepth: metrics.investigationDepth,
|
|
4204
|
+
toolDiversity,
|
|
4205
|
+
iterationEfficiency,
|
|
4206
|
+
coverage,
|
|
4207
|
+
overall
|
|
4208
|
+
};
|
|
4209
|
+
}
|
|
4210
|
+
/**
|
|
4211
|
+
* Generate recommendations
|
|
4212
|
+
*/ generateRecommendations(metrics, _result) {
|
|
4213
|
+
const recommendations = [];
|
|
4214
|
+
// Check for tool failures
|
|
4215
|
+
const failedTools = Array.from(metrics.toolStats.values()).filter((stats)=>stats.failures > 0);
|
|
4216
|
+
if (failedTools.length > 0) {
|
|
4217
|
+
recommendations.push({
|
|
4218
|
+
type: 'tool-failure',
|
|
4219
|
+
severity: 'high',
|
|
4220
|
+
message: `${failedTools.length} tool(s) had failures. Review tool implementations.`,
|
|
4221
|
+
suggestion: 'Check error logs and validate tool parameters',
|
|
4222
|
+
relatedTools: failedTools.map((t)=>t.name)
|
|
4223
|
+
});
|
|
4224
|
+
}
|
|
4225
|
+
// Check for shallow investigation
|
|
4226
|
+
if (metrics.investigationDepth === 'shallow' && metrics.toolCallsExecuted < 2) {
|
|
4227
|
+
recommendations.push({
|
|
4228
|
+
type: 'investigation-depth',
|
|
4229
|
+
severity: 'medium',
|
|
4230
|
+
message: 'Investigation was shallow. Consider adjusting strategy to encourage more tool usage.',
|
|
4231
|
+
suggestion: 'Use investigateThenRespond strategy with requireMinimumTools'
|
|
4232
|
+
});
|
|
4233
|
+
}
|
|
4234
|
+
// Check for slow tools
|
|
4235
|
+
const slowTools = Array.from(metrics.toolStats.values()).filter((stats)=>stats.avgDuration > 1000);
|
|
4236
|
+
if (slowTools.length > 0) {
|
|
4237
|
+
recommendations.push({
|
|
4238
|
+
type: 'performance',
|
|
4239
|
+
severity: 'medium',
|
|
4240
|
+
message: `${slowTools.length} tool(s) taking >1s. Consider optimization.`,
|
|
4241
|
+
suggestion: 'Add caching, reduce scope, or optimize implementations',
|
|
4242
|
+
relatedTools: slowTools.map((t)=>t.name)
|
|
4243
|
+
});
|
|
4244
|
+
}
|
|
4245
|
+
// Check token usage
|
|
4246
|
+
if (metrics.tokenUsage) {
|
|
4247
|
+
if (metrics.tokenUsage.percentage && metrics.tokenUsage.percentage > 80) {
|
|
4248
|
+
recommendations.push({
|
|
4249
|
+
type: 'token-budget',
|
|
4250
|
+
severity: 'high',
|
|
4251
|
+
message: `Token usage at ${metrics.tokenUsage.percentage.toFixed(1)}%. Increase budget or enable compression.`,
|
|
4252
|
+
suggestion: 'Increase max tokens or use priority-based compression'
|
|
4253
|
+
});
|
|
4254
|
+
}
|
|
4255
|
+
}
|
|
4256
|
+
return recommendations;
|
|
4257
|
+
}
|
|
4258
|
+
/**
|
|
4259
|
+
* Format report as markdown
|
|
4260
|
+
*/ formatMarkdown(report) {
|
|
4261
|
+
let markdown = `# Agentic Execution - Self-Reflection Report\n\n`;
|
|
4262
|
+
markdown += `**Generated:** ${report.generated.toISOString()}\n`;
|
|
4263
|
+
markdown += `**Duration:** ${(report.summary.totalDuration / 1000).toFixed(1)}s\n\n`;
|
|
4264
|
+
markdown += `## Execution Summary\n\n`;
|
|
4265
|
+
markdown += `- **Iterations**: ${report.summary.iterations}\n`;
|
|
4266
|
+
markdown += `- **Tool Calls**: ${report.summary.toolCallsExecuted}\n`;
|
|
4267
|
+
markdown += `- **Unique Tools**: ${report.summary.uniqueToolsUsed}\n`;
|
|
4268
|
+
markdown += `- **Investigation Depth**: ${report.qualityAssessment.investigationDepth}\n`;
|
|
4269
|
+
markdown += `- **Success Rate**: ${(report.summary.successRate * 100).toFixed(1)}%\n\n`;
|
|
4270
|
+
markdown += `## Tool Effectiveness Analysis\n\n`;
|
|
4271
|
+
markdown += `| Tool | Calls | Success | Failures | Success Rate | Avg Duration |\n`;
|
|
4272
|
+
markdown += `|------|-------|---------|----------|--------------|---------------|\n`;
|
|
4273
|
+
for (const [name, stats] of report.toolEffectiveness.toolStats){
|
|
4274
|
+
markdown += `| ${name} | ${stats.total} | ${stats.success} | ${stats.failures} | `;
|
|
4275
|
+
markdown += `${(stats.successRate * 100).toFixed(1)}% | ${stats.avgDuration.toFixed(0)}ms |\n`;
|
|
4276
|
+
}
|
|
4277
|
+
if (report.toolEffectiveness.failedTools.length > 0) {
|
|
4278
|
+
markdown += `\n### Tools with Failures\n\n`;
|
|
4279
|
+
for (const tool of report.toolEffectiveness.failedTools){
|
|
4280
|
+
markdown += `- **${tool.name}**: ${tool.failures} failures (${(tool.rate * 100).toFixed(1)}% success)\n`;
|
|
4281
|
+
}
|
|
4282
|
+
}
|
|
4283
|
+
if (report.toolEffectiveness.slowTools.length > 0) {
|
|
4284
|
+
markdown += `\n### Slow Tools (>1s average)\n\n`;
|
|
4285
|
+
for (const tool of report.toolEffectiveness.slowTools){
|
|
4286
|
+
markdown += `- **${tool.name}**: ${(tool.avgDuration / 1000).toFixed(2)}s average\n`;
|
|
4287
|
+
}
|
|
4288
|
+
}
|
|
4289
|
+
markdown += `\n## Quality Assessment\n\n`;
|
|
4290
|
+
markdown += `- **Overall Score**: ${(report.qualityAssessment.overall * 100).toFixed(0)}%\n`;
|
|
4291
|
+
markdown += `- **Investigation Depth**: ${report.qualityAssessment.investigationDepth}\n`;
|
|
4292
|
+
markdown += `- **Tool Diversity**: ${report.qualityAssessment.toolDiversity} unique tools\n`;
|
|
4293
|
+
markdown += `- **Efficiency**: ${report.qualityAssessment.iterationEfficiency.toFixed(2)} tools per iteration\n\n`;
|
|
4294
|
+
if (report.recommendations.length > 0) {
|
|
4295
|
+
markdown += `## Recommendations\n\n`;
|
|
4296
|
+
const byPriority = {
|
|
4297
|
+
high: report.recommendations.filter((r)=>r.severity === 'high'),
|
|
4298
|
+
medium: report.recommendations.filter((r)=>r.severity === 'medium'),
|
|
4299
|
+
low: report.recommendations.filter((r)=>r.severity === 'low')
|
|
4300
|
+
};
|
|
4301
|
+
if (byPriority.high.length > 0) {
|
|
4302
|
+
markdown += `### 🔴 High Priority\n\n`;
|
|
4303
|
+
byPriority.high.forEach((rec, i)=>{
|
|
4304
|
+
markdown += `${i + 1}. **${rec.message}**\n`;
|
|
4305
|
+
if (rec.suggestion) {
|
|
4306
|
+
markdown += ` - Suggestion: ${rec.suggestion}\n`;
|
|
4307
|
+
}
|
|
4308
|
+
markdown += `\n`;
|
|
4309
|
+
});
|
|
4310
|
+
}
|
|
4311
|
+
if (byPriority.medium.length > 0) {
|
|
4312
|
+
markdown += `### 🟡 Medium Priority\n\n`;
|
|
4313
|
+
byPriority.medium.forEach((rec, i)=>{
|
|
4314
|
+
markdown += `${i + 1}. **${rec.message}**\n`;
|
|
4315
|
+
if (rec.suggestion) {
|
|
4316
|
+
markdown += ` - Suggestion: ${rec.suggestion}\n`;
|
|
4317
|
+
}
|
|
4318
|
+
markdown += `\n`;
|
|
4319
|
+
});
|
|
4320
|
+
}
|
|
4321
|
+
}
|
|
4322
|
+
if (report.output) {
|
|
4323
|
+
markdown += `## Final Output\n\n`;
|
|
4324
|
+
markdown += `\`\`\`\n${report.output}\n\`\`\`\n\n`;
|
|
4325
|
+
}
|
|
4326
|
+
markdown += `---\n\n`;
|
|
4327
|
+
markdown += `*Report generated by RiotPrompt Agentic Reflection System*\n`;
|
|
4328
|
+
return markdown;
|
|
4329
|
+
}
|
|
4330
|
+
constructor(logger){
|
|
4331
|
+
_define_property$1(this, "logger", void 0);
|
|
4332
|
+
this.logger = wrapLogger(logger || DEFAULT_LOGGER, 'ReflectionReportGenerator');
|
|
4333
|
+
}
|
|
4334
|
+
}
|
|
4335
|
+
|
|
4336
|
+
function _define_property(obj, key, value) {
|
|
4337
|
+
if (key in obj) {
|
|
4338
|
+
Object.defineProperty(obj, key, {
|
|
4339
|
+
value: value,
|
|
4340
|
+
enumerable: true,
|
|
4341
|
+
configurable: true,
|
|
4342
|
+
writable: true
|
|
4343
|
+
});
|
|
4344
|
+
} else {
|
|
4345
|
+
obj[key] = value;
|
|
4346
|
+
}
|
|
4347
|
+
return obj;
|
|
4348
|
+
}
|
|
4349
|
+
// ===== STRATEGY EXECUTOR =====
|
|
4350
|
+
/**
|
|
4351
|
+
* StrategyExecutor executes iteration strategies.
|
|
4352
|
+
*
|
|
4353
|
+
* Features:
|
|
4354
|
+
* - Execute multi-phase strategies
|
|
4355
|
+
* - Manage tool calls and results
|
|
4356
|
+
* - Track state and metrics
|
|
4357
|
+
* - Handle timeouts and errors
|
|
4358
|
+
* - Provide lifecycle hooks
|
|
4359
|
+
*
|
|
4360
|
+
* @example
|
|
4361
|
+
* ```typescript
|
|
4362
|
+
* const executor = new StrategyExecutor(llmClient);
|
|
4363
|
+
*
|
|
4364
|
+
* const result = await executor.execute(
|
|
4365
|
+
* conversation,
|
|
4366
|
+
* toolRegistry,
|
|
4367
|
+
* strategy
|
|
4368
|
+
* );
|
|
4369
|
+
*
|
|
4370
|
+
* console.log('Completed in', result.totalIterations, 'iterations');
|
|
4371
|
+
* console.log('Used', result.toolCallsExecuted, 'tools');
|
|
4372
|
+
* ```
|
|
4373
|
+
*/ class StrategyExecutor {
|
|
4374
|
+
/**
|
|
4375
|
+
* Enable reflection generation
|
|
4376
|
+
*/ withReflection(config) {
|
|
4377
|
+
this.reflectionConfig = config;
|
|
4378
|
+
return this;
|
|
4379
|
+
}
|
|
4380
|
+
/**
|
|
4381
|
+
* Execute a strategy
|
|
4382
|
+
*/ async execute(conversation, tools, strategy) {
|
|
4383
|
+
var _this_reflectionConfig;
|
|
4384
|
+
const startTime = Date.now();
|
|
4385
|
+
// Initialize metrics collector if reflection enabled
|
|
4386
|
+
if ((_this_reflectionConfig = this.reflectionConfig) === null || _this_reflectionConfig === void 0 ? void 0 : _this_reflectionConfig.enabled) {
|
|
4387
|
+
this.metricsCollector = new MetricsCollector(this.logger);
|
|
4388
|
+
}
|
|
4389
|
+
const state = {
|
|
4390
|
+
phase: 0,
|
|
4391
|
+
iteration: 0,
|
|
4392
|
+
toolCallsExecuted: 0,
|
|
4393
|
+
startTime,
|
|
4394
|
+
insights: [],
|
|
4395
|
+
findings: [],
|
|
4396
|
+
errors: [],
|
|
4397
|
+
toolFailures: new Map()
|
|
4398
|
+
};
|
|
4399
|
+
this.logger.info('Starting strategy execution', {
|
|
4400
|
+
strategy: strategy.name
|
|
4401
|
+
});
|
|
4402
|
+
const context = {
|
|
4403
|
+
conversation,
|
|
4404
|
+
tools,
|
|
4405
|
+
llm: this.llm,
|
|
4406
|
+
state
|
|
4407
|
+
};
|
|
4408
|
+
try {
|
|
4409
|
+
var _strategy_onStart, _this_reflectionConfig1, _strategy_onComplete;
|
|
4410
|
+
// Initialize
|
|
4411
|
+
await ((_strategy_onStart = strategy.onStart) === null || _strategy_onStart === void 0 ? void 0 : _strategy_onStart.call(strategy, context));
|
|
4412
|
+
// Execute phases or single loop
|
|
4413
|
+
const phases = strategy.phases || [
|
|
4414
|
+
{
|
|
4415
|
+
name: 'default',
|
|
4416
|
+
maxIterations: strategy.maxIterations,
|
|
4417
|
+
toolUsage: 'encouraged'
|
|
4418
|
+
}
|
|
4419
|
+
];
|
|
4420
|
+
const phaseResults = [];
|
|
4421
|
+
for (const phase of phases){
|
|
4422
|
+
var _phase_skipIf, _strategy_onPhaseComplete;
|
|
4423
|
+
// Check if should skip phase
|
|
4424
|
+
if ((_phase_skipIf = phase.skipIf) === null || _phase_skipIf === void 0 ? void 0 : _phase_skipIf.call(phase, state)) {
|
|
4425
|
+
this.logger.debug('Skipping phase', {
|
|
4426
|
+
phase: phase.name
|
|
4427
|
+
});
|
|
4428
|
+
continue;
|
|
4429
|
+
}
|
|
4430
|
+
state.phase = phase.name;
|
|
4431
|
+
state.iteration = 0;
|
|
4432
|
+
this.logger.debug('Starting phase', {
|
|
4433
|
+
phase: phase.name
|
|
4434
|
+
});
|
|
4435
|
+
const phaseResult = await this.executePhase(conversation, tools, phase, state, strategy);
|
|
4436
|
+
phaseResults.push(phaseResult);
|
|
4437
|
+
await ((_strategy_onPhaseComplete = strategy.onPhaseComplete) === null || _strategy_onPhaseComplete === void 0 ? void 0 : _strategy_onPhaseComplete.call(strategy, phaseResult, state));
|
|
4438
|
+
// Check if should continue
|
|
4439
|
+
if (strategy.shouldContinue && !strategy.shouldContinue(state)) {
|
|
4440
|
+
this.logger.debug('Strategy decided to stop');
|
|
4441
|
+
break;
|
|
4442
|
+
}
|
|
4443
|
+
}
|
|
4444
|
+
const duration = Date.now() - startTime;
|
|
4445
|
+
const result = {
|
|
4446
|
+
finalMessage: conversation.getLastMessage(),
|
|
4447
|
+
phases: phaseResults,
|
|
4448
|
+
totalIterations: state.iteration,
|
|
4449
|
+
toolCallsExecuted: state.toolCallsExecuted,
|
|
4450
|
+
duration,
|
|
4451
|
+
success: true,
|
|
4452
|
+
conversation
|
|
4453
|
+
};
|
|
4454
|
+
// Generate reflection if enabled
|
|
4455
|
+
if (this.metricsCollector && ((_this_reflectionConfig1 = this.reflectionConfig) === null || _this_reflectionConfig1 === void 0 ? void 0 : _this_reflectionConfig1.enabled)) {
|
|
4456
|
+
const metrics = this.metricsCollector.getMetrics(conversation.getMessages(), conversation.getMetadata().model);
|
|
4457
|
+
const generator = new ReflectionReportGenerator(this.logger);
|
|
4458
|
+
result.reflection = generator.generate(metrics, result);
|
|
4459
|
+
// Save reflection if output path specified
|
|
4460
|
+
if (this.reflectionConfig.outputPath && result.reflection) {
|
|
4461
|
+
await this.saveReflection(result.reflection, this.reflectionConfig);
|
|
4462
|
+
}
|
|
4463
|
+
}
|
|
4464
|
+
await ((_strategy_onComplete = strategy.onComplete) === null || _strategy_onComplete === void 0 ? void 0 : _strategy_onComplete.call(strategy, result));
|
|
4465
|
+
this.logger.info('Strategy execution complete', {
|
|
4466
|
+
iterations: result.totalIterations,
|
|
4467
|
+
toolCalls: result.toolCallsExecuted,
|
|
4468
|
+
duration
|
|
4469
|
+
});
|
|
4470
|
+
return result;
|
|
4471
|
+
} catch (error) {
|
|
4472
|
+
this.logger.error('Strategy execution failed', {
|
|
4473
|
+
error
|
|
4474
|
+
});
|
|
4475
|
+
return {
|
|
4476
|
+
finalMessage: conversation.getLastMessage(),
|
|
4477
|
+
phases: [],
|
|
4478
|
+
totalIterations: state.iteration,
|
|
4479
|
+
toolCallsExecuted: state.toolCallsExecuted,
|
|
4480
|
+
duration: Date.now() - startTime,
|
|
4481
|
+
success: false,
|
|
4482
|
+
conversation
|
|
4483
|
+
};
|
|
4484
|
+
}
|
|
4485
|
+
}
|
|
4486
|
+
/**
|
|
4487
|
+
* Save reflection report
|
|
4488
|
+
*/ async saveReflection(reflection, config) {
|
|
4489
|
+
if (!config.outputPath) {
|
|
4490
|
+
return;
|
|
4491
|
+
}
|
|
4492
|
+
try {
|
|
4493
|
+
const fs = await import('fs/promises');
|
|
4494
|
+
const path = await import('path');
|
|
4495
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
4496
|
+
const filename = `reflection-${timestamp}.${config.format === 'json' ? 'json' : 'md'}`;
|
|
4497
|
+
const fullPath = path.join(config.outputPath, filename);
|
|
4498
|
+
// Ensure directory exists
|
|
4499
|
+
await fs.mkdir(config.outputPath, {
|
|
4500
|
+
recursive: true
|
|
4501
|
+
});
|
|
4502
|
+
// Save based on format
|
|
4503
|
+
if (config.format === 'json') {
|
|
4504
|
+
await fs.writeFile(fullPath, JSON.stringify(reflection, null, 2), 'utf-8');
|
|
4505
|
+
} else {
|
|
4506
|
+
const generator = new ReflectionReportGenerator(this.logger);
|
|
4507
|
+
const markdown = generator.formatMarkdown(reflection);
|
|
4508
|
+
await fs.writeFile(fullPath, markdown, 'utf-8');
|
|
4509
|
+
}
|
|
4510
|
+
this.logger.info('Reflection saved', {
|
|
4511
|
+
path: fullPath
|
|
4512
|
+
});
|
|
4513
|
+
} catch (error) {
|
|
4514
|
+
this.logger.error('Failed to save reflection', {
|
|
4515
|
+
error
|
|
4516
|
+
});
|
|
4517
|
+
}
|
|
4518
|
+
}
|
|
4519
|
+
/**
|
|
4520
|
+
* Execute a single phase
|
|
4521
|
+
*/ async executePhase(conversation, tools, phase, state, strategy) {
|
|
4522
|
+
const phaseStartTools = state.toolCallsExecuted;
|
|
4523
|
+
// Add phase instructions if provided
|
|
4524
|
+
if (phase.instructions) {
|
|
4525
|
+
conversation.asUser(phase.instructions);
|
|
4526
|
+
}
|
|
4527
|
+
// Iteration loop for this phase
|
|
4528
|
+
for(let i = 0; i < phase.maxIterations; i++){
|
|
4529
|
+
var _strategy_onIteration;
|
|
4530
|
+
state.iteration++;
|
|
4531
|
+
// Track iteration for metrics
|
|
4532
|
+
if (this.metricsCollector) {
|
|
4533
|
+
this.metricsCollector.incrementIteration();
|
|
4534
|
+
}
|
|
4535
|
+
this.logger.debug('Iteration', {
|
|
4536
|
+
phase: phase.name,
|
|
4537
|
+
iteration: i + 1
|
|
4538
|
+
});
|
|
4539
|
+
// Check iteration hook
|
|
4540
|
+
const action = await ((_strategy_onIteration = strategy.onIteration) === null || _strategy_onIteration === void 0 ? void 0 : _strategy_onIteration.call(strategy, i, state));
|
|
4541
|
+
if (action === 'stop') {
|
|
4542
|
+
break;
|
|
4543
|
+
}
|
|
4544
|
+
if (action === 'next-phase') {
|
|
4545
|
+
break;
|
|
4546
|
+
}
|
|
4547
|
+
// Get LLM response
|
|
4548
|
+
const toolsToProvide = phase.toolUsage !== 'forbidden' ? tools.toOpenAIFormat() : undefined;
|
|
4549
|
+
const response = await this.llm.complete(conversation.toMessages(), toolsToProvide);
|
|
4550
|
+
// Handle tool calls
|
|
4551
|
+
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
4552
|
+
if (phase.toolUsage === 'forbidden') {
|
|
4553
|
+
this.logger.warn('Tool calls requested but forbidden in this phase');
|
|
4554
|
+
conversation.asAssistant(response.content);
|
|
4555
|
+
continue;
|
|
4556
|
+
}
|
|
4557
|
+
conversation.asAssistant(response.content, response.tool_calls);
|
|
4558
|
+
// Execute tools
|
|
4559
|
+
for (const toolCall of response.tool_calls){
|
|
4560
|
+
var _phase_maxConsecutiveToolFailures;
|
|
4561
|
+
var _strategy_onToolCall;
|
|
4562
|
+
// Check if tool is allowed in this phase
|
|
4563
|
+
if (phase.allowedTools && !phase.allowedTools.includes(toolCall.function.name)) {
|
|
4564
|
+
this.logger.debug('Tool not allowed in phase', {
|
|
4565
|
+
tool: toolCall.function.name
|
|
4566
|
+
});
|
|
4567
|
+
continue;
|
|
4568
|
+
}
|
|
4569
|
+
// Circuit breaker: Check if tool has exceeded failure threshold
|
|
4570
|
+
const maxFailures = (_phase_maxConsecutiveToolFailures = phase.maxConsecutiveToolFailures) !== null && _phase_maxConsecutiveToolFailures !== void 0 ? _phase_maxConsecutiveToolFailures : 3;
|
|
4571
|
+
const consecutiveFailures = state.toolFailures.get(toolCall.function.name) || 0;
|
|
4572
|
+
if (consecutiveFailures >= maxFailures) {
|
|
4573
|
+
this.logger.warn('Tool circuit breaker triggered', {
|
|
4574
|
+
tool: toolCall.function.name,
|
|
4575
|
+
failures: consecutiveFailures
|
|
4576
|
+
});
|
|
4577
|
+
conversation.asTool(toolCall.id, {
|
|
4578
|
+
error: `Tool temporarily disabled due to ${consecutiveFailures} consecutive failures`
|
|
4579
|
+
}, {
|
|
4580
|
+
success: false,
|
|
4581
|
+
circuitBreakerTriggered: true
|
|
4582
|
+
});
|
|
4583
|
+
continue;
|
|
4584
|
+
}
|
|
4585
|
+
// Check tool call hook
|
|
4586
|
+
const toolAction = await ((_strategy_onToolCall = strategy.onToolCall) === null || _strategy_onToolCall === void 0 ? void 0 : _strategy_onToolCall.call(strategy, toolCall, state));
|
|
4587
|
+
if (toolAction === 'skip') {
|
|
4588
|
+
continue;
|
|
4589
|
+
}
|
|
4590
|
+
// Execute tool
|
|
4591
|
+
const toolStart = Date.now();
|
|
4592
|
+
try {
|
|
4593
|
+
var _strategy_onToolResult;
|
|
4594
|
+
// Parse tool arguments with error handling
|
|
4595
|
+
let toolArgs;
|
|
4596
|
+
try {
|
|
4597
|
+
toolArgs = JSON.parse(toolCall.function.arguments);
|
|
4598
|
+
} catch (parseError) {
|
|
4599
|
+
const error = new Error(`Invalid JSON in tool arguments for ${toolCall.function.name}: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
4600
|
+
if (parseError instanceof Error) {
|
|
4601
|
+
error.cause = parseError; // Preserve original error
|
|
4602
|
+
}
|
|
4603
|
+
throw error;
|
|
4604
|
+
}
|
|
4605
|
+
const result = await tools.execute(toolCall.function.name, toolArgs);
|
|
4606
|
+
const toolDuration = Date.now() - toolStart;
|
|
4607
|
+
const toolResult = {
|
|
4608
|
+
callId: toolCall.id,
|
|
4609
|
+
toolName: toolCall.function.name,
|
|
4610
|
+
result,
|
|
4611
|
+
duration: toolDuration
|
|
4612
|
+
};
|
|
4613
|
+
conversation.asTool(toolCall.id, result, {
|
|
4614
|
+
duration: toolDuration,
|
|
4615
|
+
success: true
|
|
4616
|
+
});
|
|
4617
|
+
state.toolCallsExecuted++;
|
|
4618
|
+
// Reset failure counter on success
|
|
4619
|
+
state.toolFailures.set(toolCall.function.name, 0);
|
|
4620
|
+
// Record metrics
|
|
4621
|
+
if (this.metricsCollector) {
|
|
4622
|
+
this.metricsCollector.recordToolCall(toolCall.function.name, state.iteration, toolDuration, true);
|
|
4623
|
+
}
|
|
4624
|
+
await ((_strategy_onToolResult = strategy.onToolResult) === null || _strategy_onToolResult === void 0 ? void 0 : _strategy_onToolResult.call(strategy, toolResult, state));
|
|
4625
|
+
} catch (error) {
|
|
4626
|
+
var _strategy_onToolResult1;
|
|
4627
|
+
this.logger.error('Tool execution failed', {
|
|
4628
|
+
tool: toolCall.function.name,
|
|
4629
|
+
error
|
|
4630
|
+
});
|
|
4631
|
+
const toolDuration = Date.now() - toolStart;
|
|
4632
|
+
const toolResult = {
|
|
4633
|
+
callId: toolCall.id,
|
|
4634
|
+
toolName: toolCall.function.name,
|
|
4635
|
+
result: null,
|
|
4636
|
+
error: error,
|
|
4637
|
+
duration: toolDuration
|
|
4638
|
+
};
|
|
4639
|
+
conversation.asTool(toolCall.id, {
|
|
4640
|
+
error: error.message
|
|
4641
|
+
}, {
|
|
4642
|
+
success: false,
|
|
4643
|
+
errorName: error.name
|
|
4644
|
+
});
|
|
4645
|
+
state.errors.push(error);
|
|
4646
|
+
// Increment failure counter for circuit breaker
|
|
4647
|
+
const failures = (state.toolFailures.get(toolCall.function.name) || 0) + 1;
|
|
4648
|
+
state.toolFailures.set(toolCall.function.name, failures);
|
|
4649
|
+
// Record metrics
|
|
4650
|
+
if (this.metricsCollector) {
|
|
4651
|
+
this.metricsCollector.recordToolCall(toolCall.function.name, state.iteration, toolDuration, false, error.message);
|
|
4652
|
+
}
|
|
4653
|
+
await ((_strategy_onToolResult1 = strategy.onToolResult) === null || _strategy_onToolResult1 === void 0 ? void 0 : _strategy_onToolResult1.call(strategy, toolResult, state));
|
|
4654
|
+
}
|
|
4655
|
+
}
|
|
4656
|
+
} else {
|
|
4657
|
+
// No tool calls - add response and potentially end phase
|
|
4658
|
+
conversation.asAssistant(response.content);
|
|
4659
|
+
// Check if this phase requires tool calls
|
|
4660
|
+
if (phase.toolUsage === 'required' && state.toolCallsExecuted === phaseStartTools) {
|
|
4661
|
+
this.logger.warn('No tools used but required in phase');
|
|
4662
|
+
// Continue to try again
|
|
4663
|
+
} else if (phase.earlyExit !== false) {
|
|
4664
|
+
break;
|
|
4665
|
+
}
|
|
4666
|
+
}
|
|
4667
|
+
// Check phase completion conditions
|
|
4668
|
+
const toolCallsInPhase = state.toolCallsExecuted - phaseStartTools;
|
|
4669
|
+
if (phase.minToolCalls && toolCallsInPhase < phase.minToolCalls) {
|
|
4670
|
+
continue; // Need more tool calls
|
|
4671
|
+
}
|
|
4672
|
+
if (phase.maxToolCalls && toolCallsInPhase >= phase.maxToolCalls) {
|
|
4673
|
+
break; // Hit max tool calls for phase
|
|
4674
|
+
}
|
|
4675
|
+
if (phase.continueIf && !phase.continueIf(state)) {
|
|
4676
|
+
break; // Condition not met
|
|
4677
|
+
}
|
|
4678
|
+
}
|
|
4679
|
+
return {
|
|
4680
|
+
name: phase.name,
|
|
4681
|
+
iterations: state.iteration,
|
|
4682
|
+
toolCalls: state.toolCallsExecuted - phaseStartTools,
|
|
4683
|
+
success: true,
|
|
4684
|
+
insights: state.insights
|
|
4685
|
+
};
|
|
4686
|
+
}
|
|
4687
|
+
constructor(llm, logger){
|
|
4688
|
+
_define_property(this, "llm", void 0);
|
|
4689
|
+
_define_property(this, "logger", void 0);
|
|
4690
|
+
_define_property(this, "metricsCollector", void 0);
|
|
4691
|
+
_define_property(this, "reflectionConfig", void 0);
|
|
4692
|
+
this.llm = llm;
|
|
4693
|
+
this.logger = wrapLogger(logger || DEFAULT_LOGGER, 'StrategyExecutor');
|
|
4694
|
+
}
|
|
4695
|
+
}
|
|
4696
|
+
// ===== PRE-BUILT STRATEGIES =====
|
|
4697
|
+
/**
|
|
4698
|
+
* Factory for creating iteration strategies
|
|
4699
|
+
*/ class IterationStrategyFactory {
|
|
4700
|
+
/**
|
|
4701
|
+
* Investigate then respond strategy
|
|
4702
|
+
* Phase 1: Use tools to gather information
|
|
4703
|
+
* Phase 2: Synthesize into final answer
|
|
4704
|
+
*/ static investigateThenRespond(config = {}) {
|
|
4705
|
+
const { maxInvestigationSteps = 5, requireMinimumTools = 1, finalSynthesis = true } = config;
|
|
4706
|
+
return {
|
|
4707
|
+
name: 'investigate-then-respond',
|
|
4708
|
+
description: 'Investigate using tools, then synthesize findings',
|
|
4709
|
+
maxIterations: maxInvestigationSteps + (finalSynthesis ? 1 : 0),
|
|
4710
|
+
phases: [
|
|
4711
|
+
{
|
|
4712
|
+
name: 'investigate',
|
|
4713
|
+
maxIterations: maxInvestigationSteps,
|
|
4714
|
+
toolUsage: 'encouraged',
|
|
4715
|
+
minToolCalls: requireMinimumTools,
|
|
4716
|
+
earlyExit: false
|
|
4717
|
+
},
|
|
4718
|
+
...finalSynthesis ? [
|
|
4719
|
+
{
|
|
4720
|
+
name: 'respond',
|
|
4721
|
+
maxIterations: 1,
|
|
4722
|
+
toolUsage: 'forbidden',
|
|
4723
|
+
instructions: 'Based on your investigation, provide a comprehensive answer.',
|
|
4724
|
+
requireFinalAnswer: true
|
|
4725
|
+
}
|
|
4726
|
+
] : []
|
|
4727
|
+
]
|
|
4728
|
+
};
|
|
4729
|
+
}
|
|
4730
|
+
/**
|
|
4731
|
+
* Multi-pass refinement strategy
|
|
4732
|
+
* Generate, critique, refine repeatedly
|
|
4733
|
+
*/ static multiPassRefinement(config = {}) {
|
|
4734
|
+
const { passes = 3, critiqueBetweenPasses = true } = config;
|
|
4735
|
+
const phases = [];
|
|
4736
|
+
for(let i = 0; i < passes; i++){
|
|
4737
|
+
phases.push({
|
|
4738
|
+
name: `pass-${i + 1}`,
|
|
4739
|
+
maxIterations: 1,
|
|
4740
|
+
toolUsage: 'optional',
|
|
4741
|
+
instructions: i === 0 ? 'Generate your best response' : 'Refine your previous response based on the critique'
|
|
4742
|
+
});
|
|
4743
|
+
if (critiqueBetweenPasses && i < passes - 1) {
|
|
4744
|
+
phases.push({
|
|
4745
|
+
name: `critique-${i + 1}`,
|
|
4746
|
+
maxIterations: 1,
|
|
4747
|
+
toolUsage: 'forbidden',
|
|
4748
|
+
instructions: 'Critique the previous response. What can be improved?'
|
|
4749
|
+
});
|
|
4750
|
+
}
|
|
4751
|
+
}
|
|
4752
|
+
return {
|
|
4753
|
+
name: 'multi-pass-refinement',
|
|
4754
|
+
description: 'Iteratively refine response through multiple passes',
|
|
4755
|
+
maxIterations: passes * 2,
|
|
4756
|
+
phases
|
|
4757
|
+
};
|
|
4758
|
+
}
|
|
4759
|
+
/**
|
|
4760
|
+
* Breadth-first investigation
|
|
4761
|
+
* Explore broadly before going deep
|
|
4762
|
+
*/ static breadthFirst(config = {}) {
|
|
4763
|
+
const { levelsDeep = 3, toolsPerLevel = 4 } = config;
|
|
4764
|
+
const phases = [];
|
|
4765
|
+
for(let level = 0; level < levelsDeep; level++){
|
|
4766
|
+
phases.push({
|
|
4767
|
+
name: `level-${level + 1}`,
|
|
4768
|
+
maxIterations: toolsPerLevel,
|
|
4769
|
+
toolUsage: 'encouraged',
|
|
4770
|
+
minToolCalls: 1,
|
|
4771
|
+
maxToolCalls: toolsPerLevel,
|
|
4772
|
+
instructions: level === 0 ? 'Get a broad overview' : `Dive deeper into areas discovered in level ${level}`
|
|
4773
|
+
});
|
|
4774
|
+
}
|
|
4775
|
+
return {
|
|
4776
|
+
name: 'breadth-first',
|
|
4777
|
+
description: 'Explore broadly at each level before going deeper',
|
|
4778
|
+
maxIterations: levelsDeep * toolsPerLevel,
|
|
4779
|
+
phases
|
|
4780
|
+
};
|
|
4781
|
+
}
|
|
4782
|
+
/**
|
|
4783
|
+
* Depth-first investigation
|
|
4784
|
+
* Deep dive immediately
|
|
4785
|
+
*/ static depthFirst(config = {}) {
|
|
4786
|
+
const { maxDepth = 5, backtrackOnFailure = true } = config;
|
|
4787
|
+
return {
|
|
4788
|
+
name: 'depth-first',
|
|
4789
|
+
description: 'Deep dive investigation path',
|
|
4790
|
+
maxIterations: maxDepth,
|
|
4791
|
+
phases: [
|
|
4792
|
+
{
|
|
4793
|
+
name: 'deep-dive',
|
|
4794
|
+
maxIterations: maxDepth,
|
|
4795
|
+
toolUsage: 'encouraged',
|
|
4796
|
+
adaptiveDepth: true
|
|
4797
|
+
}
|
|
4798
|
+
],
|
|
4799
|
+
shouldContinue: (state)=>{
|
|
4800
|
+
// Continue if making progress
|
|
4801
|
+
if (backtrackOnFailure && state.errors.length > 2) {
|
|
4802
|
+
return false;
|
|
4803
|
+
}
|
|
4804
|
+
return true;
|
|
4805
|
+
}
|
|
4806
|
+
};
|
|
4807
|
+
}
|
|
4808
|
+
/**
|
|
4809
|
+
* Adaptive strategy
|
|
4810
|
+
* Changes behavior based on progress
|
|
4811
|
+
*/ static adaptive(_config = {}) {
|
|
4812
|
+
return {
|
|
4813
|
+
name: 'adaptive',
|
|
4814
|
+
description: 'Adapts strategy based on progress',
|
|
4815
|
+
maxIterations: 20,
|
|
4816
|
+
onIteration: async (iteration, state)=>{
|
|
4817
|
+
// Change behavior based on iteration count
|
|
4818
|
+
if (iteration < 5) {
|
|
4819
|
+
// Early: broad exploration
|
|
4820
|
+
return 'continue';
|
|
4821
|
+
} else if (iteration < 15) {
|
|
4822
|
+
// Mid: focused investigation
|
|
4823
|
+
return 'continue';
|
|
4824
|
+
} else {
|
|
4825
|
+
// Late: wrap up
|
|
4826
|
+
return state.toolCallsExecuted > 0 ? 'continue' : 'stop';
|
|
4827
|
+
}
|
|
4828
|
+
}
|
|
4829
|
+
};
|
|
4830
|
+
}
|
|
4831
|
+
/**
|
|
4832
|
+
* Simple iteration (basic tool-use loop)
|
|
4833
|
+
*/ static simple(config = {}) {
|
|
4834
|
+
const { maxIterations = 10, allowTools = true } = config;
|
|
4835
|
+
return {
|
|
4836
|
+
name: 'simple',
|
|
4837
|
+
description: 'Simple iteration loop',
|
|
4838
|
+
maxIterations,
|
|
4839
|
+
phases: [
|
|
4840
|
+
{
|
|
4841
|
+
name: 'main',
|
|
4842
|
+
maxIterations,
|
|
4843
|
+
toolUsage: allowTools ? 'encouraged' : 'forbidden',
|
|
4844
|
+
earlyExit: true
|
|
4845
|
+
}
|
|
4846
|
+
]
|
|
4847
|
+
};
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4850
|
+
|
|
4851
|
+
// ===== CONFIGURATION SCHEMAS =====
|
|
4852
|
+
const ContentItemSchema = zod.z.union([
|
|
4853
|
+
zod.z.string(),
|
|
4854
|
+
zod.z.object({
|
|
4855
|
+
content: zod.z.string(),
|
|
4856
|
+
title: zod.z.string().optional(),
|
|
4857
|
+
weight: zod.z.number().optional()
|
|
4858
|
+
}),
|
|
4859
|
+
zod.z.object({
|
|
4860
|
+
path: zod.z.string(),
|
|
4861
|
+
title: zod.z.string().optional(),
|
|
4862
|
+
weight: zod.z.number().optional()
|
|
4863
|
+
}),
|
|
4864
|
+
zod.z.object({
|
|
4865
|
+
directories: zod.z.array(zod.z.string()),
|
|
4866
|
+
title: zod.z.string().optional(),
|
|
4867
|
+
weight: zod.z.number().optional()
|
|
4868
|
+
})
|
|
4869
|
+
]);
|
|
4870
|
+
const RecipeConfigSchema = zod.z.object({
|
|
4871
|
+
// Core settings
|
|
4872
|
+
basePath: zod.z.string(),
|
|
4873
|
+
logger: zod.z.any().optional().default(DEFAULT_LOGGER),
|
|
4874
|
+
overridePaths: zod.z.array(zod.z.string()).optional().default([
|
|
4875
|
+
"./"
|
|
4876
|
+
]),
|
|
4877
|
+
overrides: zod.z.boolean().optional().default(false),
|
|
4878
|
+
parameters: ParametersSchema.optional().default({}),
|
|
4879
|
+
// Content sections
|
|
4880
|
+
persona: ContentItemSchema.optional(),
|
|
4881
|
+
instructions: zod.z.array(ContentItemSchema).optional().default([]),
|
|
4882
|
+
content: zod.z.array(ContentItemSchema).optional().default([]),
|
|
4883
|
+
context: zod.z.array(ContentItemSchema).optional().default([]),
|
|
4884
|
+
// Templates and inheritance
|
|
4885
|
+
extends: zod.z.string().optional(),
|
|
4886
|
+
template: zod.z.string().optional(),
|
|
4887
|
+
// Tool integration
|
|
4888
|
+
tools: zod.z.any().optional(),
|
|
4889
|
+
toolGuidance: zod.z.union([
|
|
4890
|
+
zod.z.enum([
|
|
4891
|
+
'auto',
|
|
4892
|
+
'minimal',
|
|
4893
|
+
'detailed'
|
|
4894
|
+
]),
|
|
4895
|
+
zod.z.object({
|
|
4896
|
+
strategy: zod.z.enum([
|
|
4897
|
+
'adaptive',
|
|
4898
|
+
'prescriptive',
|
|
4899
|
+
'minimal'
|
|
4900
|
+
]),
|
|
4901
|
+
includeExamples: zod.z.boolean().optional(),
|
|
4902
|
+
explainWhenToUse: zod.z.boolean().optional(),
|
|
4903
|
+
includeCategories: zod.z.boolean().optional(),
|
|
4904
|
+
customInstructions: zod.z.string().optional()
|
|
4905
|
+
})
|
|
4906
|
+
]).optional(),
|
|
4907
|
+
toolCategories: zod.z.array(zod.z.string()).optional()
|
|
4908
|
+
});
|
|
4909
|
+
// User-customizable template registry
|
|
4910
|
+
let TEMPLATES = {};
|
|
4911
|
+
/**
|
|
4912
|
+
* Register custom templates with the recipes system
|
|
4913
|
+
*
|
|
4914
|
+
* @example
|
|
4915
|
+
* ```typescript
|
|
4916
|
+
* // Register your own templates
|
|
4917
|
+
* registerTemplates({
|
|
4918
|
+
* myWorkflow: {
|
|
4919
|
+
* persona: { path: "personas/my-persona.md" },
|
|
4920
|
+
* instructions: [{ path: "instructions/my-instructions.md" }]
|
|
4921
|
+
* },
|
|
4922
|
+
* anotherTemplate: {
|
|
4923
|
+
* persona: { content: "You are a helpful assistant" },
|
|
4924
|
+
* instructions: [{ content: "Follow these steps..." }]
|
|
4925
|
+
* }
|
|
4926
|
+
* });
|
|
4927
|
+
* ```
|
|
4928
|
+
*/ const registerTemplates = (templates)=>{
|
|
4929
|
+
TEMPLATES = {
|
|
4930
|
+
...TEMPLATES,
|
|
4931
|
+
...templates
|
|
4932
|
+
};
|
|
4933
|
+
};
|
|
4934
|
+
/**
|
|
4935
|
+
* Get currently registered templates
|
|
4936
|
+
*/ const getTemplates = ()=>({
|
|
4937
|
+
...TEMPLATES
|
|
4938
|
+
});
|
|
4939
|
+
/**
|
|
4940
|
+
* Clear all registered templates
|
|
4941
|
+
*/ const clearTemplates = ()=>{
|
|
4942
|
+
TEMPLATES = {};
|
|
4943
|
+
};
|
|
4944
|
+
// ===== TOOL GUIDANCE GENERATION =====
|
|
4945
|
+
/**
|
|
4946
|
+
* Generate tool guidance instructions based on strategy
|
|
4947
|
+
*/ const generateToolGuidance = (tools, guidance)=>{
|
|
4948
|
+
if (tools.length === 0) {
|
|
4949
|
+
return '';
|
|
4950
|
+
}
|
|
4951
|
+
// Normalize guidance config
|
|
4952
|
+
let config;
|
|
4953
|
+
if (typeof guidance === 'string') {
|
|
4954
|
+
switch(guidance){
|
|
4955
|
+
case 'auto':
|
|
4956
|
+
case 'detailed':
|
|
4957
|
+
config = {
|
|
4958
|
+
strategy: 'adaptive',
|
|
4959
|
+
includeExamples: true,
|
|
4960
|
+
explainWhenToUse: true
|
|
4961
|
+
};
|
|
4962
|
+
break;
|
|
4963
|
+
case 'minimal':
|
|
4964
|
+
config = {
|
|
4965
|
+
strategy: 'minimal',
|
|
4966
|
+
includeExamples: false,
|
|
4967
|
+
explainWhenToUse: false
|
|
4968
|
+
};
|
|
4969
|
+
break;
|
|
4970
|
+
default:
|
|
4971
|
+
config = {
|
|
4972
|
+
strategy: 'adaptive'
|
|
4973
|
+
};
|
|
4974
|
+
}
|
|
4975
|
+
} else {
|
|
4976
|
+
config = guidance;
|
|
4977
|
+
}
|
|
4978
|
+
let output = '## Available Tools\n\n';
|
|
4979
|
+
if (config.customInstructions) {
|
|
4980
|
+
output += config.customInstructions + '\n\n';
|
|
4981
|
+
}
|
|
4982
|
+
// Group by category if enabled
|
|
4983
|
+
if (config.includeCategories) {
|
|
4984
|
+
const categorized = new Map();
|
|
4985
|
+
tools.forEach((tool)=>{
|
|
4986
|
+
const category = tool.category || 'General';
|
|
4987
|
+
if (!categorized.has(category)) {
|
|
4988
|
+
categorized.set(category, []);
|
|
4989
|
+
}
|
|
4990
|
+
categorized.get(category).push(tool);
|
|
4991
|
+
});
|
|
4992
|
+
categorized.forEach((categoryTools, category)=>{
|
|
4993
|
+
output += `### ${category}\n\n`;
|
|
4994
|
+
categoryTools.forEach((tool)=>{
|
|
4995
|
+
output += formatToolGuidance(tool, config);
|
|
4996
|
+
});
|
|
4997
|
+
});
|
|
4998
|
+
} else {
|
|
4999
|
+
tools.forEach((tool)=>{
|
|
5000
|
+
output += formatToolGuidance(tool, config);
|
|
5001
|
+
});
|
|
5002
|
+
}
|
|
5003
|
+
return output;
|
|
5004
|
+
};
|
|
5005
|
+
const formatToolGuidance = (tool, config)=>{
|
|
5006
|
+
let output = `**${tool.name}**`;
|
|
5007
|
+
if (tool.cost) {
|
|
5008
|
+
output += ` _(${tool.cost})_`;
|
|
5009
|
+
}
|
|
5010
|
+
output += `\n${tool.description}\n\n`;
|
|
5011
|
+
if (config.strategy !== 'minimal') {
|
|
5012
|
+
// Parameters
|
|
5013
|
+
const required = tool.parameters.required || [];
|
|
5014
|
+
const paramList = Object.entries(tool.parameters.properties).map(([name, param])=>{
|
|
5015
|
+
const isRequired = required.includes(name);
|
|
5016
|
+
return `- \`${name}\`${isRequired ? ' (required)' : ''}: ${param.description}`;
|
|
5017
|
+
}).join('\n');
|
|
5018
|
+
if (paramList) {
|
|
5019
|
+
output += 'Parameters:\n' + paramList + '\n\n';
|
|
5020
|
+
}
|
|
5021
|
+
// When to use (adaptive and prescriptive)
|
|
5022
|
+
if (config.explainWhenToUse && (config.strategy === 'adaptive' || config.strategy === 'prescriptive')) {
|
|
5023
|
+
output += `**When to use:** ${tool.description}\n\n`;
|
|
5024
|
+
}
|
|
5025
|
+
// Examples
|
|
5026
|
+
if (config.includeExamples && tool.examples && tool.examples.length > 0) {
|
|
5027
|
+
output += '**Examples:**\n';
|
|
5028
|
+
tool.examples.forEach((example)=>{
|
|
5029
|
+
output += `- ${example.scenario}: \`${tool.name}(${JSON.stringify(example.params)})\`\n`;
|
|
5030
|
+
});
|
|
5031
|
+
output += '\n';
|
|
5032
|
+
}
|
|
5033
|
+
}
|
|
5034
|
+
output += '---\n\n';
|
|
5035
|
+
return output;
|
|
5036
|
+
};
|
|
5037
|
+
// ===== CORE RECIPE ENGINE =====
|
|
5038
|
+
const cook = async (config)=>{
|
|
5039
|
+
// Parse and validate configuration with defaults
|
|
5040
|
+
const validatedConfig = RecipeConfigSchema.parse({
|
|
5041
|
+
overridePaths: [
|
|
5042
|
+
"./"
|
|
5043
|
+
],
|
|
5044
|
+
overrides: false,
|
|
5045
|
+
parameters: {},
|
|
5046
|
+
instructions: [],
|
|
5047
|
+
content: [],
|
|
5048
|
+
context: [],
|
|
5049
|
+
...config
|
|
5050
|
+
});
|
|
5051
|
+
// Handle template inheritance
|
|
5052
|
+
let finalConfig = {
|
|
5053
|
+
...validatedConfig
|
|
5054
|
+
};
|
|
5055
|
+
if (validatedConfig.template) {
|
|
5056
|
+
const template = TEMPLATES[validatedConfig.template];
|
|
5057
|
+
if (template) {
|
|
5058
|
+
finalConfig = {
|
|
5059
|
+
...validatedConfig,
|
|
5060
|
+
persona: validatedConfig.persona || template.persona,
|
|
5061
|
+
instructions: [
|
|
5062
|
+
...template.instructions || [],
|
|
5063
|
+
...validatedConfig.instructions || []
|
|
5064
|
+
],
|
|
5065
|
+
content: [
|
|
5066
|
+
...template.content || [],
|
|
5067
|
+
...validatedConfig.content || []
|
|
5068
|
+
],
|
|
5069
|
+
context: [
|
|
5070
|
+
...template.context || [],
|
|
5071
|
+
...validatedConfig.context || []
|
|
5072
|
+
]
|
|
5073
|
+
};
|
|
5074
|
+
}
|
|
5075
|
+
}
|
|
5076
|
+
// Setup internal services
|
|
5077
|
+
const logger = wrapLogger(finalConfig.logger, 'Recipe');
|
|
5078
|
+
const parser$1 = create$4({
|
|
5079
|
+
logger
|
|
5080
|
+
});
|
|
5081
|
+
const override$1 = create$1({
|
|
5082
|
+
logger,
|
|
5083
|
+
configDirs: finalConfig.overridePaths || [
|
|
5084
|
+
"./"
|
|
5085
|
+
],
|
|
5086
|
+
overrides: finalConfig.overrides || false
|
|
5087
|
+
});
|
|
5088
|
+
const loader$1 = create$2({
|
|
5089
|
+
logger
|
|
5090
|
+
});
|
|
5091
|
+
// Create sections
|
|
5092
|
+
const personaSection = create$8({
|
|
5093
|
+
title: "Persona"
|
|
5094
|
+
});
|
|
5095
|
+
const instructionSection = create$8({
|
|
5096
|
+
title: "Instruction"
|
|
5097
|
+
});
|
|
5098
|
+
const contentSection = create$8({
|
|
5099
|
+
title: "Content"
|
|
5100
|
+
});
|
|
5101
|
+
const contextSection = create$8({
|
|
5102
|
+
title: "Context"
|
|
5103
|
+
});
|
|
5104
|
+
// Process persona
|
|
5105
|
+
if (finalConfig.persona) {
|
|
5106
|
+
await processContentItem(finalConfig.persona, personaSection, 'persona', {
|
|
5107
|
+
basePath: finalConfig.basePath,
|
|
5108
|
+
parser: parser$1,
|
|
5109
|
+
override: override$1,
|
|
5110
|
+
loader: loader$1,
|
|
5111
|
+
parameters: finalConfig.parameters});
|
|
1526
5112
|
}
|
|
1527
5113
|
// Process instructions
|
|
1528
5114
|
for (const item of finalConfig.instructions || []){
|
|
@@ -1533,6 +5119,19 @@ const cook = async (config)=>{
|
|
|
1533
5119
|
loader: loader$1,
|
|
1534
5120
|
parameters: finalConfig.parameters});
|
|
1535
5121
|
}
|
|
5122
|
+
// Generate tool guidance if tools are provided
|
|
5123
|
+
if (finalConfig.tools) {
|
|
5124
|
+
const tools = Array.isArray(finalConfig.tools) ? finalConfig.tools : finalConfig.tools.getAll();
|
|
5125
|
+
// Filter by categories if specified
|
|
5126
|
+
const filteredTools = finalConfig.toolCategories ? tools.filter((tool)=>tool.category && finalConfig.toolCategories.includes(tool.category)) : tools;
|
|
5127
|
+
if (filteredTools.length > 0 && finalConfig.toolGuidance) {
|
|
5128
|
+
const guidance = generateToolGuidance(filteredTools, finalConfig.toolGuidance);
|
|
5129
|
+
const toolSection = await parser$1.parse(guidance, {
|
|
5130
|
+
parameters: finalConfig.parameters
|
|
5131
|
+
});
|
|
5132
|
+
instructionSection.add(toolSection);
|
|
5133
|
+
}
|
|
5134
|
+
}
|
|
1536
5135
|
// Process content
|
|
1537
5136
|
for (const item of finalConfig.content || []){
|
|
1538
5137
|
await processContentItem(item, contentSection, 'content', {
|
|
@@ -1649,7 +5248,61 @@ const recipe = (basePath)=>{
|
|
|
1649
5248
|
config.overridePaths = paths;
|
|
1650
5249
|
return builder;
|
|
1651
5250
|
},
|
|
1652
|
-
|
|
5251
|
+
tools: (tools)=>{
|
|
5252
|
+
config.tools = tools;
|
|
5253
|
+
return builder;
|
|
5254
|
+
},
|
|
5255
|
+
toolRegistry: (registry)=>{
|
|
5256
|
+
config.tools = registry;
|
|
5257
|
+
return builder;
|
|
5258
|
+
},
|
|
5259
|
+
toolGuidance: (guidance)=>{
|
|
5260
|
+
config.toolGuidance = guidance;
|
|
5261
|
+
return builder;
|
|
5262
|
+
},
|
|
5263
|
+
toolCategories: (categories)=>{
|
|
5264
|
+
config.toolCategories = categories;
|
|
5265
|
+
return builder;
|
|
5266
|
+
},
|
|
5267
|
+
cook: ()=>cook(config),
|
|
5268
|
+
buildConversation: async (model, tokenBudget)=>{
|
|
5269
|
+
const prompt = await cook(config);
|
|
5270
|
+
const conversation = ConversationBuilder.create({
|
|
5271
|
+
model
|
|
5272
|
+
}, config.logger);
|
|
5273
|
+
conversation.fromPrompt(prompt, model);
|
|
5274
|
+
// Apply token budget if provided
|
|
5275
|
+
if (tokenBudget) {
|
|
5276
|
+
conversation.withTokenBudget(tokenBudget);
|
|
5277
|
+
}
|
|
5278
|
+
return conversation;
|
|
5279
|
+
},
|
|
5280
|
+
getToolRegistry: ()=>{
|
|
5281
|
+
if (config.tools instanceof ToolRegistry) {
|
|
5282
|
+
return config.tools;
|
|
5283
|
+
} else if (Array.isArray(config.tools)) {
|
|
5284
|
+
const registry = ToolRegistry.create({}, config.logger);
|
|
5285
|
+
registry.registerAll(config.tools);
|
|
5286
|
+
return registry;
|
|
5287
|
+
}
|
|
5288
|
+
return undefined;
|
|
5289
|
+
},
|
|
5290
|
+
executeWith: async (llm, strategy, model = 'gpt-4o', tokenBudget)=>{
|
|
5291
|
+
const prompt = await cook(config);
|
|
5292
|
+
const conversation = ConversationBuilder.create({
|
|
5293
|
+
model
|
|
5294
|
+
}, config.logger);
|
|
5295
|
+
conversation.fromPrompt(prompt, model);
|
|
5296
|
+
if (tokenBudget) {
|
|
5297
|
+
conversation.withTokenBudget(tokenBudget);
|
|
5298
|
+
}
|
|
5299
|
+
const registry = builder.getToolRegistry();
|
|
5300
|
+
if (!registry) {
|
|
5301
|
+
throw new Error('Tools must be configured to use executeWith');
|
|
5302
|
+
}
|
|
5303
|
+
const executor = new StrategyExecutor(llm, config.logger);
|
|
5304
|
+
return executor.execute(conversation, registry, strategy);
|
|
5305
|
+
}
|
|
1653
5306
|
};
|
|
1654
5307
|
return builder;
|
|
1655
5308
|
};
|
|
@@ -1658,6 +5311,7 @@ const recipes = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
|
|
1658
5311
|
__proto__: null,
|
|
1659
5312
|
clearTemplates,
|
|
1660
5313
|
cook,
|
|
5314
|
+
generateToolGuidance,
|
|
1661
5315
|
getTemplates,
|
|
1662
5316
|
recipe,
|
|
1663
5317
|
registerTemplates
|
|
@@ -1665,12 +5319,27 @@ const recipes = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
|
|
1665
5319
|
|
|
1666
5320
|
exports.Builder = builder;
|
|
1667
5321
|
exports.Chat = chat;
|
|
5322
|
+
exports.ContextManager = ContextManager;
|
|
5323
|
+
exports.ConversationBuilder = ConversationBuilder;
|
|
5324
|
+
exports.ConversationLogger = ConversationLogger;
|
|
5325
|
+
exports.ConversationReplayer = ConversationReplayer;
|
|
1668
5326
|
exports.Formatter = formatter;
|
|
5327
|
+
exports.IterationStrategyFactory = IterationStrategyFactory;
|
|
1669
5328
|
exports.Loader = loader;
|
|
5329
|
+
exports.MessageBuilder = MessageBuilder;
|
|
5330
|
+
exports.MessageTemplates = MessageTemplates;
|
|
5331
|
+
exports.MetricsCollector = MetricsCollector;
|
|
5332
|
+
exports.ModelRegistry = ModelRegistry;
|
|
1670
5333
|
exports.Override = override;
|
|
1671
5334
|
exports.Parser = parser;
|
|
1672
5335
|
exports.Recipes = recipes;
|
|
5336
|
+
exports.ReflectionReportGenerator = ReflectionReportGenerator;
|
|
5337
|
+
exports.StrategyExecutor = StrategyExecutor;
|
|
5338
|
+
exports.TokenBudgetManager = TokenBudgetManager;
|
|
5339
|
+
exports.TokenCounter = TokenCounter;
|
|
5340
|
+
exports.ToolRegistry = ToolRegistry;
|
|
1673
5341
|
exports.clearTemplates = clearTemplates;
|
|
5342
|
+
exports.configureModel = configureModel;
|
|
1674
5343
|
exports.cook = cook;
|
|
1675
5344
|
exports.createContent = create$b;
|
|
1676
5345
|
exports.createContext = create$a;
|
|
@@ -1680,7 +5349,14 @@ exports.createPrompt = create$6;
|
|
|
1680
5349
|
exports.createSection = create$8;
|
|
1681
5350
|
exports.createTrait = create$7;
|
|
1682
5351
|
exports.createWeighted = create$c;
|
|
5352
|
+
exports.generateToolGuidance = generateToolGuidance;
|
|
5353
|
+
exports.getEncoding = getEncoding;
|
|
5354
|
+
exports.getModelFamily = getModelFamily;
|
|
5355
|
+
exports.getModelRegistry = getModelRegistry;
|
|
5356
|
+
exports.getPersonaRole = getPersonaRole$1;
|
|
1683
5357
|
exports.getTemplates = getTemplates;
|
|
1684
5358
|
exports.recipe = recipe;
|
|
1685
5359
|
exports.registerTemplates = registerTemplates;
|
|
5360
|
+
exports.resetModelRegistry = resetModelRegistry;
|
|
5361
|
+
exports.supportsToolCalls = supportsToolCalls;
|
|
1686
5362
|
//# sourceMappingURL=riotprompt.cjs.map
|