llmist 1.2.0 → 1.3.1
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/README.md +12 -75
- package/dist/{chunk-KORMY3CD.js → chunk-RZTAKIDE.js} +605 -4
- package/dist/chunk-RZTAKIDE.js.map +1 -0
- package/dist/{chunk-LELPPETT.js → chunk-TFIKR2RK.js} +459 -3
- package/dist/chunk-TFIKR2RK.js.map +1 -0
- package/dist/cli.cjs +628 -23
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +49 -22
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +769 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +354 -32
- package/dist/index.d.ts +354 -32
- package/dist/index.js +177 -2
- package/dist/index.js.map +1 -1
- package/dist/{mock-stream-DKF5yatf.d.cts → mock-stream-DNt-HBTn.d.cts} +525 -79
- package/dist/{mock-stream-DKF5yatf.d.ts → mock-stream-DNt-HBTn.d.ts} +525 -79
- package/dist/testing/index.cjs +1063 -4
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +437 -3
- package/dist/testing/index.d.ts +437 -3
- package/dist/testing/index.js +54 -4
- package/package.json +1 -1
- package/dist/chunk-KORMY3CD.js.map +0 -1
- package/dist/chunk-LELPPETT.js.map +0 -1
package/dist/cli.cjs
CHANGED
|
@@ -1113,6 +1113,417 @@ var init_output_viewer = __esm({
|
|
|
1113
1113
|
}
|
|
1114
1114
|
});
|
|
1115
1115
|
|
|
1116
|
+
// src/agent/compaction/config.ts
|
|
1117
|
+
function resolveCompactionConfig(config = {}) {
|
|
1118
|
+
const trigger = config.triggerThresholdPercent ?? DEFAULT_COMPACTION_CONFIG.triggerThresholdPercent;
|
|
1119
|
+
const target = config.targetPercent ?? DEFAULT_COMPACTION_CONFIG.targetPercent;
|
|
1120
|
+
if (target >= trigger) {
|
|
1121
|
+
console.warn(
|
|
1122
|
+
`[llmist/compaction] targetPercent (${target}) should be less than triggerThresholdPercent (${trigger}) to be effective.`
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
const strategy = config.strategy ?? DEFAULT_COMPACTION_CONFIG.strategy;
|
|
1126
|
+
const strategyName = typeof strategy === "object" && "name" in strategy ? strategy.name : strategy;
|
|
1127
|
+
return {
|
|
1128
|
+
enabled: config.enabled ?? DEFAULT_COMPACTION_CONFIG.enabled,
|
|
1129
|
+
strategy: strategyName,
|
|
1130
|
+
triggerThresholdPercent: trigger,
|
|
1131
|
+
targetPercent: target,
|
|
1132
|
+
preserveRecentTurns: config.preserveRecentTurns ?? DEFAULT_COMPACTION_CONFIG.preserveRecentTurns,
|
|
1133
|
+
summarizationModel: config.summarizationModel,
|
|
1134
|
+
summarizationPrompt: config.summarizationPrompt ?? DEFAULT_SUMMARIZATION_PROMPT,
|
|
1135
|
+
onCompaction: config.onCompaction
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
var DEFAULT_COMPACTION_CONFIG, DEFAULT_SUMMARIZATION_PROMPT;
|
|
1139
|
+
var init_config = __esm({
|
|
1140
|
+
"src/agent/compaction/config.ts"() {
|
|
1141
|
+
"use strict";
|
|
1142
|
+
DEFAULT_COMPACTION_CONFIG = {
|
|
1143
|
+
enabled: true,
|
|
1144
|
+
strategy: "hybrid",
|
|
1145
|
+
triggerThresholdPercent: 80,
|
|
1146
|
+
targetPercent: 50,
|
|
1147
|
+
preserveRecentTurns: 5
|
|
1148
|
+
};
|
|
1149
|
+
DEFAULT_SUMMARIZATION_PROMPT = `Summarize this conversation history concisely, preserving:
|
|
1150
|
+
1. Key decisions made and their rationale
|
|
1151
|
+
2. Important facts and data discovered
|
|
1152
|
+
3. Errors encountered and how they were resolved
|
|
1153
|
+
4. Current task context and goals
|
|
1154
|
+
|
|
1155
|
+
Format as a brief narrative paragraph, not bullet points.
|
|
1156
|
+
Previous conversation:`;
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
// src/agent/compaction/strategy.ts
|
|
1161
|
+
function groupIntoTurns(messages) {
|
|
1162
|
+
const turns = [];
|
|
1163
|
+
let currentTurn = [];
|
|
1164
|
+
for (const msg of messages) {
|
|
1165
|
+
if (msg.role === "user" && currentTurn.length > 0) {
|
|
1166
|
+
turns.push({
|
|
1167
|
+
messages: currentTurn,
|
|
1168
|
+
tokenEstimate: estimateTurnTokens(currentTurn)
|
|
1169
|
+
});
|
|
1170
|
+
currentTurn = [msg];
|
|
1171
|
+
} else {
|
|
1172
|
+
currentTurn.push(msg);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
if (currentTurn.length > 0) {
|
|
1176
|
+
turns.push({
|
|
1177
|
+
messages: currentTurn,
|
|
1178
|
+
tokenEstimate: estimateTurnTokens(currentTurn)
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
return turns;
|
|
1182
|
+
}
|
|
1183
|
+
function estimateTurnTokens(messages) {
|
|
1184
|
+
return Math.ceil(messages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0) / 4);
|
|
1185
|
+
}
|
|
1186
|
+
function flattenTurns(turns) {
|
|
1187
|
+
return turns.flatMap((turn) => turn.messages);
|
|
1188
|
+
}
|
|
1189
|
+
var init_strategy = __esm({
|
|
1190
|
+
"src/agent/compaction/strategy.ts"() {
|
|
1191
|
+
"use strict";
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
// src/agent/compaction/strategies/sliding-window.ts
|
|
1196
|
+
var TRUNCATION_MARKER_TEMPLATE, SlidingWindowStrategy;
|
|
1197
|
+
var init_sliding_window = __esm({
|
|
1198
|
+
"src/agent/compaction/strategies/sliding-window.ts"() {
|
|
1199
|
+
"use strict";
|
|
1200
|
+
init_strategy();
|
|
1201
|
+
TRUNCATION_MARKER_TEMPLATE = "[Previous conversation truncated. Removed {count} turn(s) to fit context window.]";
|
|
1202
|
+
SlidingWindowStrategy = class {
|
|
1203
|
+
name = "sliding-window";
|
|
1204
|
+
async compact(messages, config, context) {
|
|
1205
|
+
const turns = groupIntoTurns(messages);
|
|
1206
|
+
const preserveCount = Math.min(config.preserveRecentTurns, turns.length);
|
|
1207
|
+
if (turns.length <= preserveCount) {
|
|
1208
|
+
return {
|
|
1209
|
+
messages,
|
|
1210
|
+
strategyName: this.name,
|
|
1211
|
+
metadata: {
|
|
1212
|
+
originalCount: messages.length,
|
|
1213
|
+
compactedCount: messages.length,
|
|
1214
|
+
tokensBefore: context.currentTokens,
|
|
1215
|
+
tokensAfter: context.currentTokens
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
const turnsToKeep = turns.slice(-preserveCount);
|
|
1220
|
+
const turnsRemoved = turns.length - preserveCount;
|
|
1221
|
+
const truncationMarker = {
|
|
1222
|
+
role: "user",
|
|
1223
|
+
content: TRUNCATION_MARKER_TEMPLATE.replace("{count}", turnsRemoved.toString())
|
|
1224
|
+
};
|
|
1225
|
+
const compactedMessages = [truncationMarker, ...flattenTurns(turnsToKeep)];
|
|
1226
|
+
const tokensAfter = Math.ceil(
|
|
1227
|
+
compactedMessages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0) / 4
|
|
1228
|
+
);
|
|
1229
|
+
return {
|
|
1230
|
+
messages: compactedMessages,
|
|
1231
|
+
strategyName: this.name,
|
|
1232
|
+
metadata: {
|
|
1233
|
+
originalCount: messages.length,
|
|
1234
|
+
compactedCount: compactedMessages.length,
|
|
1235
|
+
tokensBefore: context.currentTokens,
|
|
1236
|
+
tokensAfter
|
|
1237
|
+
}
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
});
|
|
1243
|
+
|
|
1244
|
+
// src/agent/compaction/strategies/summarization.ts
|
|
1245
|
+
var SummarizationStrategy;
|
|
1246
|
+
var init_summarization = __esm({
|
|
1247
|
+
"src/agent/compaction/strategies/summarization.ts"() {
|
|
1248
|
+
"use strict";
|
|
1249
|
+
init_strategy();
|
|
1250
|
+
SummarizationStrategy = class {
|
|
1251
|
+
name = "summarization";
|
|
1252
|
+
async compact(messages, config, context) {
|
|
1253
|
+
const turns = groupIntoTurns(messages);
|
|
1254
|
+
const preserveCount = Math.min(config.preserveRecentTurns, turns.length);
|
|
1255
|
+
if (turns.length <= preserveCount) {
|
|
1256
|
+
return {
|
|
1257
|
+
messages,
|
|
1258
|
+
strategyName: this.name,
|
|
1259
|
+
metadata: {
|
|
1260
|
+
originalCount: messages.length,
|
|
1261
|
+
compactedCount: messages.length,
|
|
1262
|
+
tokensBefore: context.currentTokens,
|
|
1263
|
+
tokensAfter: context.currentTokens
|
|
1264
|
+
}
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
const turnsToSummarize = turns.slice(0, -preserveCount);
|
|
1268
|
+
const turnsToKeep = turns.slice(-preserveCount);
|
|
1269
|
+
const conversationToSummarize = this.formatTurnsForSummary(flattenTurns(turnsToSummarize));
|
|
1270
|
+
const summary = await this.generateSummary(conversationToSummarize, config, context);
|
|
1271
|
+
const summaryMessage = {
|
|
1272
|
+
role: "user",
|
|
1273
|
+
content: `[Previous conversation summary]
|
|
1274
|
+
${summary}
|
|
1275
|
+
[End of summary - conversation continues below]`
|
|
1276
|
+
};
|
|
1277
|
+
const compactedMessages = [summaryMessage, ...flattenTurns(turnsToKeep)];
|
|
1278
|
+
const tokensAfter = Math.ceil(
|
|
1279
|
+
compactedMessages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0) / 4
|
|
1280
|
+
);
|
|
1281
|
+
return {
|
|
1282
|
+
messages: compactedMessages,
|
|
1283
|
+
summary,
|
|
1284
|
+
strategyName: this.name,
|
|
1285
|
+
metadata: {
|
|
1286
|
+
originalCount: messages.length,
|
|
1287
|
+
compactedCount: compactedMessages.length,
|
|
1288
|
+
tokensBefore: context.currentTokens,
|
|
1289
|
+
tokensAfter
|
|
1290
|
+
}
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Formats messages into a readable conversation format for summarization.
|
|
1295
|
+
*/
|
|
1296
|
+
formatTurnsForSummary(messages) {
|
|
1297
|
+
return messages.map((msg) => {
|
|
1298
|
+
const role = msg.role.charAt(0).toUpperCase() + msg.role.slice(1);
|
|
1299
|
+
return `${role}: ${msg.content}`;
|
|
1300
|
+
}).join("\n\n");
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Generates a summary using the configured LLM.
|
|
1304
|
+
*/
|
|
1305
|
+
async generateSummary(conversation, config, context) {
|
|
1306
|
+
const model = config.summarizationModel ?? context.model;
|
|
1307
|
+
const prompt = `${config.summarizationPrompt}
|
|
1308
|
+
|
|
1309
|
+
${conversation}`;
|
|
1310
|
+
const response = await context.client.complete(prompt, {
|
|
1311
|
+
model,
|
|
1312
|
+
temperature: 0.3
|
|
1313
|
+
// Low temperature for factual summarization
|
|
1314
|
+
});
|
|
1315
|
+
return response.trim();
|
|
1316
|
+
}
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
});
|
|
1320
|
+
|
|
1321
|
+
// src/agent/compaction/strategies/hybrid.ts
|
|
1322
|
+
var MIN_TURNS_FOR_SUMMARIZATION, HybridStrategy;
|
|
1323
|
+
var init_hybrid = __esm({
|
|
1324
|
+
"src/agent/compaction/strategies/hybrid.ts"() {
|
|
1325
|
+
"use strict";
|
|
1326
|
+
init_strategy();
|
|
1327
|
+
init_sliding_window();
|
|
1328
|
+
init_summarization();
|
|
1329
|
+
MIN_TURNS_FOR_SUMMARIZATION = 3;
|
|
1330
|
+
HybridStrategy = class {
|
|
1331
|
+
name = "hybrid";
|
|
1332
|
+
slidingWindow = new SlidingWindowStrategy();
|
|
1333
|
+
summarization = new SummarizationStrategy();
|
|
1334
|
+
async compact(messages, config, context) {
|
|
1335
|
+
const turns = groupIntoTurns(messages);
|
|
1336
|
+
const preserveCount = Math.min(config.preserveRecentTurns, turns.length);
|
|
1337
|
+
if (turns.length <= preserveCount) {
|
|
1338
|
+
return {
|
|
1339
|
+
messages,
|
|
1340
|
+
strategyName: this.name,
|
|
1341
|
+
metadata: {
|
|
1342
|
+
originalCount: messages.length,
|
|
1343
|
+
compactedCount: messages.length,
|
|
1344
|
+
tokensBefore: context.currentTokens,
|
|
1345
|
+
tokensAfter: context.currentTokens
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
const turnsToSummarize = turns.length - preserveCount;
|
|
1350
|
+
if (turnsToSummarize < MIN_TURNS_FOR_SUMMARIZATION) {
|
|
1351
|
+
return this.slidingWindow.compact(messages, config, context);
|
|
1352
|
+
}
|
|
1353
|
+
return this.summarization.compact(messages, config, context);
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1359
|
+
// src/agent/compaction/strategies/index.ts
|
|
1360
|
+
var init_strategies = __esm({
|
|
1361
|
+
"src/agent/compaction/strategies/index.ts"() {
|
|
1362
|
+
"use strict";
|
|
1363
|
+
init_sliding_window();
|
|
1364
|
+
init_summarization();
|
|
1365
|
+
init_hybrid();
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
|
|
1369
|
+
// src/agent/compaction/manager.ts
|
|
1370
|
+
function createStrategy(name) {
|
|
1371
|
+
switch (name) {
|
|
1372
|
+
case "sliding-window":
|
|
1373
|
+
return new SlidingWindowStrategy();
|
|
1374
|
+
case "summarization":
|
|
1375
|
+
return new SummarizationStrategy();
|
|
1376
|
+
case "hybrid":
|
|
1377
|
+
return new HybridStrategy();
|
|
1378
|
+
default:
|
|
1379
|
+
throw new Error(`Unknown compaction strategy: ${name}`);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
var CompactionManager;
|
|
1383
|
+
var init_manager = __esm({
|
|
1384
|
+
"src/agent/compaction/manager.ts"() {
|
|
1385
|
+
"use strict";
|
|
1386
|
+
init_config();
|
|
1387
|
+
init_strategies();
|
|
1388
|
+
CompactionManager = class {
|
|
1389
|
+
client;
|
|
1390
|
+
model;
|
|
1391
|
+
config;
|
|
1392
|
+
strategy;
|
|
1393
|
+
modelLimits;
|
|
1394
|
+
// Statistics
|
|
1395
|
+
totalCompactions = 0;
|
|
1396
|
+
totalTokensSaved = 0;
|
|
1397
|
+
lastTokenCount = 0;
|
|
1398
|
+
constructor(client, model, config = {}) {
|
|
1399
|
+
this.client = client;
|
|
1400
|
+
this.model = model;
|
|
1401
|
+
this.config = resolveCompactionConfig(config);
|
|
1402
|
+
if (typeof config.strategy === "object" && "compact" in config.strategy) {
|
|
1403
|
+
this.strategy = config.strategy;
|
|
1404
|
+
} else {
|
|
1405
|
+
this.strategy = createStrategy(this.config.strategy);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Check if compaction is needed and perform it if so.
|
|
1410
|
+
*
|
|
1411
|
+
* @param conversation - The conversation manager to compact
|
|
1412
|
+
* @param iteration - Current agent iteration (for event metadata)
|
|
1413
|
+
* @returns CompactionEvent if compaction was performed, null otherwise
|
|
1414
|
+
*/
|
|
1415
|
+
async checkAndCompact(conversation, iteration) {
|
|
1416
|
+
if (!this.config.enabled) {
|
|
1417
|
+
return null;
|
|
1418
|
+
}
|
|
1419
|
+
if (!this.modelLimits) {
|
|
1420
|
+
this.modelLimits = this.client.modelRegistry.getModelLimits(this.model);
|
|
1421
|
+
if (!this.modelLimits) {
|
|
1422
|
+
return null;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
if (!this.client.countTokens) {
|
|
1426
|
+
return null;
|
|
1427
|
+
}
|
|
1428
|
+
const messages = conversation.getMessages();
|
|
1429
|
+
const currentTokens = await this.client.countTokens(this.model, messages);
|
|
1430
|
+
this.lastTokenCount = currentTokens;
|
|
1431
|
+
const usagePercent = currentTokens / this.modelLimits.contextWindow * 100;
|
|
1432
|
+
if (usagePercent < this.config.triggerThresholdPercent) {
|
|
1433
|
+
return null;
|
|
1434
|
+
}
|
|
1435
|
+
const historyMessages = conversation.getHistoryMessages();
|
|
1436
|
+
const baseMessages = conversation.getBaseMessages();
|
|
1437
|
+
const historyTokens = await this.client.countTokens(this.model, historyMessages);
|
|
1438
|
+
const baseTokens = await this.client.countTokens(this.model, baseMessages);
|
|
1439
|
+
return this.compact(conversation, iteration, {
|
|
1440
|
+
historyMessages,
|
|
1441
|
+
baseMessages,
|
|
1442
|
+
historyTokens,
|
|
1443
|
+
baseTokens,
|
|
1444
|
+
currentTokens: historyTokens + baseTokens
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Force compaction regardless of threshold.
|
|
1449
|
+
*
|
|
1450
|
+
* @param conversation - The conversation manager to compact
|
|
1451
|
+
* @param iteration - Current agent iteration (for event metadata). Use -1 for manual compaction.
|
|
1452
|
+
* @param precomputed - Optional pre-computed token counts (passed from checkAndCompact for efficiency)
|
|
1453
|
+
* @returns CompactionEvent with compaction details
|
|
1454
|
+
*/
|
|
1455
|
+
async compact(conversation, iteration, precomputed) {
|
|
1456
|
+
if (!this.modelLimits) {
|
|
1457
|
+
this.modelLimits = this.client.modelRegistry.getModelLimits(this.model);
|
|
1458
|
+
if (!this.modelLimits) {
|
|
1459
|
+
return null;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
const historyMessages = precomputed?.historyMessages ?? conversation.getHistoryMessages();
|
|
1463
|
+
const baseMessages = precomputed?.baseMessages ?? conversation.getBaseMessages();
|
|
1464
|
+
const historyTokens = precomputed?.historyTokens ?? await this.client.countTokens(this.model, historyMessages);
|
|
1465
|
+
const baseTokens = precomputed?.baseTokens ?? await this.client.countTokens(this.model, baseMessages);
|
|
1466
|
+
const currentTokens = precomputed?.currentTokens ?? historyTokens + baseTokens;
|
|
1467
|
+
const targetTotalTokens = Math.floor(
|
|
1468
|
+
this.modelLimits.contextWindow * this.config.targetPercent / 100
|
|
1469
|
+
);
|
|
1470
|
+
const targetHistoryTokens = Math.max(0, targetTotalTokens - baseTokens);
|
|
1471
|
+
const result = await this.strategy.compact(historyMessages, this.config, {
|
|
1472
|
+
currentTokens: historyTokens,
|
|
1473
|
+
targetTokens: targetHistoryTokens,
|
|
1474
|
+
modelLimits: this.modelLimits,
|
|
1475
|
+
client: this.client,
|
|
1476
|
+
model: this.config.summarizationModel ?? this.model
|
|
1477
|
+
});
|
|
1478
|
+
conversation.replaceHistory(result.messages);
|
|
1479
|
+
const afterTokens = await this.client.countTokens(this.model, conversation.getMessages());
|
|
1480
|
+
const tokensSaved = currentTokens - afterTokens;
|
|
1481
|
+
this.totalCompactions++;
|
|
1482
|
+
this.totalTokensSaved += tokensSaved;
|
|
1483
|
+
this.lastTokenCount = afterTokens;
|
|
1484
|
+
const event = {
|
|
1485
|
+
strategy: result.strategyName,
|
|
1486
|
+
tokensBefore: currentTokens,
|
|
1487
|
+
tokensAfter: afterTokens,
|
|
1488
|
+
messagesBefore: historyMessages.length + baseMessages.length,
|
|
1489
|
+
messagesAfter: result.messages.length + baseMessages.length,
|
|
1490
|
+
summary: result.summary,
|
|
1491
|
+
iteration
|
|
1492
|
+
};
|
|
1493
|
+
if (this.config.onCompaction) {
|
|
1494
|
+
try {
|
|
1495
|
+
this.config.onCompaction(event);
|
|
1496
|
+
} catch (err) {
|
|
1497
|
+
console.warn("[llmist/compaction] onCompaction callback error:", err);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
return event;
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Get compaction statistics.
|
|
1504
|
+
*/
|
|
1505
|
+
getStats() {
|
|
1506
|
+
const contextWindow = this.modelLimits?.contextWindow ?? 0;
|
|
1507
|
+
return {
|
|
1508
|
+
totalCompactions: this.totalCompactions,
|
|
1509
|
+
totalTokensSaved: this.totalTokensSaved,
|
|
1510
|
+
currentUsage: {
|
|
1511
|
+
tokens: this.lastTokenCount,
|
|
1512
|
+
percent: contextWindow > 0 ? this.lastTokenCount / contextWindow * 100 : 0
|
|
1513
|
+
},
|
|
1514
|
+
contextWindow
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1518
|
+
* Check if compaction is enabled.
|
|
1519
|
+
*/
|
|
1520
|
+
isEnabled() {
|
|
1521
|
+
return this.config.enabled;
|
|
1522
|
+
}
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
});
|
|
1526
|
+
|
|
1116
1527
|
// src/agent/gadget-output-store.ts
|
|
1117
1528
|
var import_node_crypto, GadgetOutputStore;
|
|
1118
1529
|
var init_gadget_output_store = __esm({
|
|
@@ -1215,10 +1626,16 @@ var init_conversation_manager = __esm({
|
|
|
1215
1626
|
baseMessages;
|
|
1216
1627
|
initialMessages;
|
|
1217
1628
|
historyBuilder;
|
|
1629
|
+
startPrefix;
|
|
1630
|
+
endPrefix;
|
|
1631
|
+
argPrefix;
|
|
1218
1632
|
constructor(baseMessages, initialMessages, options = {}) {
|
|
1219
1633
|
this.baseMessages = baseMessages;
|
|
1220
1634
|
this.initialMessages = initialMessages;
|
|
1221
1635
|
this.historyBuilder = new LLMMessageBuilder();
|
|
1636
|
+
this.startPrefix = options.startPrefix;
|
|
1637
|
+
this.endPrefix = options.endPrefix;
|
|
1638
|
+
this.argPrefix = options.argPrefix;
|
|
1222
1639
|
if (options.startPrefix && options.endPrefix) {
|
|
1223
1640
|
this.historyBuilder.withPrefixes(options.startPrefix, options.endPrefix, options.argPrefix);
|
|
1224
1641
|
}
|
|
@@ -1235,6 +1652,25 @@ var init_conversation_manager = __esm({
|
|
|
1235
1652
|
getMessages() {
|
|
1236
1653
|
return [...this.baseMessages, ...this.initialMessages, ...this.historyBuilder.build()];
|
|
1237
1654
|
}
|
|
1655
|
+
getHistoryMessages() {
|
|
1656
|
+
return this.historyBuilder.build();
|
|
1657
|
+
}
|
|
1658
|
+
getBaseMessages() {
|
|
1659
|
+
return [...this.baseMessages, ...this.initialMessages];
|
|
1660
|
+
}
|
|
1661
|
+
replaceHistory(newHistory) {
|
|
1662
|
+
this.historyBuilder = new LLMMessageBuilder();
|
|
1663
|
+
if (this.startPrefix && this.endPrefix) {
|
|
1664
|
+
this.historyBuilder.withPrefixes(this.startPrefix, this.endPrefix, this.argPrefix);
|
|
1665
|
+
}
|
|
1666
|
+
for (const msg of newHistory) {
|
|
1667
|
+
if (msg.role === "user") {
|
|
1668
|
+
this.historyBuilder.addUser(msg.content);
|
|
1669
|
+
} else if (msg.role === "assistant") {
|
|
1670
|
+
this.historyBuilder.addAssistant(msg.content);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1238
1674
|
};
|
|
1239
1675
|
}
|
|
1240
1676
|
});
|
|
@@ -2781,6 +3217,7 @@ var init_agent = __esm({
|
|
|
2781
3217
|
init_model_shortcuts();
|
|
2782
3218
|
init_output_viewer();
|
|
2783
3219
|
init_logger();
|
|
3220
|
+
init_manager();
|
|
2784
3221
|
init_gadget_output_store();
|
|
2785
3222
|
init_agent_internal_key();
|
|
2786
3223
|
init_conversation_manager();
|
|
@@ -2811,6 +3248,8 @@ var init_agent = __esm({
|
|
|
2811
3248
|
outputStore;
|
|
2812
3249
|
outputLimitEnabled;
|
|
2813
3250
|
outputLimitCharLimit;
|
|
3251
|
+
// Context compaction
|
|
3252
|
+
compactionManager;
|
|
2814
3253
|
/**
|
|
2815
3254
|
* Creates a new Agent instance.
|
|
2816
3255
|
* @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
|
|
@@ -2870,6 +3309,14 @@ var init_agent = __esm({
|
|
|
2870
3309
|
if (options.userPrompt) {
|
|
2871
3310
|
this.conversation.addUserMessage(options.userPrompt);
|
|
2872
3311
|
}
|
|
3312
|
+
const compactionEnabled = options.compactionConfig?.enabled ?? true;
|
|
3313
|
+
if (compactionEnabled) {
|
|
3314
|
+
this.compactionManager = new CompactionManager(
|
|
3315
|
+
this.client,
|
|
3316
|
+
this.model,
|
|
3317
|
+
options.compactionConfig
|
|
3318
|
+
);
|
|
3319
|
+
}
|
|
2873
3320
|
}
|
|
2874
3321
|
/**
|
|
2875
3322
|
* Get the gadget registry for this agent.
|
|
@@ -2892,6 +3339,53 @@ var init_agent = __esm({
|
|
|
2892
3339
|
getRegistry() {
|
|
2893
3340
|
return this.registry;
|
|
2894
3341
|
}
|
|
3342
|
+
/**
|
|
3343
|
+
* Manually trigger context compaction.
|
|
3344
|
+
*
|
|
3345
|
+
* Forces compaction regardless of threshold. Useful for:
|
|
3346
|
+
* - Pre-emptive context management before expected long operations
|
|
3347
|
+
* - Testing compaction behavior
|
|
3348
|
+
*
|
|
3349
|
+
* @returns CompactionEvent if compaction was performed, null if not configured or no history
|
|
3350
|
+
*
|
|
3351
|
+
* @example
|
|
3352
|
+
* ```typescript
|
|
3353
|
+
* const agent = await LLMist.createAgent()
|
|
3354
|
+
* .withModel('sonnet')
|
|
3355
|
+
* .withCompaction()
|
|
3356
|
+
* .ask('...');
|
|
3357
|
+
*
|
|
3358
|
+
* // Manually compact before a long operation
|
|
3359
|
+
* const event = await agent.compact();
|
|
3360
|
+
* if (event) {
|
|
3361
|
+
* console.log(`Saved ${event.tokensBefore - event.tokensAfter} tokens`);
|
|
3362
|
+
* }
|
|
3363
|
+
* ```
|
|
3364
|
+
*/
|
|
3365
|
+
async compact() {
|
|
3366
|
+
if (!this.compactionManager) {
|
|
3367
|
+
return null;
|
|
3368
|
+
}
|
|
3369
|
+
return this.compactionManager.compact(this.conversation, -1);
|
|
3370
|
+
}
|
|
3371
|
+
/**
|
|
3372
|
+
* Get compaction statistics.
|
|
3373
|
+
*
|
|
3374
|
+
* @returns CompactionStats if compaction is enabled, null otherwise
|
|
3375
|
+
*
|
|
3376
|
+
* @example
|
|
3377
|
+
* ```typescript
|
|
3378
|
+
* const stats = agent.getCompactionStats();
|
|
3379
|
+
* if (stats) {
|
|
3380
|
+
* console.log(`Total compactions: ${stats.totalCompactions}`);
|
|
3381
|
+
* console.log(`Tokens saved: ${stats.totalTokensSaved}`);
|
|
3382
|
+
* console.log(`Current usage: ${stats.currentUsage.percent.toFixed(1)}%`);
|
|
3383
|
+
* }
|
|
3384
|
+
* ```
|
|
3385
|
+
*/
|
|
3386
|
+
getCompactionStats() {
|
|
3387
|
+
return this.compactionManager?.getStats() ?? null;
|
|
3388
|
+
}
|
|
2895
3389
|
/**
|
|
2896
3390
|
* Run the agent loop.
|
|
2897
3391
|
* Clean, simple orchestration - all complexity is in StreamProcessor.
|
|
@@ -2912,6 +3406,30 @@ var init_agent = __esm({
|
|
|
2912
3406
|
while (currentIteration < this.maxIterations) {
|
|
2913
3407
|
this.logger.debug("Starting iteration", { iteration: currentIteration });
|
|
2914
3408
|
try {
|
|
3409
|
+
if (this.compactionManager) {
|
|
3410
|
+
const compactionEvent = await this.compactionManager.checkAndCompact(
|
|
3411
|
+
this.conversation,
|
|
3412
|
+
currentIteration
|
|
3413
|
+
);
|
|
3414
|
+
if (compactionEvent) {
|
|
3415
|
+
this.logger.info("Context compacted", {
|
|
3416
|
+
strategy: compactionEvent.strategy,
|
|
3417
|
+
tokensBefore: compactionEvent.tokensBefore,
|
|
3418
|
+
tokensAfter: compactionEvent.tokensAfter
|
|
3419
|
+
});
|
|
3420
|
+
yield { type: "compaction", event: compactionEvent };
|
|
3421
|
+
await this.safeObserve(async () => {
|
|
3422
|
+
if (this.hooks.observers?.onCompaction) {
|
|
3423
|
+
await this.hooks.observers.onCompaction({
|
|
3424
|
+
iteration: currentIteration,
|
|
3425
|
+
event: compactionEvent,
|
|
3426
|
+
stats: this.compactionManager.getStats(),
|
|
3427
|
+
logger: this.logger
|
|
3428
|
+
});
|
|
3429
|
+
}
|
|
3430
|
+
});
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
2915
3433
|
let llmOptions = {
|
|
2916
3434
|
model: this.model,
|
|
2917
3435
|
messages: this.conversation.getMessages(),
|
|
@@ -2931,6 +3449,7 @@ var init_agent = __esm({
|
|
|
2931
3449
|
if (this.hooks.controllers?.beforeLLMCall) {
|
|
2932
3450
|
const context = {
|
|
2933
3451
|
iteration: currentIteration,
|
|
3452
|
+
maxIterations: this.maxIterations,
|
|
2934
3453
|
options: llmOptions,
|
|
2935
3454
|
logger: this.logger
|
|
2936
3455
|
};
|
|
@@ -2995,12 +3514,17 @@ var init_agent = __esm({
|
|
|
2995
3514
|
});
|
|
2996
3515
|
let finalMessage = result.finalMessage;
|
|
2997
3516
|
if (this.hooks.controllers?.afterLLMCall) {
|
|
3517
|
+
const gadgetCallCount = result.outputs.filter(
|
|
3518
|
+
(output) => output.type === "gadget_result"
|
|
3519
|
+
).length;
|
|
2998
3520
|
const context = {
|
|
2999
3521
|
iteration: currentIteration,
|
|
3522
|
+
maxIterations: this.maxIterations,
|
|
3000
3523
|
options: llmOptions,
|
|
3001
3524
|
finishReason: result.finishReason,
|
|
3002
3525
|
usage: result.usage,
|
|
3003
3526
|
finalMessage: result.finalMessage,
|
|
3527
|
+
gadgetCallCount,
|
|
3004
3528
|
logger: this.logger
|
|
3005
3529
|
};
|
|
3006
3530
|
const action = await this.hooks.controllers.afterLLMCall(context);
|
|
@@ -5235,6 +5759,7 @@ var init_builder = __esm({
|
|
|
5235
5759
|
defaultGadgetTimeoutMs;
|
|
5236
5760
|
gadgetOutputLimit;
|
|
5237
5761
|
gadgetOutputLimitPercent;
|
|
5762
|
+
compactionConfig;
|
|
5238
5763
|
constructor(client) {
|
|
5239
5764
|
this.client = client;
|
|
5240
5765
|
}
|
|
@@ -5630,6 +6155,57 @@ var init_builder = __esm({
|
|
|
5630
6155
|
this.gadgetOutputLimitPercent = percent;
|
|
5631
6156
|
return this;
|
|
5632
6157
|
}
|
|
6158
|
+
/**
|
|
6159
|
+
* Configure context compaction.
|
|
6160
|
+
*
|
|
6161
|
+
* Context compaction automatically manages conversation history to prevent
|
|
6162
|
+
* context window overflow in long-running agent conversations.
|
|
6163
|
+
*
|
|
6164
|
+
* @param config - Compaction configuration options
|
|
6165
|
+
* @returns This builder for chaining
|
|
6166
|
+
*
|
|
6167
|
+
* @example
|
|
6168
|
+
* ```typescript
|
|
6169
|
+
* // Custom thresholds
|
|
6170
|
+
* .withCompaction({
|
|
6171
|
+
* triggerThresholdPercent: 70,
|
|
6172
|
+
* targetPercent: 40,
|
|
6173
|
+
* preserveRecentTurns: 10,
|
|
6174
|
+
* })
|
|
6175
|
+
*
|
|
6176
|
+
* // Different strategy
|
|
6177
|
+
* .withCompaction({
|
|
6178
|
+
* strategy: 'sliding-window',
|
|
6179
|
+
* })
|
|
6180
|
+
*
|
|
6181
|
+
* // With callback
|
|
6182
|
+
* .withCompaction({
|
|
6183
|
+
* onCompaction: (event) => {
|
|
6184
|
+
* console.log(`Saved ${event.tokensBefore - event.tokensAfter} tokens`);
|
|
6185
|
+
* }
|
|
6186
|
+
* })
|
|
6187
|
+
* ```
|
|
6188
|
+
*/
|
|
6189
|
+
withCompaction(config) {
|
|
6190
|
+
this.compactionConfig = { ...config, enabled: config.enabled ?? true };
|
|
6191
|
+
return this;
|
|
6192
|
+
}
|
|
6193
|
+
/**
|
|
6194
|
+
* Disable context compaction.
|
|
6195
|
+
*
|
|
6196
|
+
* By default, compaction is enabled. Use this method to explicitly disable it.
|
|
6197
|
+
*
|
|
6198
|
+
* @returns This builder for chaining
|
|
6199
|
+
*
|
|
6200
|
+
* @example
|
|
6201
|
+
* ```typescript
|
|
6202
|
+
* .withoutCompaction() // Disable automatic compaction
|
|
6203
|
+
* ```
|
|
6204
|
+
*/
|
|
6205
|
+
withoutCompaction() {
|
|
6206
|
+
this.compactionConfig = { enabled: false };
|
|
6207
|
+
return this;
|
|
6208
|
+
}
|
|
5633
6209
|
/**
|
|
5634
6210
|
* Add a synthetic gadget call to the conversation history.
|
|
5635
6211
|
*
|
|
@@ -5745,7 +6321,8 @@ ${endPrefix}`
|
|
|
5745
6321
|
shouldContinueAfterError: this.shouldContinueAfterError,
|
|
5746
6322
|
defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
|
|
5747
6323
|
gadgetOutputLimit: this.gadgetOutputLimit,
|
|
5748
|
-
gadgetOutputLimitPercent: this.gadgetOutputLimitPercent
|
|
6324
|
+
gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
|
|
6325
|
+
compactionConfig: this.compactionConfig
|
|
5749
6326
|
};
|
|
5750
6327
|
return new Agent(AGENT_INTERNAL_KEY, options);
|
|
5751
6328
|
}
|
|
@@ -5847,7 +6424,8 @@ ${endPrefix}`
|
|
|
5847
6424
|
shouldContinueAfterError: this.shouldContinueAfterError,
|
|
5848
6425
|
defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
|
|
5849
6426
|
gadgetOutputLimit: this.gadgetOutputLimit,
|
|
5850
|
-
gadgetOutputLimitPercent: this.gadgetOutputLimitPercent
|
|
6427
|
+
gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
|
|
6428
|
+
compactionConfig: this.compactionConfig
|
|
5851
6429
|
};
|
|
5852
6430
|
return new Agent(AGENT_INTERNAL_KEY, options);
|
|
5853
6431
|
}
|
|
@@ -5906,7 +6484,7 @@ var import_commander2 = require("commander");
|
|
|
5906
6484
|
// package.json
|
|
5907
6485
|
var package_default = {
|
|
5908
6486
|
name: "llmist",
|
|
5909
|
-
version: "1.
|
|
6487
|
+
version: "1.3.0",
|
|
5910
6488
|
description: "Universal TypeScript LLM client with streaming-first agent framework. Works with any model - no structured outputs or native tool calling required. Implements its own flexible grammar for function calling.",
|
|
5911
6489
|
type: "module",
|
|
5912
6490
|
main: "dist/index.cjs",
|
|
@@ -6053,26 +6631,16 @@ var askUser = createGadget({
|
|
|
6053
6631
|
});
|
|
6054
6632
|
var tellUser = createGadget({
|
|
6055
6633
|
name: "TellUser",
|
|
6056
|
-
description: "Tell the user something important.
|
|
6634
|
+
description: "Tell the user something important.",
|
|
6057
6635
|
schema: import_zod2.z.object({
|
|
6058
6636
|
message: import_zod2.z.string().optional().describe("The message to display to the user in Markdown"),
|
|
6059
|
-
done: import_zod2.z.boolean().default(false).describe("Set to true to end the conversation, false to continue"),
|
|
6060
6637
|
type: import_zod2.z.enum(["info", "success", "warning", "error"]).default("info").describe("Message type: info, success, warning, or error")
|
|
6061
6638
|
}),
|
|
6062
6639
|
examples: [
|
|
6063
6640
|
{
|
|
6064
|
-
comment: "
|
|
6065
|
-
params: {
|
|
6066
|
-
message: "I've completed the refactoring. All tests pass.",
|
|
6067
|
-
done: true,
|
|
6068
|
-
type: "success"
|
|
6069
|
-
}
|
|
6070
|
-
},
|
|
6071
|
-
{
|
|
6072
|
-
comment: "Warn the user about something without ending",
|
|
6641
|
+
comment: "Warn the user about something",
|
|
6073
6642
|
params: {
|
|
6074
6643
|
message: "Found 3 files with potential issues. Continuing analysis...",
|
|
6075
|
-
done: false,
|
|
6076
6644
|
type: "warning"
|
|
6077
6645
|
}
|
|
6078
6646
|
},
|
|
@@ -6080,12 +6648,11 @@ var tellUser = createGadget({
|
|
|
6080
6648
|
comment: "Share detailed analysis with bullet points (use heredoc for multiline)",
|
|
6081
6649
|
params: {
|
|
6082
6650
|
message: "Here's what I found in the codebase:\n\n1. **Main entry point**: `src/index.ts` exports all public APIs\n2. **Core logic**: Located in `src/core/` with 5 modules\n3. **Tests**: Good coverage in `src/__tests__/`\n\nI'll continue exploring the core modules.",
|
|
6083
|
-
done: false,
|
|
6084
6651
|
type: "info"
|
|
6085
6652
|
}
|
|
6086
6653
|
}
|
|
6087
6654
|
],
|
|
6088
|
-
execute: ({ message,
|
|
6655
|
+
execute: ({ message, type }) => {
|
|
6089
6656
|
if (!message || message.trim() === "") {
|
|
6090
6657
|
return "\u26A0\uFE0F TellUser was called without a message. Please provide content in the 'message' field.";
|
|
6091
6658
|
}
|
|
@@ -6095,14 +6662,24 @@ var tellUser = createGadget({
|
|
|
6095
6662
|
warning: "\u26A0\uFE0F ",
|
|
6096
6663
|
error: "\u274C "
|
|
6097
6664
|
};
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6665
|
+
return prefixes[type] + message;
|
|
6666
|
+
}
|
|
6667
|
+
});
|
|
6668
|
+
var finish = createGadget({
|
|
6669
|
+
name: "Finish",
|
|
6670
|
+
description: "Signal that you have completed your task. Call this when your work is done.",
|
|
6671
|
+
schema: import_zod2.z.object({}),
|
|
6672
|
+
examples: [
|
|
6673
|
+
{
|
|
6674
|
+
comment: "Signal task completion",
|
|
6675
|
+
params: {}
|
|
6101
6676
|
}
|
|
6102
|
-
|
|
6677
|
+
],
|
|
6678
|
+
execute: () => {
|
|
6679
|
+
throw new BreakLoopException("Task completed");
|
|
6103
6680
|
}
|
|
6104
6681
|
});
|
|
6105
|
-
var builtinGadgets = [askUser, tellUser];
|
|
6682
|
+
var builtinGadgets = [askUser, tellUser, finish];
|
|
6106
6683
|
|
|
6107
6684
|
// src/cli/gadgets.ts
|
|
6108
6685
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
@@ -6658,6 +7235,17 @@ var StreamProgress = class {
|
|
|
6658
7235
|
} else {
|
|
6659
7236
|
parts.push(iterPart);
|
|
6660
7237
|
}
|
|
7238
|
+
const usagePercent = this.getContextUsagePercent();
|
|
7239
|
+
if (usagePercent !== null) {
|
|
7240
|
+
const formatted = `${Math.round(usagePercent)}%`;
|
|
7241
|
+
if (usagePercent >= 80) {
|
|
7242
|
+
parts.push(import_chalk2.default.red(formatted));
|
|
7243
|
+
} else if (usagePercent >= 50) {
|
|
7244
|
+
parts.push(import_chalk2.default.yellow(formatted));
|
|
7245
|
+
} else {
|
|
7246
|
+
parts.push(import_chalk2.default.green(formatted));
|
|
7247
|
+
}
|
|
7248
|
+
}
|
|
6661
7249
|
if (this.callInputTokens > 0) {
|
|
6662
7250
|
const prefix = this.callInputTokensEstimated ? "~" : "";
|
|
6663
7251
|
parts.push(import_chalk2.default.dim("\u2191") + import_chalk2.default.yellow(` ${prefix}${formatTokens(this.callInputTokens)}`));
|
|
@@ -6693,6 +7281,21 @@ var StreamProgress = class {
|
|
|
6693
7281
|
return 0;
|
|
6694
7282
|
}
|
|
6695
7283
|
}
|
|
7284
|
+
/**
|
|
7285
|
+
* Calculates context window usage percentage.
|
|
7286
|
+
* Returns null if model is unknown or context window unavailable.
|
|
7287
|
+
*/
|
|
7288
|
+
getContextUsagePercent() {
|
|
7289
|
+
if (!this.modelRegistry || !this.model || this.callInputTokens === 0) {
|
|
7290
|
+
return null;
|
|
7291
|
+
}
|
|
7292
|
+
const modelName = this.model.includes(":") ? this.model.split(":")[1] : this.model;
|
|
7293
|
+
const limits = this.modelRegistry.getModelLimits(modelName);
|
|
7294
|
+
if (!limits?.contextWindow) {
|
|
7295
|
+
return null;
|
|
7296
|
+
}
|
|
7297
|
+
return this.callInputTokens / limits.contextWindow * 100;
|
|
7298
|
+
}
|
|
6696
7299
|
renderCumulativeMode(spinner) {
|
|
6697
7300
|
const elapsed = ((Date.now() - this.totalStartTime) / 1e3).toFixed(1);
|
|
6698
7301
|
const parts = [];
|
|
@@ -7320,7 +7923,9 @@ function resolveTemplate(eta, template, context = {}, configPath) {
|
|
|
7320
7923
|
try {
|
|
7321
7924
|
const fullContext = {
|
|
7322
7925
|
...context,
|
|
7323
|
-
env: process.env
|
|
7926
|
+
env: process.env,
|
|
7927
|
+
date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
7928
|
+
// "2025-12-01"
|
|
7324
7929
|
};
|
|
7325
7930
|
return eta.renderString(template, fullContext);
|
|
7326
7931
|
} catch (error) {
|