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