@riotprompt/riotprompt 0.0.9 → 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 (49) hide show
  1. package/BUG-ANALYSIS.md +523 -0
  2. package/CODE-REVIEW-SUMMARY.md +330 -0
  3. package/FIXES-APPLIED.md +437 -0
  4. package/dist/chat.d.ts +1 -1
  5. package/dist/chat.js +2 -5
  6. package/dist/chat.js.map +1 -1
  7. package/dist/constants.js +1 -2
  8. package/dist/constants.js.map +1 -1
  9. package/dist/context-manager.d.ts +3 -2
  10. package/dist/context-manager.js +29 -6
  11. package/dist/context-manager.js.map +1 -1
  12. package/dist/conversation-logger.d.ts +3 -1
  13. package/dist/conversation-logger.js +41 -4
  14. package/dist/conversation-logger.js.map +1 -1
  15. package/dist/conversation.d.ts +8 -2
  16. package/dist/conversation.js +36 -9
  17. package/dist/conversation.js.map +1 -1
  18. package/dist/items/section.js +3 -3
  19. package/dist/items/section.js.map +1 -1
  20. package/dist/iteration-strategy.d.ts +2 -0
  21. package/dist/iteration-strategy.js +40 -6
  22. package/dist/iteration-strategy.js.map +1 -1
  23. package/dist/loader.js +18 -3
  24. package/dist/loader.js.map +1 -1
  25. package/dist/message-builder.js +4 -2
  26. package/dist/message-builder.js.map +1 -1
  27. package/dist/model-config.d.ts +115 -0
  28. package/dist/model-config.js +205 -0
  29. package/dist/model-config.js.map +1 -0
  30. package/dist/override.js +5 -1
  31. package/dist/override.js.map +1 -1
  32. package/dist/parser.js +3 -3
  33. package/dist/parser.js.map +1 -1
  34. package/dist/recipes.d.ts +1 -1
  35. package/dist/recipes.js +4 -4
  36. package/dist/recipes.js.map +1 -1
  37. package/dist/reflection.js +5 -2
  38. package/dist/reflection.js.map +1 -1
  39. package/dist/riotprompt.cjs +439 -94
  40. package/dist/riotprompt.cjs.map +1 -1
  41. package/dist/riotprompt.d.ts +2 -0
  42. package/dist/riotprompt.js +1 -0
  43. package/dist/riotprompt.js.map +1 -1
  44. package/dist/token-budget.d.ts +2 -2
  45. package/dist/token-budget.js +23 -26
  46. package/dist/token-budget.js.map +1 -1
  47. package/dist/util/general.js +1 -1
  48. package/dist/util/general.js.map +1 -1
  49. package/package.json +1 -1
@@ -128,7 +128,7 @@ const create$8 = (options = {})=>{
128
128
  };
