@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.
Files changed (59) hide show
  1. package/.kodrdriv-test-cache.json +6 -0
  2. package/BUG-ANALYSIS.md +523 -0
  3. package/CODE-REVIEW-SUMMARY.md +330 -0
  4. package/FIXES-APPLIED.md +437 -0
  5. package/README.md +2 -2
  6. package/dist/builder.js +3 -0
  7. package/dist/builder.js.map +1 -1
  8. package/dist/chat.d.ts +1 -1
  9. package/dist/chat.js +2 -5
  10. package/dist/chat.js.map +1 -1
  11. package/dist/constants.js +1 -2
  12. package/dist/constants.js.map +1 -1
  13. package/dist/context-manager.d.ts +136 -0
  14. package/dist/context-manager.js +243 -0
  15. package/dist/context-manager.js.map +1 -0
  16. package/dist/conversation-logger.d.ts +285 -0
  17. package/dist/conversation-logger.js +491 -0
  18. package/dist/conversation-logger.js.map +1 -0
  19. package/dist/conversation.d.ts +277 -0
  20. package/dist/conversation.js +649 -0
  21. package/dist/conversation.js.map +1 -0
  22. package/dist/formatter.js.map +1 -1
  23. package/dist/items/section.js +3 -3
  24. package/dist/items/section.js.map +1 -1
  25. package/dist/iteration-strategy.d.ts +233 -0
  26. package/dist/iteration-strategy.js +520 -0
  27. package/dist/iteration-strategy.js.map +1 -0
  28. package/dist/loader.js +21 -3
  29. package/dist/loader.js.map +1 -1
  30. package/dist/message-builder.d.ts +156 -0
  31. package/dist/message-builder.js +256 -0
  32. package/dist/message-builder.js.map +1 -0
  33. package/dist/model-config.d.ts +115 -0
  34. package/dist/model-config.js +205 -0
  35. package/dist/model-config.js.map +1 -0
  36. package/dist/override.js +8 -1
  37. package/dist/override.js.map +1 -1
  38. package/dist/parser.js +3 -3
  39. package/dist/parser.js.map +1 -1
  40. package/dist/recipes.d.ts +42 -0
  41. package/dist/recipes.js +189 -4
  42. package/dist/recipes.js.map +1 -1
  43. package/dist/reflection.d.ts +250 -0
  44. package/dist/reflection.js +419 -0
  45. package/dist/reflection.js.map +1 -0
  46. package/dist/riotprompt.cjs +3854 -178
  47. package/dist/riotprompt.cjs.map +1 -1
  48. package/dist/riotprompt.d.ts +20 -2
  49. package/dist/riotprompt.js +10 -1
  50. package/dist/riotprompt.js.map +1 -1
  51. package/dist/token-budget.d.ts +177 -0
  52. package/dist/token-budget.js +401 -0
  53. package/dist/token-budget.js.map +1 -0
  54. package/dist/tools.d.ts +239 -0
  55. package/dist/tools.js +324 -0
  56. package/dist/tools.js.map +1 -0
  57. package/dist/util/general.js +1 -1
  58. package/dist/util/general.js.map +1 -1
  59. package/package.json +23 -20
@@ -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[0] === undefined) return '[]';
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 with marked at ${filePath}:`, error);
810
- throw new Error(`Failed to parse instructions from ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
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)=>new RegExp(pattern, 'i'));
1072
- const filteredFiles = files.filter((file)=>!ignorePatternsRegex.some((regex)=>regex.test(file)));
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
- for (const append of appends.reverse()){
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
- // ===== CONFIGURATION SCHEMAS =====
1380
- const ContentItemSchema = zod.z.union([
1381
- zod.z.string(),
1382
- zod.z.object({
1383
- content: zod.z.string(),
1384
- title: zod.z.string().optional(),
1385
- weight: zod.z.number().optional()
1386
- }),
1387
- zod.z.object({
1388
- path: zod.z.string(),
1389
- title: zod.z.string().optional(),
1390
- weight: zod.z.number().optional()
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
- * Register custom templates with the recipes system
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
- * // Register your own templates
1424
- * registerTemplates({
1425
- * myWorkflow: {
1426
- * persona: { path: "personas/my-persona.md" },
1427
- * instructions: [{ path: "instructions/my-instructions.md" }]
1428
- * },
1429
- * anotherTemplate: {
1430
- * persona: { content: "You are a helpful assistant" },
1431
- * instructions: [{ content: "Follow these steps..." }]
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
- */ const registerTemplates = (templates)=>{
1436
- TEMPLATES = {
1437
- ...TEMPLATES,
1438
- ...templates
1439
- };
1440
- };
1441
- /**
1442
- * Get currently registered templates
1443
- */ const getTemplates = ()=>({
1444
- ...TEMPLATES
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
- // Setup internal services
1491
- const logger = wrapLogger(finalConfig.logger, 'Recipe');
1492
- const parser$1 = create$4({
1493
- logger
1494
- });
1495
- const override$1 = create$1({
1496
- logger,
1497
- configDirs: finalConfig.overridePaths || [
1498
- "./"
1499
- ],
1500
- overrides: finalConfig.overrides || false
1501
- });
1502
- const loader$1 = create$2({
1503
- logger
1504
- });
1505
- // Create sections
1506
- const personaSection = create$8({
1507
- title: "Persona"
1508
- });
1509
- const instructionSection = create$8({
1510
- title: "Instruction"
1511
- });
1512
- const contentSection = create$8({
1513
- title: "Content"
1514
- });
1515
- const contextSection = create$8({
1516
- title: "Context"
1517
- });
1518
- // Process persona
1519
- if (finalConfig.persona) {
1520
- await processContentItem(finalConfig.persona, personaSection, 'persona', {
1521
- basePath: finalConfig.basePath,
1522
- parser: parser$1,
1523
- override: override$1,
1524
- loader: loader$1,
1525
- parameters: finalConfig.parameters});
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
- cook: ()=>cook(config)
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