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/dist/index.cjs CHANGED
@@ -375,10 +375,21 @@ function resolveRulesTemplate(rules, context) {
375
375
  }
376
376
  return [resolved];
377
377
  }
378
- var DEFAULT_PROMPTS;
378
+ function resolveHintTemplate(template, defaultValue, context) {
379
+ const resolved = template ?? defaultValue;
380
+ if (typeof resolved === "function") {
381
+ return resolved(context);
382
+ }
383
+ return resolved.replace(/\{iteration\}/g, String(context.iteration)).replace(/\{maxIterations\}/g, String(context.maxIterations)).replace(/\{remaining\}/g, String(context.remaining));
384
+ }
385
+ var DEFAULT_HINTS, DEFAULT_PROMPTS;
379
386
  var init_prompt_config = __esm({
380
387
  "src/core/prompt-config.ts"() {
381
388
  "use strict";
389
+ DEFAULT_HINTS = {
390
+ parallelGadgetsHint: "Tip: You can call multiple gadgets in a single response for efficiency.",
391
+ iterationProgressHint: "[Iteration {iteration}/{maxIterations}] Plan your actions accordingly."
392
+ };
382
393
  DEFAULT_PROMPTS = {
383
394
  mainInstruction: [
384
395
  "\u26A0\uFE0F CRITICAL: RESPOND ONLY WITH GADGET INVOCATIONS",
@@ -1129,6 +1140,417 @@ var init_output_viewer = __esm({
1129
1140
  }
1130
1141
  });
1131
1142
 
1143
+ // src/agent/compaction/config.ts
1144
+ function resolveCompactionConfig(config = {}) {
1145
+ const trigger = config.triggerThresholdPercent ?? DEFAULT_COMPACTION_CONFIG.triggerThresholdPercent;
1146
+ const target = config.targetPercent ?? DEFAULT_COMPACTION_CONFIG.targetPercent;
1147
+ if (target >= trigger) {
1148
+ console.warn(
1149
+ `[llmist/compaction] targetPercent (${target}) should be less than triggerThresholdPercent (${trigger}) to be effective.`
1150
+ );
1151
+ }
1152
+ const strategy = config.strategy ?? DEFAULT_COMPACTION_CONFIG.strategy;
1153
+ const strategyName = typeof strategy === "object" && "name" in strategy ? strategy.name : strategy;
1154
+ return {
1155
+ enabled: config.enabled ?? DEFAULT_COMPACTION_CONFIG.enabled,
1156
+ strategy: strategyName,
1157
+ triggerThresholdPercent: trigger,
1158
+ targetPercent: target,
1159
+ preserveRecentTurns: config.preserveRecentTurns ?? DEFAULT_COMPACTION_CONFIG.preserveRecentTurns,
1160
+ summarizationModel: config.summarizationModel,
1161
+ summarizationPrompt: config.summarizationPrompt ?? DEFAULT_SUMMARIZATION_PROMPT,
1162
+ onCompaction: config.onCompaction
1163
+ };
1164
+ }
1165
+ var DEFAULT_COMPACTION_CONFIG, DEFAULT_SUMMARIZATION_PROMPT;
1166
+ var init_config = __esm({
1167
+ "src/agent/compaction/config.ts"() {
1168
+ "use strict";
1169
+ DEFAULT_COMPACTION_CONFIG = {
1170
+ enabled: true,
1171
+ strategy: "hybrid",
1172
+ triggerThresholdPercent: 80,
1173
+ targetPercent: 50,
1174
+ preserveRecentTurns: 5
1175
+ };
1176
+ DEFAULT_SUMMARIZATION_PROMPT = `Summarize this conversation history concisely, preserving:
1177
+ 1. Key decisions made and their rationale
1178
+ 2. Important facts and data discovered
1179
+ 3. Errors encountered and how they were resolved
1180
+ 4. Current task context and goals
1181
+
1182
+ Format as a brief narrative paragraph, not bullet points.
1183
+ Previous conversation:`;
1184
+ }
1185
+ });
1186
+
1187
+ // src/agent/compaction/strategy.ts
1188
+ function groupIntoTurns(messages) {
1189
+ const turns = [];
1190
+ let currentTurn = [];
1191
+ for (const msg of messages) {
1192
+ if (msg.role === "user" && currentTurn.length > 0) {
1193
+ turns.push({
1194
+ messages: currentTurn,
1195
+ tokenEstimate: estimateTurnTokens(currentTurn)
1196
+ });
1197
+ currentTurn = [msg];
1198
+ } else {
1199
+ currentTurn.push(msg);
1200
+ }
1201
+ }
1202
+ if (currentTurn.length > 0) {
1203
+ turns.push({
1204
+ messages: currentTurn,
1205
+ tokenEstimate: estimateTurnTokens(currentTurn)
1206
+ });
1207
+ }
1208
+ return turns;
1209
+ }
1210
+ function estimateTurnTokens(messages) {
1211
+ return Math.ceil(messages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0) / 4);
1212
+ }
1213
+ function flattenTurns(turns) {
1214
+ return turns.flatMap((turn) => turn.messages);
1215
+ }
1216
+ var init_strategy = __esm({
1217
+ "src/agent/compaction/strategy.ts"() {
1218
+ "use strict";
1219
+ }
1220
+ });
1221
+
1222
+ // src/agent/compaction/strategies/sliding-window.ts
1223
+ var TRUNCATION_MARKER_TEMPLATE, SlidingWindowStrategy;
1224
+ var init_sliding_window = __esm({
1225
+ "src/agent/compaction/strategies/sliding-window.ts"() {
1226
+ "use strict";
1227
+ init_strategy();
1228
+ TRUNCATION_MARKER_TEMPLATE = "[Previous conversation truncated. Removed {count} turn(s) to fit context window.]";
1229
+ SlidingWindowStrategy = class {
1230
+ name = "sliding-window";
1231
+ async compact(messages, config, context) {
1232
+ const turns = groupIntoTurns(messages);
1233
+ const preserveCount = Math.min(config.preserveRecentTurns, turns.length);
1234
+ if (turns.length <= preserveCount) {
1235
+ return {
1236
+ messages,
1237
+ strategyName: this.name,
1238
+ metadata: {
1239
+ originalCount: messages.length,
1240
+ compactedCount: messages.length,
1241
+ tokensBefore: context.currentTokens,
1242
+ tokensAfter: context.currentTokens
1243
+ }
1244
+ };
1245
+ }
1246
+ const turnsToKeep = turns.slice(-preserveCount);
1247
+ const turnsRemoved = turns.length - preserveCount;
1248
+ const truncationMarker = {
1249
+ role: "user",
1250
+ content: TRUNCATION_MARKER_TEMPLATE.replace("{count}", turnsRemoved.toString())
1251
+ };
1252
+ const compactedMessages = [truncationMarker, ...flattenTurns(turnsToKeep)];
1253
+ const tokensAfter = Math.ceil(
1254
+ compactedMessages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0) / 4
1255
+ );
1256
+ return {
1257
+ messages: compactedMessages,
1258
+ strategyName: this.name,
1259
+ metadata: {
1260
+ originalCount: messages.length,
1261
+ compactedCount: compactedMessages.length,
1262
+ tokensBefore: context.currentTokens,
1263
+ tokensAfter
1264
+ }
1265
+ };
1266
+ }
1267
+ };
1268
+ }
1269
+ });
1270
+
1271
+ // src/agent/compaction/strategies/summarization.ts
1272
+ var SummarizationStrategy;
1273
+ var init_summarization = __esm({
1274
+ "src/agent/compaction/strategies/summarization.ts"() {
1275
+ "use strict";
1276
+ init_strategy();
1277
+ SummarizationStrategy = class {
1278
+ name = "summarization";
1279
+ async compact(messages, config, context) {
1280
+ const turns = groupIntoTurns(messages);
1281
+ const preserveCount = Math.min(config.preserveRecentTurns, turns.length);
1282
+ if (turns.length <= preserveCount) {
1283
+ return {
1284
+ messages,
1285
+ strategyName: this.name,
1286
+ metadata: {
1287
+ originalCount: messages.length,
1288
+ compactedCount: messages.length,
1289
+ tokensBefore: context.currentTokens,
1290
+ tokensAfter: context.currentTokens
1291
+ }
1292
+ };
1293
+ }
1294
+ const turnsToSummarize = turns.slice(0, -preserveCount);
1295
+ const turnsToKeep = turns.slice(-preserveCount);
1296
+ const conversationToSummarize = this.formatTurnsForSummary(flattenTurns(turnsToSummarize));
1297
+ const summary = await this.generateSummary(conversationToSummarize, config, context);
1298
+ const summaryMessage = {
1299
+ role: "user",
1300
+ content: `[Previous conversation summary]
1301
+ ${summary}
1302
+ [End of summary - conversation continues below]`
1303
+ };
1304
+ const compactedMessages = [summaryMessage, ...flattenTurns(turnsToKeep)];
1305
+ const tokensAfter = Math.ceil(
1306
+ compactedMessages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0) / 4
1307
+ );
1308
+ return {
1309
+ messages: compactedMessages,
1310
+ summary,
1311
+ strategyName: this.name,
1312
+ metadata: {
1313
+ originalCount: messages.length,
1314
+ compactedCount: compactedMessages.length,
1315
+ tokensBefore: context.currentTokens,
1316
+ tokensAfter
1317
+ }
1318
+ };
1319
+ }
1320
+ /**
1321
+ * Formats messages into a readable conversation format for summarization.
1322
+ */
1323
+ formatTurnsForSummary(messages) {
1324
+ return messages.map((msg) => {
1325
+ const role = msg.role.charAt(0).toUpperCase() + msg.role.slice(1);
1326
+ return `${role}: ${msg.content}`;
1327
+ }).join("\n\n");
1328
+ }
1329
+ /**
1330
+ * Generates a summary using the configured LLM.
1331
+ */
1332
+ async generateSummary(conversation, config, context) {
1333
+ const model = config.summarizationModel ?? context.model;
1334
+ const prompt = `${config.summarizationPrompt}
1335
+
1336
+ ${conversation}`;
1337
+ const response = await context.client.complete(prompt, {
1338
+ model,
1339
+ temperature: 0.3
1340
+ // Low temperature for factual summarization
1341
+ });
1342
+ return response.trim();
1343
+ }
1344
+ };
1345
+ }
1346
+ });
1347
+
1348
+ // src/agent/compaction/strategies/hybrid.ts
1349
+ var MIN_TURNS_FOR_SUMMARIZATION, HybridStrategy;
1350
+ var init_hybrid = __esm({
1351
+ "src/agent/compaction/strategies/hybrid.ts"() {
1352
+ "use strict";
1353
+ init_strategy();
1354
+ init_sliding_window();
1355
+ init_summarization();
1356
+ MIN_TURNS_FOR_SUMMARIZATION = 3;
1357
+ HybridStrategy = class {
1358
+ name = "hybrid";
1359
+ slidingWindow = new SlidingWindowStrategy();
1360
+ summarization = new SummarizationStrategy();
1361
+ async compact(messages, config, context) {
1362
+ const turns = groupIntoTurns(messages);
1363
+ const preserveCount = Math.min(config.preserveRecentTurns, turns.length);
1364
+ if (turns.length <= preserveCount) {
1365
+ return {
1366
+ messages,
1367
+ strategyName: this.name,
1368
+ metadata: {
1369
+ originalCount: messages.length,
1370
+ compactedCount: messages.length,
1371
+ tokensBefore: context.currentTokens,
1372
+ tokensAfter: context.currentTokens
1373
+ }
1374
+ };
1375
+ }
1376
+ const turnsToSummarize = turns.length - preserveCount;
1377
+ if (turnsToSummarize < MIN_TURNS_FOR_SUMMARIZATION) {
1378
+ return this.slidingWindow.compact(messages, config, context);
1379
+ }
1380
+ return this.summarization.compact(messages, config, context);
1381
+ }
1382
+ };
1383
+ }
1384
+ });
1385
+
1386
+ // src/agent/compaction/strategies/index.ts
1387
+ var init_strategies = __esm({
1388
+ "src/agent/compaction/strategies/index.ts"() {
1389
+ "use strict";
1390
+ init_sliding_window();
1391
+ init_summarization();
1392
+ init_hybrid();
1393
+ }
1394
+ });
1395
+
1396
+ // src/agent/compaction/manager.ts
1397
+ function createStrategy(name) {
1398
+ switch (name) {
1399
+ case "sliding-window":
1400
+ return new SlidingWindowStrategy();
1401
+ case "summarization":
1402
+ return new SummarizationStrategy();
1403
+ case "hybrid":
1404
+ return new HybridStrategy();
1405
+ default:
1406
+ throw new Error(`Unknown compaction strategy: ${name}`);
1407
+ }
1408
+ }
1409
+ var CompactionManager;
1410
+ var init_manager = __esm({
1411
+ "src/agent/compaction/manager.ts"() {
1412
+ "use strict";
1413
+ init_config();
1414
+ init_strategies();
1415
+ CompactionManager = class {
1416
+ client;
1417
+ model;
1418
+ config;
1419
+ strategy;
1420
+ modelLimits;
1421
+ // Statistics
1422
+ totalCompactions = 0;
1423
+ totalTokensSaved = 0;
1424
+ lastTokenCount = 0;
1425
+ constructor(client, model, config = {}) {
1426
+ this.client = client;
1427
+ this.model = model;
1428
+ this.config = resolveCompactionConfig(config);
1429
+ if (typeof config.strategy === "object" && "compact" in config.strategy) {
1430
+ this.strategy = config.strategy;
1431
+ } else {
1432
+ this.strategy = createStrategy(this.config.strategy);
1433
+ }
1434
+ }
1435
+ /**
1436
+ * Check if compaction is needed and perform it if so.
1437
+ *
1438
+ * @param conversation - The conversation manager to compact
1439
+ * @param iteration - Current agent iteration (for event metadata)
1440
+ * @returns CompactionEvent if compaction was performed, null otherwise
1441
+ */
1442
+ async checkAndCompact(conversation, iteration) {
1443
+ if (!this.config.enabled) {
1444
+ return null;
1445
+ }
1446
+ if (!this.modelLimits) {
1447
+ this.modelLimits = this.client.modelRegistry.getModelLimits(this.model);
1448
+ if (!this.modelLimits) {
1449
+ return null;
1450
+ }
1451
+ }
1452
+ if (!this.client.countTokens) {
1453
+ return null;
1454
+ }
1455
+ const messages = conversation.getMessages();
1456
+ const currentTokens = await this.client.countTokens(this.model, messages);
1457
+ this.lastTokenCount = currentTokens;
1458
+ const usagePercent = currentTokens / this.modelLimits.contextWindow * 100;
1459
+ if (usagePercent < this.config.triggerThresholdPercent) {
1460
+ return null;
1461
+ }
1462
+ const historyMessages = conversation.getHistoryMessages();
1463
+ const baseMessages = conversation.getBaseMessages();
1464
+ const historyTokens = await this.client.countTokens(this.model, historyMessages);
1465
+ const baseTokens = await this.client.countTokens(this.model, baseMessages);
1466
+ return this.compact(conversation, iteration, {
1467
+ historyMessages,
1468
+ baseMessages,
1469
+ historyTokens,
1470
+ baseTokens,
1471
+ currentTokens: historyTokens + baseTokens
1472
+ });
1473
+ }
1474
+ /**
1475
+ * Force compaction regardless of threshold.
1476
+ *
1477
+ * @param conversation - The conversation manager to compact
1478
+ * @param iteration - Current agent iteration (for event metadata). Use -1 for manual compaction.
1479
+ * @param precomputed - Optional pre-computed token counts (passed from checkAndCompact for efficiency)
1480
+ * @returns CompactionEvent with compaction details
1481
+ */
1482
+ async compact(conversation, iteration, precomputed) {
1483
+ if (!this.modelLimits) {
1484
+ this.modelLimits = this.client.modelRegistry.getModelLimits(this.model);
1485
+ if (!this.modelLimits) {
1486
+ return null;
1487
+ }
1488
+ }
1489
+ const historyMessages = precomputed?.historyMessages ?? conversation.getHistoryMessages();
1490
+ const baseMessages = precomputed?.baseMessages ?? conversation.getBaseMessages();
1491
+ const historyTokens = precomputed?.historyTokens ?? await this.client.countTokens(this.model, historyMessages);
1492
+ const baseTokens = precomputed?.baseTokens ?? await this.client.countTokens(this.model, baseMessages);
1493
+ const currentTokens = precomputed?.currentTokens ?? historyTokens + baseTokens;
1494
+ const targetTotalTokens = Math.floor(
1495
+ this.modelLimits.contextWindow * this.config.targetPercent / 100
1496
+ );
1497
+ const targetHistoryTokens = Math.max(0, targetTotalTokens - baseTokens);
1498
+ const result = await this.strategy.compact(historyMessages, this.config, {
1499
+ currentTokens: historyTokens,
1500
+ targetTokens: targetHistoryTokens,
1501
+ modelLimits: this.modelLimits,
1502
+ client: this.client,
1503
+ model: this.config.summarizationModel ?? this.model
1504
+ });
1505
+ conversation.replaceHistory(result.messages);
1506
+ const afterTokens = await this.client.countTokens(this.model, conversation.getMessages());
1507
+ const tokensSaved = currentTokens - afterTokens;
1508
+ this.totalCompactions++;
1509
+ this.totalTokensSaved += tokensSaved;
1510
+ this.lastTokenCount = afterTokens;
1511
+ const event = {
1512
+ strategy: result.strategyName,
1513
+ tokensBefore: currentTokens,
1514
+ tokensAfter: afterTokens,
1515
+ messagesBefore: historyMessages.length + baseMessages.length,
1516
+ messagesAfter: result.messages.length + baseMessages.length,
1517
+ summary: result.summary,
1518
+ iteration
1519
+ };
1520
+ if (this.config.onCompaction) {
1521
+ try {
1522
+ this.config.onCompaction(event);
1523
+ } catch (err) {
1524
+ console.warn("[llmist/compaction] onCompaction callback error:", err);
1525
+ }
1526
+ }
1527
+ return event;
1528
+ }
1529
+ /**
1530
+ * Get compaction statistics.
1531
+ */
1532
+ getStats() {
1533
+ const contextWindow = this.modelLimits?.contextWindow ?? 0;
1534
+ return {
1535
+ totalCompactions: this.totalCompactions,
1536
+ totalTokensSaved: this.totalTokensSaved,
1537
+ currentUsage: {
1538
+ tokens: this.lastTokenCount,
1539
+ percent: contextWindow > 0 ? this.lastTokenCount / contextWindow * 100 : 0
1540
+ },
1541
+ contextWindow
1542
+ };
1543
+ }
1544
+ /**
1545
+ * Check if compaction is enabled.
1546
+ */
1547
+ isEnabled() {
1548
+ return this.config.enabled;
1549
+ }
1550
+ };
1551
+ }
1552
+ });
1553
+
1132
1554
  // src/agent/gadget-output-store.ts
1133
1555
  var import_node_crypto, GadgetOutputStore;
1134
1556
  var init_gadget_output_store = __esm({
@@ -1231,10 +1653,16 @@ var init_conversation_manager = __esm({
1231
1653
  baseMessages;
1232
1654
  initialMessages;
1233
1655
  historyBuilder;
1656
+ startPrefix;
1657
+ endPrefix;
1658
+ argPrefix;
1234
1659
  constructor(baseMessages, initialMessages, options = {}) {
1235
1660
  this.baseMessages = baseMessages;
1236
1661
  this.initialMessages = initialMessages;
1237
1662
  this.historyBuilder = new LLMMessageBuilder();
1663
+ this.startPrefix = options.startPrefix;
1664
+ this.endPrefix = options.endPrefix;
1665
+ this.argPrefix = options.argPrefix;
1238
1666
  if (options.startPrefix && options.endPrefix) {
1239
1667
  this.historyBuilder.withPrefixes(options.startPrefix, options.endPrefix, options.argPrefix);
1240
1668
  }
@@ -1251,6 +1679,25 @@ var init_conversation_manager = __esm({
1251
1679
  getMessages() {
1252
1680
  return [...this.baseMessages, ...this.initialMessages, ...this.historyBuilder.build()];
1253
1681
  }
1682
+ getHistoryMessages() {
1683
+ return this.historyBuilder.build();
1684
+ }
1685
+ getBaseMessages() {
1686
+ return [...this.baseMessages, ...this.initialMessages];
1687
+ }
1688
+ replaceHistory(newHistory) {
1689
+ this.historyBuilder = new LLMMessageBuilder();
1690
+ if (this.startPrefix && this.endPrefix) {
1691
+ this.historyBuilder.withPrefixes(this.startPrefix, this.endPrefix, this.argPrefix);
1692
+ }
1693
+ for (const msg of newHistory) {
1694
+ if (msg.role === "user") {
1695
+ this.historyBuilder.addUser(msg.content);
1696
+ } else if (msg.role === "assistant") {
1697
+ this.historyBuilder.addAssistant(msg.content);
1698
+ }
1699
+ }
1700
+ }
1254
1701
  };
1255
1702
  }
1256
1703
  });
@@ -2827,6 +3274,7 @@ var init_agent = __esm({
2827
3274
  init_model_shortcuts();
2828
3275
  init_output_viewer();
2829
3276
  init_logger();
3277
+ init_manager();
2830
3278
  init_gadget_output_store();
2831
3279
  init_agent_internal_key();
2832
3280
  init_conversation_manager();
@@ -2857,6 +3305,8 @@ var init_agent = __esm({
2857
3305
  outputStore;
2858
3306
  outputLimitEnabled;
2859
3307
  outputLimitCharLimit;
3308
+ // Context compaction
3309
+ compactionManager;
2860
3310
  /**
2861
3311
  * Creates a new Agent instance.
2862
3312
  * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
@@ -2916,6 +3366,14 @@ var init_agent = __esm({
2916
3366
  if (options.userPrompt) {
2917
3367
  this.conversation.addUserMessage(options.userPrompt);
2918
3368
  }
3369
+ const compactionEnabled = options.compactionConfig?.enabled ?? true;
3370
+ if (compactionEnabled) {
3371
+ this.compactionManager = new CompactionManager(
3372
+ this.client,
3373
+ this.model,
3374
+ options.compactionConfig
3375
+ );
3376
+ }
2919
3377
  }
2920
3378
  /**
2921
3379
  * Get the gadget registry for this agent.
@@ -2938,6 +3396,53 @@ var init_agent = __esm({
2938
3396
  getRegistry() {
2939
3397
  return this.registry;
2940
3398
  }
3399
+ /**
3400
+ * Manually trigger context compaction.
3401
+ *
3402
+ * Forces compaction regardless of threshold. Useful for:
3403
+ * - Pre-emptive context management before expected long operations
3404
+ * - Testing compaction behavior
3405
+ *
3406
+ * @returns CompactionEvent if compaction was performed, null if not configured or no history
3407
+ *
3408
+ * @example
3409
+ * ```typescript
3410
+ * const agent = await LLMist.createAgent()
3411
+ * .withModel('sonnet')
3412
+ * .withCompaction()
3413
+ * .ask('...');
3414
+ *
3415
+ * // Manually compact before a long operation
3416
+ * const event = await agent.compact();
3417
+ * if (event) {
3418
+ * console.log(`Saved ${event.tokensBefore - event.tokensAfter} tokens`);
3419
+ * }
3420
+ * ```
3421
+ */
3422
+ async compact() {
3423
+ if (!this.compactionManager) {
3424
+ return null;
3425
+ }
3426
+ return this.compactionManager.compact(this.conversation, -1);
3427
+ }
3428
+ /**
3429
+ * Get compaction statistics.
3430
+ *
3431
+ * @returns CompactionStats if compaction is enabled, null otherwise
3432
+ *
3433
+ * @example
3434
+ * ```typescript
3435
+ * const stats = agent.getCompactionStats();
3436
+ * if (stats) {
3437
+ * console.log(`Total compactions: ${stats.totalCompactions}`);
3438
+ * console.log(`Tokens saved: ${stats.totalTokensSaved}`);
3439
+ * console.log(`Current usage: ${stats.currentUsage.percent.toFixed(1)}%`);
3440
+ * }
3441
+ * ```
3442
+ */
3443
+ getCompactionStats() {
3444
+ return this.compactionManager?.getStats() ?? null;
3445
+ }
2941
3446
  /**
2942
3447
  * Run the agent loop.
2943
3448
  * Clean, simple orchestration - all complexity is in StreamProcessor.
@@ -2958,6 +3463,30 @@ var init_agent = __esm({
2958
3463
  while (currentIteration < this.maxIterations) {
2959
3464
  this.logger.debug("Starting iteration", { iteration: currentIteration });
2960
3465
  try {
3466
+ if (this.compactionManager) {
3467
+ const compactionEvent = await this.compactionManager.checkAndCompact(
3468
+ this.conversation,
3469
+ currentIteration
3470
+ );
3471
+ if (compactionEvent) {
3472
+ this.logger.info("Context compacted", {
3473
+ strategy: compactionEvent.strategy,
3474
+ tokensBefore: compactionEvent.tokensBefore,
3475
+ tokensAfter: compactionEvent.tokensAfter
3476
+ });
3477
+ yield { type: "compaction", event: compactionEvent };
3478
+ await this.safeObserve(async () => {
3479
+ if (this.hooks.observers?.onCompaction) {
3480
+ await this.hooks.observers.onCompaction({
3481
+ iteration: currentIteration,
3482
+ event: compactionEvent,
3483
+ stats: this.compactionManager.getStats(),
3484
+ logger: this.logger
3485
+ });
3486
+ }
3487
+ });
3488
+ }
3489
+ }
2961
3490
  let llmOptions = {
2962
3491
  model: this.model,
2963
3492
  messages: this.conversation.getMessages(),
@@ -2977,6 +3506,7 @@ var init_agent = __esm({
2977
3506
  if (this.hooks.controllers?.beforeLLMCall) {
2978
3507
  const context = {
2979
3508
  iteration: currentIteration,
3509
+ maxIterations: this.maxIterations,
2980
3510
  options: llmOptions,
2981
3511
  logger: this.logger
2982
3512
  };
@@ -3041,12 +3571,17 @@ var init_agent = __esm({
3041
3571
  });
3042
3572
  let finalMessage = result.finalMessage;
3043
3573
  if (this.hooks.controllers?.afterLLMCall) {
3574
+ const gadgetCallCount = result.outputs.filter(
3575
+ (output) => output.type === "gadget_result"
3576
+ ).length;
3044
3577
  const context = {
3045
3578
  iteration: currentIteration,
3579
+ maxIterations: this.maxIterations,
3046
3580
  options: llmOptions,
3047
3581
  finishReason: result.finishReason,
3048
3582
  usage: result.usage,
3049
3583
  finalMessage: result.finalMessage,
3584
+ gadgetCallCount,
3050
3585
  logger: this.logger
3051
3586
  };
3052
3587
  const action = await this.hooks.controllers.afterLLMCall(context);
@@ -5281,6 +5816,7 @@ var init_builder = __esm({
5281
5816
  defaultGadgetTimeoutMs;
5282
5817
  gadgetOutputLimit;
5283
5818
  gadgetOutputLimitPercent;
5819
+ compactionConfig;
5284
5820
  constructor(client) {
5285
5821
  this.client = client;
5286
5822
  }
@@ -5676,6 +6212,57 @@ var init_builder = __esm({
5676
6212
  this.gadgetOutputLimitPercent = percent;
5677
6213
  return this;
5678
6214
  }
6215
+ /**
6216
+ * Configure context compaction.
6217
+ *
6218
+ * Context compaction automatically manages conversation history to prevent
6219
+ * context window overflow in long-running agent conversations.
6220
+ *
6221
+ * @param config - Compaction configuration options
6222
+ * @returns This builder for chaining
6223
+ *
6224
+ * @example
6225
+ * ```typescript
6226
+ * // Custom thresholds
6227
+ * .withCompaction({
6228
+ * triggerThresholdPercent: 70,
6229
+ * targetPercent: 40,
6230
+ * preserveRecentTurns: 10,
6231
+ * })
6232
+ *
6233
+ * // Different strategy
6234
+ * .withCompaction({
6235
+ * strategy: 'sliding-window',
6236
+ * })
6237
+ *
6238
+ * // With callback
6239
+ * .withCompaction({
6240
+ * onCompaction: (event) => {
6241
+ * console.log(`Saved ${event.tokensBefore - event.tokensAfter} tokens`);
6242
+ * }
6243
+ * })
6244
+ * ```
6245
+ */
6246
+ withCompaction(config) {
6247
+ this.compactionConfig = { ...config, enabled: config.enabled ?? true };
6248
+ return this;
6249
+ }
6250
+ /**
6251
+ * Disable context compaction.
6252
+ *
6253
+ * By default, compaction is enabled. Use this method to explicitly disable it.
6254
+ *
6255
+ * @returns This builder for chaining
6256
+ *
6257
+ * @example
6258
+ * ```typescript
6259
+ * .withoutCompaction() // Disable automatic compaction
6260
+ * ```
6261
+ */
6262
+ withoutCompaction() {
6263
+ this.compactionConfig = { enabled: false };
6264
+ return this;
6265
+ }
5679
6266
  /**
5680
6267
  * Add a synthetic gadget call to the conversation history.
5681
6268
  *
@@ -5791,7 +6378,8 @@ ${endPrefix}`
5791
6378
  shouldContinueAfterError: this.shouldContinueAfterError,
5792
6379
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
5793
6380
  gadgetOutputLimit: this.gadgetOutputLimit,
5794
- gadgetOutputLimitPercent: this.gadgetOutputLimitPercent
6381
+ gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
6382
+ compactionConfig: this.compactionConfig
5795
6383
  };
5796
6384
  return new Agent(AGENT_INTERNAL_KEY, options);
5797
6385
  }
@@ -5893,7 +6481,8 @@ ${endPrefix}`
5893
6481
  shouldContinueAfterError: this.shouldContinueAfterError,
5894
6482
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
5895
6483
  gadgetOutputLimit: this.gadgetOutputLimit,
5896
- gadgetOutputLimitPercent: this.gadgetOutputLimitPercent
6484
+ gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
6485
+ compactionConfig: this.compactionConfig
5897
6486
  };
5898
6487
  return new Agent(AGENT_INTERNAL_KEY, options);
5899
6488
  }
@@ -5908,8 +6497,12 @@ __export(index_exports, {
5908
6497
  AnthropicMessagesProvider: () => AnthropicMessagesProvider,
5909
6498
  BaseGadget: () => BaseGadget,
5910
6499
  BreakLoopException: () => BreakLoopException,
6500
+ CompactionManager: () => CompactionManager,
5911
6501
  ConversationManager: () => ConversationManager,
6502
+ DEFAULT_COMPACTION_CONFIG: () => DEFAULT_COMPACTION_CONFIG,
6503
+ DEFAULT_HINTS: () => DEFAULT_HINTS,
5912
6504
  DEFAULT_PROMPTS: () => DEFAULT_PROMPTS,
6505
+ DEFAULT_SUMMARIZATION_PROMPT: () => DEFAULT_SUMMARIZATION_PROMPT,
5913
6506
  Gadget: () => Gadget,
5914
6507
  GadgetExecutor: () => GadgetExecutor,
5915
6508
  GadgetOutputStore: () => GadgetOutputStore,
@@ -5917,6 +6510,7 @@ __export(index_exports, {
5917
6510
  GeminiGenerativeProvider: () => GeminiGenerativeProvider,
5918
6511
  HookPresets: () => HookPresets,
5919
6512
  HumanInputException: () => HumanInputException,
6513
+ HybridStrategy: () => HybridStrategy,
5920
6514
  LLMMessageBuilder: () => LLMMessageBuilder,
5921
6515
  LLMist: () => LLMist,
5922
6516
  MODEL_ALIASES: () => MODEL_ALIASES,
@@ -5926,8 +6520,10 @@ __export(index_exports, {
5926
6520
  ModelIdentifierParser: () => ModelIdentifierParser,
5927
6521
  ModelRegistry: () => ModelRegistry,
5928
6522
  OpenAIChatProvider: () => OpenAIChatProvider,
6523
+ SlidingWindowStrategy: () => SlidingWindowStrategy,
5929
6524
  StreamParser: () => StreamParser,
5930
6525
  StreamProcessor: () => StreamProcessor,
6526
+ SummarizationStrategy: () => SummarizationStrategy,
5931
6527
  collectEvents: () => collectEvents,
5932
6528
  collectText: () => collectText,
5933
6529
  complete: () => complete,
@@ -5935,6 +6531,7 @@ __export(index_exports, {
5935
6531
  createGadget: () => createGadget,
5936
6532
  createGadgetOutputViewer: () => createGadgetOutputViewer,
5937
6533
  createGeminiProviderFromEnv: () => createGeminiProviderFromEnv,
6534
+ createHints: () => createHints,
5938
6535
  createLogger: () => createLogger,
5939
6536
  createMockAdapter: () => createMockAdapter,
5940
6537
  createMockClient: () => createMockClient,
@@ -5947,7 +6544,10 @@ __export(index_exports, {
5947
6544
  getModelId: () => getModelId,
5948
6545
  getProvider: () => getProvider,
5949
6546
  hasProviderPrefix: () => hasProviderPrefix,
6547
+ iterationProgressHint: () => iterationProgressHint,
5950
6548
  mockLLM: () => mockLLM,
6549
+ parallelGadgetHint: () => parallelGadgetHint,
6550
+ resolveHintTemplate: () => resolveHintTemplate,
5951
6551
  resolveModel: () => resolveModel,
5952
6552
  resolvePromptTemplate: () => resolvePromptTemplate,
5953
6553
  resolveRulesTemplate: () => resolveRulesTemplate,
@@ -6462,6 +7062,51 @@ var HookPresets = class _HookPresets {
6462
7062
  }
6463
7063
  };
6464
7064
  }
7065
+ /**
7066
+ * Tracks context compaction events.
7067
+ *
7068
+ * **Output:**
7069
+ * - Compaction events with 🗜️ emoji
7070
+ * - Strategy name, tokens before/after, and savings
7071
+ * - Cumulative statistics
7072
+ *
7073
+ * **Use cases:**
7074
+ * - Monitoring long-running conversations
7075
+ * - Understanding when and how compaction occurs
7076
+ * - Debugging context management issues
7077
+ *
7078
+ * **Performance:** Minimal overhead. Simple console output.
7079
+ *
7080
+ * @returns Hook configuration that can be passed to .withHooks()
7081
+ *
7082
+ * @example
7083
+ * ```typescript
7084
+ * await LLMist.createAgent()
7085
+ * .withHooks(HookPresets.compactionTracking())
7086
+ * .ask("Your prompt");
7087
+ * ```
7088
+ */
7089
+ static compactionTracking() {
7090
+ return {
7091
+ observers: {
7092
+ onCompaction: async (ctx) => {
7093
+ const saved = ctx.event.tokensBefore - ctx.event.tokensAfter;
7094
+ const percent = (saved / ctx.event.tokensBefore * 100).toFixed(1);
7095
+ console.log(
7096
+ `\u{1F5DC}\uFE0F Compaction (${ctx.event.strategy}): ${ctx.event.tokensBefore} \u2192 ${ctx.event.tokensAfter} tokens (saved ${saved}, ${percent}%)`
7097
+ );
7098
+ console.log(
7099
+ ` Messages: ${ctx.event.messagesBefore} \u2192 ${ctx.event.messagesAfter}`
7100
+ );
7101
+ if (ctx.stats.totalCompactions > 1) {
7102
+ console.log(
7103
+ ` Cumulative: ${ctx.stats.totalCompactions} compactions, ${ctx.stats.totalTokensSaved} tokens saved`
7104
+ );
7105
+ }
7106
+ }
7107
+ }
7108
+ };
7109
+ }
6465
7110
  /**
6466
7111
  * Returns empty hook configuration for clean output without any logging.
6467
7112
  *
@@ -6692,6 +7337,113 @@ init_conversation_manager();
6692
7337
  init_stream_processor();
6693
7338
  init_gadget_output_store();
6694
7339
 
7340
+ // src/agent/compaction/index.ts
7341
+ init_config();
7342
+ init_strategy();
7343
+ init_strategies();
7344
+ init_manager();
7345
+
7346
+ // src/agent/hints.ts
7347
+ init_prompt_config();
7348
+ function iterationProgressHint(options) {
7349
+ const { timing = "always", showUrgency = true, template } = options ?? {};
7350
+ return {
7351
+ controllers: {
7352
+ beforeLLMCall: async (ctx) => {
7353
+ const iteration = ctx.iteration + 1;
7354
+ const maxIterations = ctx.maxIterations;
7355
+ const progress = iteration / maxIterations;
7356
+ if (timing === "late" && progress < 0.5) {
7357
+ return { action: "proceed" };
7358
+ }
7359
+ if (timing === "urgent" && progress < 0.8) {
7360
+ return { action: "proceed" };
7361
+ }
7362
+ const remaining = maxIterations - iteration;
7363
+ const hintContext = {
7364
+ iteration,
7365
+ maxIterations,
7366
+ remaining
7367
+ };
7368
+ let hint = resolveHintTemplate(
7369
+ template,
7370
+ DEFAULT_HINTS.iterationProgressHint,
7371
+ hintContext
7372
+ );
7373
+ if (showUrgency && progress >= 0.8) {
7374
+ hint += " \u26A0\uFE0F Running low on iterations - focus on completing the task.";
7375
+ }
7376
+ const messages = [...ctx.options.messages];
7377
+ let lastUserIndex = -1;
7378
+ for (let i = messages.length - 1; i >= 0; i--) {
7379
+ if (messages[i].role === "user") {
7380
+ lastUserIndex = i;
7381
+ break;
7382
+ }
7383
+ }
7384
+ if (lastUserIndex >= 0) {
7385
+ messages.splice(lastUserIndex + 1, 0, {
7386
+ role: "user",
7387
+ content: `[System Hint] ${hint}`
7388
+ });
7389
+ } else {
7390
+ messages.push({
7391
+ role: "user",
7392
+ content: `[System Hint] ${hint}`
7393
+ });
7394
+ }
7395
+ return {
7396
+ action: "proceed",
7397
+ modifiedOptions: { messages }
7398
+ };
7399
+ }
7400
+ }
7401
+ };
7402
+ }
7403
+ function parallelGadgetHint(options) {
7404
+ const {
7405
+ minGadgetsForEfficiency = 2,
7406
+ message = DEFAULT_HINTS.parallelGadgetsHint,
7407
+ enabled = true
7408
+ } = options ?? {};
7409
+ return {
7410
+ controllers: {
7411
+ afterLLMCall: async (ctx) => {
7412
+ if (!enabled) {
7413
+ return { action: "continue" };
7414
+ }
7415
+ if (ctx.gadgetCallCount > 0 && ctx.gadgetCallCount < minGadgetsForEfficiency) {
7416
+ return {
7417
+ action: "append_messages",
7418
+ messages: [
7419
+ {
7420
+ role: "user",
7421
+ content: `[System Hint] ${message}`
7422
+ }
7423
+ ]
7424
+ };
7425
+ }
7426
+ return { action: "continue" };
7427
+ }
7428
+ }
7429
+ };
7430
+ }
7431
+ function createHints(config) {
7432
+ const hooksToMerge = [];
7433
+ if (config.iterationProgress) {
7434
+ const options = typeof config.iterationProgress === "boolean" ? {} : config.iterationProgress;
7435
+ hooksToMerge.push(iterationProgressHint(options));
7436
+ }
7437
+ if (config.parallelGadgets) {
7438
+ const options = typeof config.parallelGadgets === "boolean" ? {} : config.parallelGadgets;
7439
+ hooksToMerge.push(parallelGadgetHint(options));
7440
+ }
7441
+ if (config.custom) {
7442
+ hooksToMerge.push(...config.custom);
7443
+ }
7444
+ return HookPresets.merge(...hooksToMerge);
7445
+ }
7446
+
6695
7447
  // src/index.ts
6696
7448
  init_client();
6697
7449
  init_messages();
@@ -7466,14 +8218,21 @@ function createMockClient(options) {
7466
8218
 
7467
8219
  // src/testing/mock-gadget.ts
7468
8220
  init_gadget();
8221
+
8222
+ // src/testing/cli-helpers.ts
8223
+ var import_node_stream = require("stream");
7469
8224
  // Annotate the CommonJS export names for ESM import in node:
7470
8225
  0 && (module.exports = {
7471
8226
  AgentBuilder,
7472
8227
  AnthropicMessagesProvider,
7473
8228
  BaseGadget,
7474
8229
  BreakLoopException,
8230
+ CompactionManager,
7475
8231
  ConversationManager,
8232
+ DEFAULT_COMPACTION_CONFIG,
8233
+ DEFAULT_HINTS,
7476
8234
  DEFAULT_PROMPTS,
8235
+ DEFAULT_SUMMARIZATION_PROMPT,
7477
8236
  Gadget,
7478
8237
  GadgetExecutor,
7479
8238
  GadgetOutputStore,
@@ -7481,6 +8240,7 @@ init_gadget();
7481
8240
  GeminiGenerativeProvider,
7482
8241
  HookPresets,
7483
8242
  HumanInputException,
8243
+ HybridStrategy,
7484
8244
  LLMMessageBuilder,
7485
8245
  LLMist,
7486
8246
  MODEL_ALIASES,
@@ -7490,8 +8250,10 @@ init_gadget();
7490
8250
  ModelIdentifierParser,
7491
8251
  ModelRegistry,
7492
8252
  OpenAIChatProvider,
8253
+ SlidingWindowStrategy,
7493
8254
  StreamParser,
7494
8255
  StreamProcessor,
8256
+ SummarizationStrategy,
7495
8257
  collectEvents,
7496
8258
  collectText,
7497
8259
  complete,
@@ -7499,6 +8261,7 @@ init_gadget();
7499
8261
  createGadget,
7500
8262
  createGadgetOutputViewer,
7501
8263
  createGeminiProviderFromEnv,
8264
+ createHints,
7502
8265
  createLogger,
7503
8266
  createMockAdapter,
7504
8267
  createMockClient,
@@ -7511,7 +8274,10 @@ init_gadget();
7511
8274
  getModelId,
7512
8275
  getProvider,
7513
8276
  hasProviderPrefix,
8277
+ iterationProgressHint,
7514
8278
  mockLLM,
8279
+ parallelGadgetHint,
8280
+ resolveHintTemplate,
7515
8281
  resolveModel,
7516
8282
  resolvePromptTemplate,
7517
8283
  resolveRulesTemplate,