129
129
  if (Array.isArray(item)) {
130
130
  item.forEach((item)=>{
131
- append(item);
131
+ append(item, options); // Propagate options to array items
132
132
  });
133
133
  } else {
134
134
  if (typeof item === 'string') {
@@ -147,7 +147,7 @@ const create$8 = (options = {})=>{
147
147
  };
148
148
  if (Array.isArray(item)) {
149
149
  item.forEach((item)=>{
150
- prepend(item);
150
+ prepend(item, options); // Propagate options to array items
151
151
  });
152
152
  } else {
153
153
  if (typeof item === 'string') {
@@ -166,7 +166,7 @@ const create$8 = (options = {})=>{
166
166
  };
167
167
  if (Array.isArray(item)) {
168
168
  item.forEach((item)=>{
169
- insert(index, item);
169
+ insert(index, item, options); // Propagate options to array items
170
170
  });
171
171
  } else {
172
172
  if (typeof item === 'string') {
@@ -243,7 +243,6 @@ const create$6 = ({ persona, instructions, contents, contexts })=>{
243
243
 
244
244
  const DEFAULT_CHARACTER_ENCODING = "utf8";
245
245
  const LIBRARY_NAME = "riotprompt";
246
- const DEFAULT_PERSONA_ROLE = "developer";
247
246
  const DEFAULT_IGNORE_PATTERNS = [
248
247
  "^\\..*",
249
248
  "\\.(jpg|jpeg|png|gif|bmp|svg|webp|ico)$",
@@ -262,29 +261,6 @@ const DEFAULT_FORMAT_OPTIONS = {
262
261
  sectionDepth: 0
263
262
  };
264
263
 
265
- const getPersonaRole = (model)=>{
266
- if (model === "gpt-4o" || model === "gpt-4o-mini") {
267
- return "system";
268
- }
269
- return DEFAULT_PERSONA_ROLE;
270
- };
271
- const createRequest = (model)=>{
272
- const messages = [];
273
- return {
274
- model,
275
- messages,
276
- addMessage: (message)=>{
277
- messages.push(message);
278
- }
279
- };
280
- };
281
-
282
- const chat = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
283
- __proto__: null,
284
- createRequest,
285
- getPersonaRole
286
- }, Symbol.toStringTag, { value: 'Module' }));
287
-
288
264
  const DEFAULT_LOGGER = {
289
265
  name: 'default',
290
266
  debug: (message, ...args)=>console.debug(message, ...args),
@@ -329,6 +305,227 @@ const wrapLogger = (toWrap, name)=>{
329
305
  };
330
306
  };
331
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
+
332
529
  const clean = (obj)=>{
333
530
  return Object.fromEntries(Object.entries(obj).filter(([_, v])=>v !== undefined));
334
531
  };
@@ -343,7 +540,7 @@ const stringifyJSON = function(obj, visited = new Set()) {
343
540
  return '"(circular)"';
344
541
  } else if (Array.isArray(obj)) {
345
542
  //check for empty array
346
- if (obj[0] === undefined) return '[]';
543
+ if (obj.length === 0) return '[]';
347
544
  else {
348
545
  // Add array to visited before processing its elements
349
546
  visited.add(obj);
@@ -807,13 +1004,13 @@ const create$4 = (parserOptions)=>{
807
1004
  });
808
1005
  } catch (error) {
809
1006
  // Log the error or handle it appropriately
810
- logger.error(`Error reading or parsing file with marked at ${filePath}:`, error);
811
- 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)}`);
812
1009
  }
813
1010
  };
814
1011
  /**
815
1012
  * Reads Markdown content and parses it into a single Section.
816
- *
1013
+ *
817
1014
  * - If the content starts with a heading, that becomes the title of the returned Section
818
1015
  * - If no heading at the start, creates a Section with no title
819
1016
  * - Headers within the content create nested sections based on their depth
@@ -1017,7 +1214,7 @@ const create$2 = (loaderOptions)=>{
1017
1214
  };
1018
1215
  /**
1019
1216
  * Loads context from the provided directories and returns instruction sections
1020
- *
1217
+ *
1021
1218
  * @param contextDirectories Directories containing context files
1022
1219
  * @returns Array of instruction sections loaded from context directories
1023
1220
  */ const load = async (contextDirectories = [], options = {})=>{
@@ -1069,8 +1266,22 @@ const create$2 = (loaderOptions)=>{
1069
1266
  }
1070
1267
  // Get all other files in the directory
1071
1268
  const files = await storage.listFiles(contextDir);
1072
- const ignorePatternsRegex = ignorePatterns.map((pattern)=>new RegExp(pattern, 'i'));
1073
- 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
+ });
1074
1285
  for (const file of filteredFiles){
1075
1286
  // Skip the context.md file as it's already processed
1076
1287
  if (file === 'context.md') continue;
@@ -1098,6 +1309,7 @@ const create$2 = (loaderOptions)=>{
1098
1309
  ...sectionOptions
1099
1310
  });
1100
1311
  // Add this file section to the main context section
1312
+ // Type is correct - Section.add() accepts Section<T>
1101
1313
  mainContextSection.add(fileSection, {
1102
1314
  ...sectionOptions
1103
1315
  });
@@ -1213,7 +1425,11 @@ const create$1 = (overrideOptions = {})=>{
1213
1425
  finalSection = finalSection.prepend(prepend);
1214
1426
  }
1215
1427
  // Apply appends in reverse order (furthest layers first, then closest)
1216
- 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){
1217
1433
  logger.silly('Append found, adding to content from file %s', append);
1218
1434
  finalSection = finalSection.append(append);
1219
1435
  }
@@ -1422,10 +1638,17 @@ function _define_property$7(obj, key, value) {
1422
1638
  * ```
1423
1639
  */ class ContextManager {
1424
1640
  /**
1425
- * Track a context item
1641
+ * Track a context item (with deduplication by content hash for items without ID)
1426
1642
  */ track(item, position) {
1427
- const id = item.id || this.generateId();
1428
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;
1650
+ }
1651
+ const id = item.id || this.generateId();
1429
1652
  const trackedItem = {
1430
1653
  ...item,
1431
1654
  id,
@@ -1456,7 +1679,16 @@ function _define_property$7(obj, key, value) {
1456
1679
  }
1457
1680
  /**
1458
1681
  * Check if similar content exists (fuzzy match)
1459
- */ hasSimilarContent(content) {
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
+ }
1460
1692
  const normalized = this.normalizeContent(content);
1461
1693
  for (const item of this.items.values()){
1462
1694
  const itemNormalized = this.normalizeContent(item.content || '');
@@ -1464,9 +1696,16 @@ function _define_property$7(obj, key, value) {
1464
1696
  if (normalized === itemNormalized) {
1465
1697
  return true;
1466
1698
  }
1467
- // Substring match (one contains the other)
1468
- if (normalized.includes(itemNormalized) || itemNormalized.includes(normalized)) {
1469
- return true;
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
+ }
1470
1709
  }
1471
1710
  }
1472
1711
  return false;
@@ -1638,6 +1877,8 @@ function _define_property$6(obj, key, value) {
1638
1877
  ...metadata,
1639
1878
  startTime: this.startTime
1640
1879
  };
1880
+ // Reset cached output path to prevent file collision if logger is reused
1881
+ this.cachedOutputPath = undefined;
1641
1882
  this.logger.debug('Conversation logging started', {
1642
1883
  id: this.conversationId
1643
1884
  });
@@ -1660,9 +1901,21 @@ function _define_property$6(obj, key, value) {
1660
1901
  metadata
1661
1902
  };
1662
1903
  this.messages.push(loggedMessage);
1663
- // For JSONL format, append immediately
1904
+ // For JSONL format, append immediately with write queue
1664
1905
  if (this.config.format === 'jsonl') {
1665
- this.appendToJSONL(loggedMessage).catch(this.config.onError);
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
+ });
1666
1919
  }
1667
1920
  }
1668
1921
  /**
@@ -1744,8 +1997,11 @@ function _define_property$6(obj, key, value) {
1744
1997
  return `conv-${timestamp}-${random}`;
1745
1998
  }
1746
1999
  /**
1747
- * Get output file path
2000
+ * Get output file path (cached for JSONL to avoid recalculation)
1748
2001
  */ async getOutputPath() {
2002
+ if (this.cachedOutputPath) {
2003
+ return this.cachedOutputPath;
2004
+ }
1749
2005
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
1750
2006
  const filename = this.config.filenameTemplate.replace('{timestamp}', timestamp).replace('{id}', this.conversationId).replace('{template}', this.metadata.template || 'default');
1751
2007
  const ext = this.config.format === 'markdown' ? '.md' : '.json';
@@ -1754,6 +2010,10 @@ function _define_property$6(obj, key, value) {
1754
2010
  await fs.mkdir(path.dirname(fullPath), {
1755
2011
  recursive: true
1756
2012
  });
2013
+ // Cache path for JSONL format to ensure consistent file writes
2014
+ if (this.config.format === 'jsonl') {
2015
+ this.cachedOutputPath = fullPath;
2016
+ }
1757
2017
  return fullPath;
1758
2018
  }
1759
2019
  /**
@@ -1844,6 +2104,8 @@ function _define_property$6(obj, key, value) {
1844
2104
  _define_property$6(this, "startTime", void 0);
1845
2105
  _define_property$6(this, "logger", void 0);
1846
2106
  _define_property$6(this, "messageIndex", void 0);
2107
+ _define_property$6(this, "cachedOutputPath", void 0);
2108
+ _define_property$6(this, "writeQueue", Promise.resolve());
1847
2109
  this.config = {
1848
2110
  outputPath: 'logs/conversations',
1849
2111
  format: 'json',
@@ -1960,12 +2222,26 @@ function _define_property$6(obj, key, value) {
1960
2222
  for (const msg of this.conversation.messages){
1961
2223
  if (msg.tool_calls) {
1962
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
+ }
1963
2239
  toolCalls.push({
1964
2240
  callId: call.id,
1965
2241
  toolName: call.function.name,
1966
2242
  timestamp: msg.timestamp,
1967
2243
  iteration: 0,
1968
- arguments: JSON.parse(call.function.arguments),
2244
+ arguments: parsedArgs,
1969
2245
  result: null,
1970
2246
  duration: 0,
1971
2247
  success: true
@@ -2214,8 +2490,9 @@ function _define_property$5(obj, key, value) {
2214
2490
  const message = this.build();
2215
2491
  // Handle model-specific role requirements
2216
2492
  if (this.semanticRole === 'system') {
2217
- // O1 models use 'developer' instead of 'system'
2218
- if (model.startsWith('o1') || model.startsWith('o3') || model === 'o1-pro') {
2493
+ // Use model registry to determine correct role
2494
+ const personaRole = getPersonaRole$1(model);
2495
+ if (personaRole === 'developer') {
2219
2496
  message.role = 'developer';
2220
2497
  }
2221
2498
  }
@@ -2385,19 +2662,16 @@ function _define_property$4(obj, key, value) {
2385
2662
  return Math.max(500, Math.floor(inputTokens * 0.2));
2386
2663
  }
2387
2664
  /**
2388
- * Map RiotPrompt model to Tiktoken model
2665
+ * Map RiotPrompt model to Tiktoken model using model registry
2389
2666
  */ mapToTiktokenModel(model) {
2390
- switch(model){
2667
+ const encoding = getEncoding(model);
2668
+ // Map our encoding types to tiktoken models
2669
+ switch(encoding){
2391
2670
  case 'gpt-4o':
2392
- case 'gpt-4o-mini':
2393
- return 'gpt-4o';
2394
- case 'o1-preview':
2395
- case 'o1-mini':
2396
- case 'o1':
2397
- case 'o3-mini':
2398
- case 'o1-pro':
2399
- // O1 models use gpt-4o tokenization
2671
+ case 'o200k_base':
2400
2672
  return 'gpt-4o';
2673
+ case 'cl100k_base':
2674
+ return 'gpt-3.5-turbo';
2401
2675
  default:
2402
2676
  return 'gpt-4o';
2403
2677
  }
@@ -2564,16 +2838,16 @@ function _define_property$4(obj, key, value) {
2564
2838
  return kept.map((item)=>item.message);
2565
2839
  }
2566
2840
  /**
2567
- * Compress using FIFO (remove oldest first)
2841
+ * Compress using FIFO (remove oldest first) - optimized with Set
2568
2842
  */ compressFIFO(messages, targetTokens) {
2569
2843
  var _this_config_preserveRecent;
2570
- const preserved = [];
2844
+ const preservedSet = new Set();
2571
2845
  let totalTokens = 0;
2572
2846
  // Always preserve system messages if configured
2573
2847
  const systemMessages = messages.filter((m)=>m.role === 'system');
2574
2848
  if (this.config.preserveSystem) {
2575
2849
  for (const msg of systemMessages){
2576
- preserved.push(msg);
2850
+ preservedSet.add(msg);
2577
2851
  totalTokens += this.counter.countMessage(msg);
2578
2852
  }
2579
2853
  }
@@ -2581,28 +2855,28 @@ function _define_property$4(obj, key, value) {
2581
2855
  const recentCount = (_this_config_preserveRecent = this.config.preserveRecent) !== null && _this_config_preserveRecent !== void 0 ? _this_config_preserveRecent : 3;
2582
2856
  const recentMessages = messages.slice(-recentCount).filter((m)=>m.role !== 'system');
2583
2857
  for (const msg of recentMessages){
2584
- if (!preserved.includes(msg)) {
2858
+ if (!preservedSet.has(msg)) {
2585
2859
  const tokens = this.counter.countMessage(msg);
2586
2860
  if (totalTokens + tokens <= targetTokens) {
2587
- preserved.push(msg);
2861
+ preservedSet.add(msg);
2588
2862
  totalTokens += tokens;
2589
2863
  }
2590
2864
  }
2591
2865
  }
2592
2866
  // Add older messages if space available
2593
- const otherMessages = messages.filter((m)=>!preserved.includes(m) && m.role !== 'system');
2867
+ const otherMessages = messages.filter((m)=>!preservedSet.has(m) && m.role !== 'system');
2594
2868
  for(let i = otherMessages.length - 1; i >= 0; i--){
2595
2869
  const msg = otherMessages[i];
2596
2870
  const tokens = this.counter.countMessage(msg);
2597
2871
  if (totalTokens + tokens <= targetTokens) {
2598
- preserved.unshift(msg);
2872
+ preservedSet.add(msg);
2599
2873
  totalTokens += tokens;
2600
2874
  } else {
2601
2875
  break;
2602
2876
  }
2603
2877
  }
2604
- // Sort to maintain conversation order
2605
- return messages.filter((m)=>preserved.includes(m));
2878
+ // Sort to maintain conversation order - use Set for O(1) lookup
2879
+ return messages.filter((m)=>preservedSet.has(m));
2606
2880
  }
2607
2881
  /**
2608
2882
  * Adaptive compression based on conversation phase
@@ -2614,13 +2888,12 @@ function _define_property$4(obj, key, value) {
2614
2888
  }
2615
2889
  // Mid phase: moderate compression
2616
2890
  if (messageCount <= 15) {
2617
- // Use FIFO but preserve more recent messages
2618
- const modifiedConfig = {
2619
- ...this.config,
2620
- preserveRecent: 5
2621
- };
2622
- const tempManager = new TokenBudgetManager(modifiedConfig, 'gpt-4o', this.logger);
2623
- return tempManager.compressFIFO(messages, targetTokens);
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;
2624
2897
  }
2625
2898
  // Late phase: aggressive compression (priority-based)
2626
2899
  return this.compressByPriority(messages, targetTokens);
@@ -2809,6 +3082,12 @@ const ConversationBuilderConfigSchema = zod.z.object({
2809
3082
  if (!this.budgetManager.canAddMessage(message, this.state.messages)) {
2810
3083
  this.logger.warn('Budget exceeded, compressing conversation');
2811
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
+ }
2812
3091
  }
2813
3092
  }
2814
3093
  this.state.messages.push(message);
@@ -2936,15 +3215,18 @@ const ConversationBuilderConfigSchema = zod.z.object({
2936
3215
  // Calculate position
2937
3216
  const position = this.calculatePosition(opts.position);
2938
3217
  // Format and inject
2939
- for (const item of itemsToAdd){
3218
+ for(let i = 0; i < itemsToAdd.length; i++){
3219
+ const item = itemsToAdd[i];
2940
3220
  const formatted = this.formatContextItem(item, opts.format);
2941
3221
  const contextMessage = {
2942
3222
  role: 'user',
2943
3223
  content: formatted
2944
3224
  };
2945
- this.state.messages.splice(position, 0, contextMessage);
2946
- // Track in context manager
2947
- this.state.contextManager.track(item, position);
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);
2948
3230
  }
2949
3231
  this.updateMetadata();
2950
3232
  return this;
@@ -2997,10 +3279,16 @@ const ConversationBuilderConfigSchema = zod.z.object({
2997
3279
  };
2998
3280
  }
2999
3281
  /**
3000
- * Export messages in OpenAI format
3282
+ * Export messages in OpenAI format (deep copy to prevent shared state)
3001
3283
  */ toMessages() {
3002
3284
  return this.state.messages.map((msg)=>({
3003
- ...msg
3285
+ ...msg,
3286
+ tool_calls: msg.tool_calls ? msg.tool_calls.map((tc)=>({
3287
+ ...tc,
3288
+ function: {
3289
+ ...tc.function
3290
+ }
3291
+ })) : undefined
3004
3292
  }));
3005
3293
  }
3006
3294
  /**
@@ -3036,7 +3324,7 @@ const ConversationBuilderConfigSchema = zod.z.object({
3036
3324
  return builder;
3037
3325
  }
3038
3326
  /**
3039
- * Clone the conversation for parallel exploration
3327
+ * Clone the conversation for parallel exploration (deep copy to prevent shared state)
3040
3328
  */ clone() {
3041
3329
  this.logger.debug('Cloning conversation');
3042
3330
  const cloned = ConversationBuilder.create({
@@ -3044,7 +3332,13 @@ const ConversationBuilderConfigSchema = zod.z.object({
3044
3332
  }, this.logger);
3045
3333
  // Deep copy state (note: contextManager is already created in constructor)
3046
3334
  cloned.state.messages = this.state.messages.map((msg)=>({
3047
- ...msg
3335
+ ...msg,
3336
+ tool_calls: msg.tool_calls ? msg.tool_calls.map((tc)=>({
3337
+ ...tc,
3338
+ function: {
3339
+ ...tc.function
3340
+ }
3341
+ })) : undefined
3048
3342
  }));
3049
3343
  cloned.state.metadata = {
3050
3344
  ...this.state.metadata
@@ -3215,6 +3509,12 @@ const ConversationBuilderConfigSchema = zod.z.object({
3215
3509
  }
3216
3510
  /**
3217
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)
3218
3518
  */ calculatePosition(position) {
3219
3519
  if (typeof position === 'number') {
3220
3520
  return Math.max(0, Math.min(position, this.state.messages.length));
@@ -3226,7 +3526,7 @@ const ConversationBuilderConfigSchema = zod.z.object({
3226
3526
  return Math.max(0, this.state.messages.length - 1);
3227
3527
  case 'after-system':
3228
3528
  {
3229
- // Find last system message (reverse search for compatibility)
3529
+ // Find last system message (uses reverse search to find most recent system message)
3230
3530
  let lastSystemIdx = -1;
3231
3531
  for(let i = this.state.messages.length - 1; i >= 0; i--){
3232
3532
  if (this.state.messages[i].role === 'system') {
@@ -3682,10 +3982,10 @@ function _define_property$1(obj, key, value) {
3682
3982
  // Calculate token usage if model provided
3683
3983
  let tokenUsage;
3684
3984
  if (model) {
3985
+ let counter;
3685
3986
  try {
3686
- const counter = new TokenCounter(model);
3987
+ counter = new TokenCounter(model);
3687
3988
  const total = counter.countConversation(messages);
3688
- counter.dispose();
3689
3989
  tokenUsage = {
3690
3990
  total,
3691
3991
  systemPrompt: 0,
@@ -3697,6 +3997,9 @@ function _define_property$1(obj, key, value) {
3697
3997
  this.logger.warn('Could not calculate token usage', {
3698
3998
  error
3699
3999
  });
4000
+ } finally{
4001
+ // Always dispose of the counter to prevent resource leaks
4002
+ counter === null || counter === void 0 ? void 0 : counter.dispose();
3700
4003
  }
3701
4004
  }
3702
4005
  return {
@@ -4090,7 +4393,8 @@ function _define_property(obj, key, value) {
4090
4393
  startTime,
4091
4394
  insights: [],
4092
4395
  findings: [],
4093
- errors: []
4396
+ errors: [],
4397
+ toolFailures: new Map()
4094
4398
  };
4095
4399
  this.logger.info('Starting strategy execution', {
4096
4400
  strategy: strategy.name
@@ -4130,10 +4434,6 @@ function _define_property(obj, key, value) {
4130
4434
  });
4131
4435
  const phaseResult = await this.executePhase(conversation, tools, phase, state, strategy);
4132
4436
  phaseResults.push(phaseResult);
4133
- // Track iteration for metrics
4134
- if (this.metricsCollector) {
4135
- this.metricsCollector.incrementIteration();
4136
- }
4137
4437
  await ((_strategy_onPhaseComplete = strategy.onPhaseComplete) === null || _strategy_onPhaseComplete === void 0 ? void 0 : _strategy_onPhaseComplete.call(strategy, phaseResult, state));
4138
4438
  // Check if should continue
4139
4439
  if (strategy.shouldContinue && !strategy.shouldContinue(state)) {
@@ -4228,6 +4528,10 @@ function _define_property(obj, key, value) {
4228
4528
  for(let i = 0; i < phase.maxIterations; i++){
4229
4529
  var _strategy_onIteration;
4230
4530
  state.iteration++;
4531
+ // Track iteration for metrics
4532
+ if (this.metricsCollector) {
4533
+ this.metricsCollector.incrementIteration();
4534
+ }
4231
4535
  this.logger.debug('Iteration', {
4232
4536
  phase: phase.name,
4233
4537
  iteration: i + 1
@@ -4253,6 +4557,7 @@ function _define_property(obj, key, value) {
4253
4557
  conversation.asAssistant(response.content, response.tool_calls);
4254
4558
  // Execute tools
4255
4559
  for (const toolCall of response.tool_calls){
4560
+ var _phase_maxConsecutiveToolFailures;
4256
4561
  var _strategy_onToolCall;
4257
4562
  // Check if tool is allowed in this phase
4258
4563
  if (phase.allowedTools && !phase.allowedTools.includes(toolCall.function.name)) {
@@ -4261,6 +4566,22 @@ function _define_property(obj, key, value) {
4261
4566
  });
4262
4567
  continue;
4263
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
+ }
4264
4585
  // Check tool call hook
4265
4586
  const toolAction = await ((_strategy_onToolCall = strategy.onToolCall) === null || _strategy_onToolCall === void 0 ? void 0 : _strategy_onToolCall.call(strategy, toolCall, state));
4266
4587
  if (toolAction === 'skip') {
@@ -4270,7 +4591,18 @@ function _define_property(obj, key, value) {
4270
4591
  const toolStart = Date.now();
4271
4592
  try {
4272
4593
  var _strategy_onToolResult;
4273
- const result = await tools.execute(toolCall.function.name, JSON.parse(toolCall.function.arguments));
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);
4274
4606
  const toolDuration = Date.now() - toolStart;
4275
4607
  const toolResult = {
4276
4608
  callId: toolCall.id,
@@ -4283,6 +4615,8 @@ function _define_property(obj, key, value) {
4283
4615
  success: true
4284
4616
  });
4285
4617
  state.toolCallsExecuted++;
4618
+ // Reset failure counter on success
4619
+ state.toolFailures.set(toolCall.function.name, 0);
4286
4620
  // Record metrics
4287
4621
  if (this.metricsCollector) {
4288
4622
  this.metricsCollector.recordToolCall(toolCall.function.name, state.iteration, toolDuration, true);
@@ -4309,6 +4643,9 @@ function _define_property(obj, key, value) {
4309
4643
  errorName: error.name
4310
4644
  });
4311
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);
4312
4649
  // Record metrics
4313
4650
  if (this.metricsCollector) {
4314
4651
  this.metricsCollector.recordToolCall(toolCall.function.name, state.iteration, toolDuration, false, error.message);
@@ -4786,7 +5123,7 @@ const cook = async (config)=>{
4786
5123
  if (finalConfig.tools) {
4787
5124
  const tools = Array.isArray(finalConfig.tools) ? finalConfig.tools : finalConfig.tools.getAll();
4788
5125
  // Filter by categories if specified
4789
- const filteredTools = finalConfig.toolCategories ? tools.filter((tool)=>finalConfig.toolCategories.includes(tool.category || '')) : tools;
5126
+ const filteredTools = finalConfig.toolCategories ? tools.filter((tool)=>tool.category && finalConfig.toolCategories.includes(tool.category)) : tools;
4790
5127
  if (filteredTools.length > 0 && finalConfig.toolGuidance) {
4791
5128
  const guidance = generateToolGuidance(filteredTools, finalConfig.toolGuidance);
4792
5129
  const toolSection = await parser$1.parse(guidance, {
@@ -4950,12 +5287,12 @@ const recipe = (basePath)=>{
4950
5287
  }
4951
5288
  return undefined;
4952
5289
  },
4953
- executeWith: async (llm, strategy, tokenBudget)=>{
5290
+ executeWith: async (llm, strategy, model = 'gpt-4o', tokenBudget)=>{
4954
5291
  const prompt = await cook(config);
4955
5292
  const conversation = ConversationBuilder.create({
4956
- model: 'gpt-4o'
5293
+ model
4957
5294
  }, config.logger);
4958
- conversation.fromPrompt(prompt, 'gpt-4o');
5295
+ conversation.fromPrompt(prompt, model);
4959
5296
  if (tokenBudget) {
4960
5297
  conversation.withTokenBudget(tokenBudget);
4961
5298
  }
@@ -4992,6 +5329,7 @@ exports.Loader = loader;
4992
5329
  exports.MessageBuilder = MessageBuilder;
4993
5330
  exports.MessageTemplates = MessageTemplates;
4994
5331
  exports.MetricsCollector = MetricsCollector;
5332
+ exports.ModelRegistry = ModelRegistry;
4995
5333
  exports.Override = override;
4996
5334
  exports.Parser = parser;
4997
5335
  exports.Recipes = recipes;
@@ -5001,6 +5339,7 @@ exports.TokenBudgetManager = TokenBudgetManager;
5001
5339
  exports.TokenCounter = TokenCounter;
5002
5340
  exports.ToolRegistry = ToolRegistry;
5003
5341
  exports.clearTemplates = clearTemplates;
5342
+ exports.configureModel = configureModel;
5004
5343
  exports.cook = cook;
5005
5344
  exports.createContent = create$b;
5006
5345
  exports.createContext = create$a;
@@ -5011,7 +5350,13 @@ exports.createSection = create$8;
5011
5350
  exports.createTrait = create$7;
5012
5351
  exports.createWeighted = create$c;
5013
5352
  exports.generateToolGuidance = generateToolGuidance;
5353
+ exports.getEncoding = getEncoding;
5354
+ exports.getModelFamily = getModelFamily;
5355
+ exports.getModelRegistry = getModelRegistry;
5356
+ exports.getPersonaRole = getPersonaRole$1;
5014
5357
  exports.getTemplates = getTemplates;
5015
5358
  exports.recipe = recipe;
5016
5359
  exports.registerTemplates = registerTemplates;
5360
+ exports.resetModelRegistry = resetModelRegistry;
5361
+ exports.supportsToolCalls = supportsToolCalls;
5017
5362
  //# sourceMappingURL=riotprompt.cjs.map