llmist 1.1.0 → 1.3.0

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
  });
@@ -1493,334 +1940,241 @@ var init_hook_validators = __esm({
1493
1940
  }
1494
1941
  });
1495
1942
 
1496
- // src/gadgets/error-formatter.ts
1497
- var GadgetErrorFormatter;
1498
- var init_error_formatter = __esm({
1499
- "src/gadgets/error-formatter.ts"() {
1943
+ // src/gadgets/schema-introspector.ts
1944
+ function getDef(schema) {
1945
+ return schema._def;
1946
+ }
1947
+ function getTypeName(schema) {
1948
+ const def = getDef(schema);
1949
+ return def?.type ?? def?.typeName;
1950
+ }
1951
+ function getShape(schema) {
1952
+ const def = getDef(schema);
1953
+ if (typeof def?.shape === "function") {
1954
+ return def.shape();
1955
+ }
1956
+ return def?.shape;
1957
+ }
1958
+ var SchemaIntrospector;
1959
+ var init_schema_introspector = __esm({
1960
+ "src/gadgets/schema-introspector.ts"() {
1500
1961
  "use strict";
1501
- init_constants();
1502
- GadgetErrorFormatter = class {
1503
- argPrefix;
1504
- startPrefix;
1505
- endPrefix;
1506
- constructor(options = {}) {
1507
- this.argPrefix = options.argPrefix ?? GADGET_ARG_PREFIX;
1508
- this.startPrefix = options.startPrefix ?? GADGET_START_PREFIX;
1509
- this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
1962
+ SchemaIntrospector = class {
1963
+ schema;
1964
+ cache = /* @__PURE__ */ new Map();
1965
+ constructor(schema) {
1966
+ this.schema = schema;
1510
1967
  }
1511
1968
  /**
1512
- * Format a Zod validation error with full gadget instructions.
1969
+ * Get the expected type at a JSON pointer path.
1513
1970
  *
1514
- * @param gadgetName - Name of the gadget that was called
1515
- * @param zodError - The Zod validation error
1516
- * @param gadget - The gadget instance (for generating instructions)
1517
- * @returns Formatted error message with usage instructions
1971
+ * @param pointer - JSON pointer path without leading / (e.g., "config/timeout", "items/0")
1972
+ * @returns Type hint for coercion decision
1518
1973
  */
1519
- formatValidationError(gadgetName, zodError, gadget) {
1520
- const parts = [];
1521
- parts.push(`Error: Invalid parameters for '${gadgetName}':`);
1522
- for (const issue of zodError.issues) {
1523
- const path = issue.path.join(".") || "root";
1524
- parts.push(` - ${path}: ${issue.message}`);
1974
+ getTypeAtPath(pointer) {
1975
+ const cached = this.cache.get(pointer);
1976
+ if (cached !== void 0) {
1977
+ return cached;
1525
1978
  }
1526
- parts.push("");
1527
- parts.push("Gadget Usage:");
1528
- parts.push(gadget.getInstruction(this.argPrefix));
1529
- return parts.join("\n");
1979
+ const result = this.resolveTypeAtPath(pointer);
1980
+ this.cache.set(pointer, result);
1981
+ return result;
1530
1982
  }
1531
1983
  /**
1532
- * Format a parse error with block format reference.
1533
- *
1534
- * @param gadgetName - Name of the gadget that was called
1535
- * @param parseError - The parse error message
1536
- * @param gadget - The gadget instance if found (for generating instructions)
1537
- * @returns Formatted error message with format reference
1984
+ * Internal method to resolve type at path without caching.
1538
1985
  */
1539
- formatParseError(gadgetName, parseError, gadget) {
1540
- const parts = [];
1541
- parts.push(`Error: Failed to parse parameters for '${gadgetName}':`);
1542
- parts.push(` ${parseError}`);
1543
- if (gadget) {
1544
- parts.push("");
1545
- parts.push("Gadget Usage:");
1546
- parts.push(gadget.getInstruction(this.argPrefix));
1986
+ resolveTypeAtPath(pointer) {
1987
+ if (!pointer) {
1988
+ return this.getBaseType(this.schema);
1989
+ }
1990
+ const segments = pointer.split("/");
1991
+ let current = this.schema;
1992
+ for (const segment of segments) {
1993
+ current = this.unwrapSchema(current);
1994
+ const typeName = getTypeName(current);
1995
+ if (typeName === "object" || typeName === "ZodObject") {
1996
+ const shape = getShape(current);
1997
+ if (!shape || !(segment in shape)) {
1998
+ return "unknown";
1999
+ }
2000
+ current = shape[segment];
2001
+ } else if (typeName === "array" || typeName === "ZodArray") {
2002
+ if (!/^\d+$/.test(segment)) {
2003
+ return "unknown";
2004
+ }
2005
+ const def = getDef(current);
2006
+ const elementType = def?.element ?? def?.type;
2007
+ if (!elementType) {
2008
+ return "unknown";
2009
+ }
2010
+ current = elementType;
2011
+ } else if (typeName === "tuple" || typeName === "ZodTuple") {
2012
+ if (!/^\d+$/.test(segment)) {
2013
+ return "unknown";
2014
+ }
2015
+ const index = parseInt(segment, 10);
2016
+ const def = getDef(current);
2017
+ const items = def?.items;
2018
+ if (!items || index >= items.length) {
2019
+ return "unknown";
2020
+ }
2021
+ current = items[index];
2022
+ } else if (typeName === "record" || typeName === "ZodRecord") {
2023
+ const def = getDef(current);
2024
+ const valueType = def?.valueType;
2025
+ if (!valueType) {
2026
+ return "unknown";
2027
+ }
2028
+ current = valueType;
2029
+ } else {
2030
+ return "unknown";
2031
+ }
1547
2032
  }
1548
- parts.push("");
1549
- parts.push("Block Format Reference:");
1550
- parts.push(` ${this.startPrefix}${gadgetName}`);
1551
- parts.push(` ${this.argPrefix}parameterName`);
1552
- parts.push(" parameter value here");
1553
- parts.push(` ${this.endPrefix}`);
1554
- return parts.join("\n");
2033
+ return this.getBaseType(current);
1555
2034
  }
1556
2035
  /**
1557
- * Format a registry error (gadget not found) with available gadgets list.
1558
- *
1559
- * @param gadgetName - Name of the gadget that was not found
1560
- * @param availableGadgets - List of available gadget names
1561
- * @returns Formatted error message with available gadgets
2036
+ * Unwrap schema modifiers (optional, default, nullable, branded, etc.)
2037
+ * to get to the underlying type.
1562
2038
  */
1563
- formatRegistryError(gadgetName, availableGadgets) {
1564
- const parts = [];
1565
- parts.push(`Error: Gadget '${gadgetName}' not found.`);
1566
- if (availableGadgets.length > 0) {
1567
- parts.push("");
1568
- parts.push(`Available gadgets: ${availableGadgets.join(", ")}`);
1569
- } else {
1570
- parts.push("");
1571
- parts.push("No gadgets are currently registered.");
2039
+ unwrapSchema(schema) {
2040
+ let current = schema;
2041
+ let iterations = 0;
2042
+ const maxIterations = 20;
2043
+ while (iterations < maxIterations) {
2044
+ const typeName = getTypeName(current);
2045
+ const wrapperTypes = [
2046
+ "optional",
2047
+ "nullable",
2048
+ "default",
2049
+ "catch",
2050
+ "branded",
2051
+ "readonly",
2052
+ "pipeline",
2053
+ "ZodOptional",
2054
+ "ZodNullable",
2055
+ "ZodDefault",
2056
+ "ZodCatch",
2057
+ "ZodBranded",
2058
+ "ZodReadonly",
2059
+ "ZodPipeline"
2060
+ ];
2061
+ if (typeName && wrapperTypes.includes(typeName)) {
2062
+ const def = getDef(current);
2063
+ const inner = def?.innerType ?? def?.in ?? def?.type;
2064
+ if (!inner || inner === current) break;
2065
+ current = inner;
2066
+ iterations++;
2067
+ continue;
2068
+ }
2069
+ break;
1572
2070
  }
1573
- return parts.join("\n");
1574
- }
1575
- };
1576
- }
1577
- });
1578
-
1579
- // src/gadgets/exceptions.ts
1580
- var BreakLoopException, HumanInputException, TimeoutException;
1581
- var init_exceptions = __esm({
1582
- "src/gadgets/exceptions.ts"() {
1583
- "use strict";
1584
- BreakLoopException = class extends Error {
1585
- constructor(message) {
1586
- super(message ?? "Agent loop terminated by gadget");
1587
- this.name = "BreakLoopException";
1588
- }
1589
- };
1590
- HumanInputException = class extends Error {
1591
- question;
1592
- constructor(question) {
1593
- super(`Human input required: ${question}`);
1594
- this.name = "HumanInputException";
1595
- this.question = question;
2071
+ return current;
1596
2072
  }
1597
- };
1598
- TimeoutException = class extends Error {
1599
- timeoutMs;
1600
- gadgetName;
1601
- constructor(gadgetName, timeoutMs) {
1602
- super(`Gadget '${gadgetName}' execution exceeded timeout of ${timeoutMs}ms`);
1603
- this.name = "TimeoutException";
1604
- this.gadgetName = gadgetName;
1605
- this.timeoutMs = timeoutMs;
1606
- }
1607
- };
1608
- }
1609
- });
1610
-
1611
- // src/gadgets/executor.ts
1612
- var GadgetExecutor;
1613
- var init_executor = __esm({
1614
- "src/gadgets/executor.ts"() {
1615
- "use strict";
1616
- init_logger();
1617
- init_error_formatter();
1618
- init_exceptions();
1619
- GadgetExecutor = class {
1620
- constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions) {
1621
- this.registry = registry;
1622
- this.onHumanInputRequired = onHumanInputRequired;
1623
- this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
1624
- this.logger = logger ?? createLogger({ name: "llmist:executor" });
1625
- this.errorFormatter = new GadgetErrorFormatter(errorFormatterOptions);
1626
- }
1627
- logger;
1628
- errorFormatter;
1629
- /**
1630
- * Creates a promise that rejects with a TimeoutException after the specified timeout.
1631
- */
1632
- createTimeoutPromise(gadgetName, timeoutMs) {
1633
- return new Promise((_, reject) => {
1634
- setTimeout(() => {
1635
- reject(new TimeoutException(gadgetName, timeoutMs));
1636
- }, timeoutMs);
1637
- });
1638
- }
1639
- // Execute a gadget call asynchronously
1640
- async execute(call) {
1641
- const startTime = Date.now();
1642
- this.logger.debug("Executing gadget", {
1643
- gadgetName: call.gadgetName,
1644
- invocationId: call.invocationId,
1645
- parameters: call.parameters
1646
- });
1647
- const rawParameters = call.parameters ?? {};
1648
- let validatedParameters = rawParameters;
1649
- try {
1650
- const gadget = this.registry.get(call.gadgetName);
1651
- if (!gadget) {
1652
- this.logger.error("Gadget not found", { gadgetName: call.gadgetName });
1653
- const availableGadgets = this.registry.getNames();
1654
- return {
1655
- gadgetName: call.gadgetName,
1656
- invocationId: call.invocationId,
1657
- parameters: call.parameters ?? {},
1658
- error: this.errorFormatter.formatRegistryError(call.gadgetName, availableGadgets),
1659
- executionTimeMs: Date.now() - startTime
1660
- };
1661
- }
1662
- if (call.parseError || !call.parameters) {
1663
- this.logger.error("Gadget parameter parse error", {
1664
- gadgetName: call.gadgetName,
1665
- parseError: call.parseError,
1666
- rawParameters: call.parametersRaw
1667
- });
1668
- const parseErrorMessage = call.parseError ?? "Failed to parse parameters";
1669
- return {
1670
- gadgetName: call.gadgetName,
1671
- invocationId: call.invocationId,
1672
- parameters: {},
1673
- error: this.errorFormatter.formatParseError(call.gadgetName, parseErrorMessage, gadget),
1674
- executionTimeMs: Date.now() - startTime
1675
- };
1676
- }
1677
- if (gadget.parameterSchema) {
1678
- const validationResult = gadget.parameterSchema.safeParse(rawParameters);
1679
- if (!validationResult.success) {
1680
- const validationError = this.errorFormatter.formatValidationError(
1681
- call.gadgetName,
1682
- validationResult.error,
1683
- gadget
1684
- );
1685
- this.logger.error("Gadget parameter validation failed", {
1686
- gadgetName: call.gadgetName,
1687
- issueCount: validationResult.error.issues.length
1688
- });
1689
- return {
1690
- gadgetName: call.gadgetName,
1691
- invocationId: call.invocationId,
1692
- parameters: rawParameters,
1693
- error: validationError,
1694
- executionTimeMs: Date.now() - startTime
1695
- };
1696
- }
1697
- validatedParameters = validationResult.data;
1698
- }
1699
- const timeoutMs = gadget.timeoutMs ?? this.defaultGadgetTimeoutMs;
1700
- let result;
1701
- if (timeoutMs && timeoutMs > 0) {
1702
- this.logger.debug("Executing gadget with timeout", {
1703
- gadgetName: call.gadgetName,
1704
- timeoutMs
1705
- });
1706
- result = await Promise.race([
1707
- Promise.resolve(gadget.execute(validatedParameters)),
1708
- this.createTimeoutPromise(call.gadgetName, timeoutMs)
1709
- ]);
1710
- } else {
1711
- result = await Promise.resolve(gadget.execute(validatedParameters));
1712
- }
1713
- const executionTimeMs = Date.now() - startTime;
1714
- this.logger.info("Gadget executed successfully", {
1715
- gadgetName: call.gadgetName,
1716
- invocationId: call.invocationId,
1717
- executionTimeMs
1718
- });
1719
- this.logger.debug("Gadget result", {
1720
- gadgetName: call.gadgetName,
1721
- invocationId: call.invocationId,
1722
- parameters: validatedParameters,
1723
- result,
1724
- executionTimeMs
1725
- });
1726
- return {
1727
- gadgetName: call.gadgetName,
1728
- invocationId: call.invocationId,
1729
- parameters: validatedParameters,
1730
- result,
1731
- executionTimeMs
1732
- };
1733
- } catch (error) {
1734
- if (error instanceof BreakLoopException) {
1735
- this.logger.info("Gadget requested loop termination", {
1736
- gadgetName: call.gadgetName,
1737
- message: error.message
1738
- });
1739
- return {
1740
- gadgetName: call.gadgetName,
1741
- invocationId: call.invocationId,
1742
- parameters: validatedParameters,
1743
- result: error.message,
1744
- breaksLoop: true,
1745
- executionTimeMs: Date.now() - startTime
1746
- };
1747
- }
1748
- if (error instanceof TimeoutException) {
1749
- this.logger.error("Gadget execution timed out", {
1750
- gadgetName: call.gadgetName,
1751
- timeoutMs: error.timeoutMs,
1752
- executionTimeMs: Date.now() - startTime
1753
- });
1754
- return {
1755
- gadgetName: call.gadgetName,
1756
- invocationId: call.invocationId,
1757
- parameters: validatedParameters,
1758
- error: error.message,
1759
- executionTimeMs: Date.now() - startTime
1760
- };
1761
- }
1762
- if (error instanceof HumanInputException) {
1763
- this.logger.info("Gadget requested human input", {
1764
- gadgetName: call.gadgetName,
1765
- question: error.question
1766
- });
1767
- if (this.onHumanInputRequired) {
1768
- try {
1769
- const answer = await this.onHumanInputRequired(error.question);
1770
- this.logger.debug("Human input received", {
1771
- gadgetName: call.gadgetName,
1772
- answerLength: answer.length
1773
- });
1774
- return {
1775
- gadgetName: call.gadgetName,
1776
- invocationId: call.invocationId,
1777
- parameters: validatedParameters,
1778
- result: answer,
1779
- executionTimeMs: Date.now() - startTime
1780
- };
1781
- } catch (inputError) {
1782
- this.logger.error("Human input callback error", {
1783
- gadgetName: call.gadgetName,
1784
- error: inputError instanceof Error ? inputError.message : String(inputError)
1785
- });
1786
- return {
1787
- gadgetName: call.gadgetName,
1788
- invocationId: call.invocationId,
1789
- parameters: validatedParameters,
1790
- error: inputError instanceof Error ? inputError.message : String(inputError),
1791
- executionTimeMs: Date.now() - startTime
1792
- };
1793
- }
1794
- }
1795
- this.logger.warn("Human input required but no callback provided", {
1796
- gadgetName: call.gadgetName
1797
- });
1798
- return {
1799
- gadgetName: call.gadgetName,
1800
- invocationId: call.invocationId,
1801
- parameters: validatedParameters,
1802
- error: "Human input required but not available (stdin is not interactive)",
1803
- executionTimeMs: Date.now() - startTime
1804
- };
1805
- }
1806
- const executionTimeMs = Date.now() - startTime;
1807
- this.logger.error("Gadget execution failed", {
1808
- gadgetName: call.gadgetName,
1809
- error: error instanceof Error ? error.message : String(error),
1810
- executionTimeMs
1811
- });
1812
- return {
1813
- gadgetName: call.gadgetName,
1814
- invocationId: call.invocationId,
1815
- parameters: validatedParameters,
1816
- error: error instanceof Error ? error.message : String(error),
1817
- executionTimeMs
1818
- };
1819
- }
1820
- }
1821
- // Execute multiple gadget calls in parallel
1822
- async executeAll(calls) {
1823
- return Promise.all(calls.map((call) => this.execute(call)));
2073
+ /**
2074
+ * Get the primitive type hint from an unwrapped schema.
2075
+ */
2076
+ getBaseType(schema) {
2077
+ const unwrapped = this.unwrapSchema(schema);
2078
+ const typeName = getTypeName(unwrapped);
2079
+ switch (typeName) {
2080
+ // Primitive types
2081
+ case "string":
2082
+ case "ZodString":
2083
+ return "string";
2084
+ case "number":
2085
+ case "ZodNumber":
2086
+ case "bigint":
2087
+ case "ZodBigInt":
2088
+ return "number";
2089
+ case "boolean":
2090
+ case "ZodBoolean":
2091
+ return "boolean";
2092
+ // Literal types - check the literal value type
2093
+ case "literal":
2094
+ case "ZodLiteral": {
2095
+ const def = getDef(unwrapped);
2096
+ const values = def?.values;
2097
+ const value = values?.[0] ?? def?.value;
2098
+ if (typeof value === "string") return "string";
2099
+ if (typeof value === "number" || typeof value === "bigint")
2100
+ return "number";
2101
+ if (typeof value === "boolean") return "boolean";
2102
+ return "unknown";
2103
+ }
2104
+ // Enum - always string keys
2105
+ case "enum":
2106
+ case "ZodEnum":
2107
+ case "nativeEnum":
2108
+ case "ZodNativeEnum":
2109
+ return "string";
2110
+ // Union - return 'unknown' to let auto-coercion decide
2111
+ // Since multiple types are valid, we can't definitively say what the LLM intended
2112
+ // Auto-coercion will handle common cases (numbers, booleans) appropriately
2113
+ case "union":
2114
+ case "ZodUnion":
2115
+ return "unknown";
2116
+ // Discriminated union - complex, return unknown
2117
+ case "discriminatedUnion":
2118
+ case "ZodDiscriminatedUnion":
2119
+ return "unknown";
2120
+ // Intersection - check both sides
2121
+ case "intersection":
2122
+ case "ZodIntersection": {
2123
+ const def = getDef(unwrapped);
2124
+ const left = def?.left;
2125
+ const right = def?.right;
2126
+ if (!left || !right) return "unknown";
2127
+ const leftType = this.getBaseType(left);
2128
+ const rightType = this.getBaseType(right);
2129
+ if (leftType === rightType) return leftType;
2130
+ if (leftType === "string" || rightType === "string") return "string";
2131
+ return "unknown";
2132
+ }
2133
+ // Effects/transforms - return unknown to let Zod handle it
2134
+ case "effects":
2135
+ case "ZodEffects":
2136
+ return "unknown";
2137
+ // Lazy - can't resolve without evaluating
2138
+ case "lazy":
2139
+ case "ZodLazy":
2140
+ return "unknown";
2141
+ // Complex types - return unknown
2142
+ case "object":
2143
+ case "ZodObject":
2144
+ case "array":
2145
+ case "ZodArray":
2146
+ case "tuple":
2147
+ case "ZodTuple":
2148
+ case "record":
2149
+ case "ZodRecord":
2150
+ case "map":
2151
+ case "ZodMap":
2152
+ case "set":
2153
+ case "ZodSet":
2154
+ case "function":
2155
+ case "ZodFunction":
2156
+ case "promise":
2157
+ case "ZodPromise":
2158
+ case "date":
2159
+ case "ZodDate":
2160
+ return "unknown";
2161
+ // Unknown/any/never/void/undefined/null
2162
+ case "unknown":
2163
+ case "ZodUnknown":
2164
+ case "any":
2165
+ case "ZodAny":
2166
+ case "never":
2167
+ case "ZodNever":
2168
+ case "void":
2169
+ case "ZodVoid":
2170
+ case "undefined":
2171
+ case "ZodUndefined":
2172
+ case "null":
2173
+ case "ZodNull":
2174
+ return "unknown";
2175
+ default:
2176
+ return "unknown";
2177
+ }
1824
2178
  }
1825
2179
  };
1826
2180
  }
@@ -1831,6 +2185,7 @@ function parseBlockParams(content, options) {
1831
2185
  const argPrefix = options?.argPrefix ?? GADGET_ARG_PREFIX;
1832
2186
  const result = {};
1833
2187
  const seenPointers = /* @__PURE__ */ new Set();
2188
+ const introspector = options?.schema ? new SchemaIntrospector(options.schema) : void 0;
1834
2189
  const parts = content.split(argPrefix);
1835
2190
  for (let i = 1; i < parts.length; i++) {
1836
2191
  const part = parts[i];
@@ -1842,7 +2197,7 @@ function parseBlockParams(content, options) {
1842
2197
  throw new Error(`Duplicate pointer: ${pointer2}`);
1843
2198
  }
1844
2199
  seenPointers.add(pointer2);
1845
- setByPointer(result, pointer2, "");
2200
+ setByPointer(result, pointer2, "", introspector);
1846
2201
  }
1847
2202
  continue;
1848
2203
  }
@@ -1858,15 +2213,30 @@ function parseBlockParams(content, options) {
1858
2213
  throw new Error(`Duplicate pointer: ${pointer}`);
1859
2214
  }
1860
2215
  seenPointers.add(pointer);
1861
- setByPointer(result, pointer, value);
2216
+ setByPointer(result, pointer, value, introspector);
1862
2217
  }
1863
2218
  return result;
1864
2219
  }
1865
- function coerceValue(value) {
2220
+ function coerceValue(value, expectedType) {
1866
2221
  if (value.includes("\n")) {
1867
2222
  return value;
1868
2223
  }
1869
2224
  const trimmed = value.trim();
2225
+ if (expectedType === "string") {
2226
+ return value;
2227
+ }
2228
+ if (expectedType === "boolean") {
2229
+ if (trimmed === "true") return true;
2230
+ if (trimmed === "false") return false;
2231
+ return value;
2232
+ }
2233
+ if (expectedType === "number") {
2234
+ const num = Number(trimmed);
2235
+ if (!isNaN(num) && isFinite(num) && trimmed !== "") {
2236
+ return num;
2237
+ }
2238
+ return value;
2239
+ }
1870
2240
  if (trimmed === "true") return true;
1871
2241
  if (trimmed === "false") return false;
1872
2242
  if (trimmed !== "" && /^-?\d+(\.\d+)?$/.test(trimmed)) {
@@ -1877,7 +2247,7 @@ function coerceValue(value) {
1877
2247
  }
1878
2248
  return value;
1879
2249
  }
1880
- function setByPointer(obj, pointer, value) {
2250
+ function setByPointer(obj, pointer, value, introspector) {
1881
2251
  const segments = pointer.split("/");
1882
2252
  let current = obj;
1883
2253
  for (let i = 0; i < segments.length - 1; i++) {
@@ -1905,7 +2275,8 @@ function setByPointer(obj, pointer, value) {
1905
2275
  }
1906
2276
  }
1907
2277
  const lastSegment = segments[segments.length - 1];
1908
- const coercedValue = coerceValue(value);
2278
+ const expectedType = introspector?.getTypeAtPath(pointer);
2279
+ const coercedValue = coerceValue(value, expectedType);
1909
2280
  if (Array.isArray(current)) {
1910
2281
  const index = parseInt(lastSegment, 10);
1911
2282
  if (isNaN(index) || index < 0) {
@@ -1923,6 +2294,122 @@ var init_block_params = __esm({
1923
2294
  "src/gadgets/block-params.ts"() {
1924
2295
  "use strict";
1925
2296
  init_constants();
2297
+ init_schema_introspector();
2298
+ }
2299
+ });
2300
+
2301
+ // src/gadgets/error-formatter.ts
2302
+ var GadgetErrorFormatter;
2303
+ var init_error_formatter = __esm({
2304
+ "src/gadgets/error-formatter.ts"() {
2305
+ "use strict";
2306
+ init_constants();
2307
+ GadgetErrorFormatter = class {
2308
+ argPrefix;
2309
+ startPrefix;
2310
+ endPrefix;
2311
+ constructor(options = {}) {
2312
+ this.argPrefix = options.argPrefix ?? GADGET_ARG_PREFIX;
2313
+ this.startPrefix = options.startPrefix ?? GADGET_START_PREFIX;
2314
+ this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
2315
+ }
2316
+ /**
2317
+ * Format a Zod validation error with full gadget instructions.
2318
+ *
2319
+ * @param gadgetName - Name of the gadget that was called
2320
+ * @param zodError - The Zod validation error
2321
+ * @param gadget - The gadget instance (for generating instructions)
2322
+ * @returns Formatted error message with usage instructions
2323
+ */
2324
+ formatValidationError(gadgetName, zodError, gadget) {
2325
+ const parts = [];
2326
+ parts.push(`Error: Invalid parameters for '${gadgetName}':`);
2327
+ for (const issue of zodError.issues) {
2328
+ const path = issue.path.join(".") || "root";
2329
+ parts.push(` - ${path}: ${issue.message}`);
2330
+ }
2331
+ parts.push("");
2332
+ parts.push("Gadget Usage:");
2333
+ parts.push(gadget.getInstruction(this.argPrefix));
2334
+ return parts.join("\n");
2335
+ }
2336
+ /**
2337
+ * Format a parse error with block format reference.
2338
+ *
2339
+ * @param gadgetName - Name of the gadget that was called
2340
+ * @param parseError - The parse error message
2341
+ * @param gadget - The gadget instance if found (for generating instructions)
2342
+ * @returns Formatted error message with format reference
2343
+ */
2344
+ formatParseError(gadgetName, parseError, gadget) {
2345
+ const parts = [];
2346
+ parts.push(`Error: Failed to parse parameters for '${gadgetName}':`);
2347
+ parts.push(` ${parseError}`);
2348
+ if (gadget) {
2349
+ parts.push("");
2350
+ parts.push("Gadget Usage:");
2351
+ parts.push(gadget.getInstruction(this.argPrefix));
2352
+ }
2353
+ parts.push("");
2354
+ parts.push("Block Format Reference:");
2355
+ parts.push(` ${this.startPrefix}${gadgetName}`);
2356
+ parts.push(` ${this.argPrefix}parameterName`);
2357
+ parts.push(" parameter value here");
2358
+ parts.push(` ${this.endPrefix}`);
2359
+ return parts.join("\n");
2360
+ }
2361
+ /**
2362
+ * Format a registry error (gadget not found) with available gadgets list.
2363
+ *
2364
+ * @param gadgetName - Name of the gadget that was not found
2365
+ * @param availableGadgets - List of available gadget names
2366
+ * @returns Formatted error message with available gadgets
2367
+ */
2368
+ formatRegistryError(gadgetName, availableGadgets) {
2369
+ const parts = [];
2370
+ parts.push(`Error: Gadget '${gadgetName}' not found.`);
2371
+ if (availableGadgets.length > 0) {
2372
+ parts.push("");
2373
+ parts.push(`Available gadgets: ${availableGadgets.join(", ")}`);
2374
+ } else {
2375
+ parts.push("");
2376
+ parts.push("No gadgets are currently registered.");
2377
+ }
2378
+ return parts.join("\n");
2379
+ }
2380
+ };
2381
+ }
2382
+ });
2383
+
2384
+ // src/gadgets/exceptions.ts
2385
+ var BreakLoopException, HumanInputException, TimeoutException;
2386
+ var init_exceptions = __esm({
2387
+ "src/gadgets/exceptions.ts"() {
2388
+ "use strict";
2389
+ BreakLoopException = class extends Error {
2390
+ constructor(message) {
2391
+ super(message ?? "Agent loop terminated by gadget");
2392
+ this.name = "BreakLoopException";
2393
+ }
2394
+ };
2395
+ HumanInputException = class extends Error {
2396
+ question;
2397
+ constructor(question) {
2398
+ super(`Human input required: ${question}`);
2399
+ this.name = "HumanInputException";
2400
+ this.question = question;
2401
+ }
2402
+ };
2403
+ TimeoutException = class extends Error {
2404
+ timeoutMs;
2405
+ gadgetName;
2406
+ constructor(gadgetName, timeoutMs) {
2407
+ super(`Gadget '${gadgetName}' execution exceeded timeout of ${timeoutMs}ms`);
2408
+ this.name = "TimeoutException";
2409
+ this.gadgetName = gadgetName;
2410
+ this.timeoutMs = timeoutMs;
2411
+ }
2412
+ };
1926
2413
  }
1927
2414
  });
1928
2415
 
@@ -2052,52 +2539,329 @@ var init_parser = __esm({
2052
2539
  parseError
2053
2540
  }
2054
2541
  };
2055
- startIndex = partEndIndex + endMarkerLength;
2056
- this.lastReportedTextLength = startIndex;
2057
- }
2058
- if (startIndex > 0) {
2059
- this.buffer = this.buffer.substring(startIndex);
2060
- this.lastReportedTextLength = 0;
2061
- }
2062
- }
2063
- // Finalize parsing and return remaining text or incomplete gadgets
2064
- *finalize() {
2065
- const startIndex = this.buffer.indexOf(this.startPrefix, this.lastReportedTextLength);
2066
- if (startIndex !== -1) {
2067
- const textBefore = this.takeTextUntil(startIndex);
2068
- if (textBefore !== void 0) {
2069
- yield { type: "text", content: textBefore };
2542
+ startIndex = partEndIndex + endMarkerLength;
2543
+ this.lastReportedTextLength = startIndex;
2544
+ }
2545
+ if (startIndex > 0) {
2546
+ this.buffer = this.buffer.substring(startIndex);
2547
+ this.lastReportedTextLength = 0;
2548
+ }
2549
+ }
2550
+ // Finalize parsing and return remaining text or incomplete gadgets
2551
+ *finalize() {
2552
+ const startIndex = this.buffer.indexOf(this.startPrefix, this.lastReportedTextLength);
2553
+ if (startIndex !== -1) {
2554
+ const textBefore = this.takeTextUntil(startIndex);
2555
+ if (textBefore !== void 0) {
2556
+ yield { type: "text", content: textBefore };
2557
+ }
2558
+ const metadataStartIndex = startIndex + this.startPrefix.length;
2559
+ const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
2560
+ if (metadataEndIndex !== -1) {
2561
+ const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
2562
+ const { actualName: actualGadgetName, invocationId } = this.parseGadgetName(gadgetName);
2563
+ const contentStartIndex = metadataEndIndex + 1;
2564
+ const parametersRaw = this.buffer.substring(contentStartIndex).trim();
2565
+ const { parameters, parseError } = this.parseParameters(parametersRaw);
2566
+ yield {
2567
+ type: "gadget_call",
2568
+ call: {
2569
+ gadgetName: actualGadgetName,
2570
+ invocationId,
2571
+ parametersRaw,
2572
+ parameters,
2573
+ parseError
2574
+ }
2575
+ };
2576
+ return;
2577
+ }
2578
+ }
2579
+ const remainingText = this.takeTextUntil(this.buffer.length);
2580
+ if (remainingText !== void 0) {
2581
+ yield { type: "text", content: remainingText };
2582
+ }
2583
+ }
2584
+ // Reset parser state (note: global invocation counter is NOT reset to ensure unique IDs)
2585
+ reset() {
2586
+ this.buffer = "";
2587
+ this.lastReportedTextLength = 0;
2588
+ }
2589
+ };
2590
+ }
2591
+ });
2592
+
2593
+ // src/gadgets/executor.ts
2594
+ var GadgetExecutor;
2595
+ var init_executor = __esm({
2596
+ "src/gadgets/executor.ts"() {
2597
+ "use strict";
2598
+ init_constants();
2599
+ init_logger();
2600
+ init_block_params();
2601
+ init_error_formatter();
2602
+ init_exceptions();
2603
+ init_parser();
2604
+ GadgetExecutor = class {
2605
+ constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions) {
2606
+ this.registry = registry;
2607
+ this.onHumanInputRequired = onHumanInputRequired;
2608
+ this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
2609
+ this.logger = logger ?? createLogger({ name: "llmist:executor" });
2610
+ this.errorFormatter = new GadgetErrorFormatter(errorFormatterOptions);
2611
+ this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
2612
+ }
2613
+ logger;
2614
+ errorFormatter;
2615
+ argPrefix;
2616
+ /**
2617
+ * Creates a promise that rejects with a TimeoutException after the specified timeout.
2618
+ */
2619
+ createTimeoutPromise(gadgetName, timeoutMs) {
2620
+ return new Promise((_, reject) => {
2621
+ setTimeout(() => {
2622
+ reject(new TimeoutException(gadgetName, timeoutMs));
2623
+ }, timeoutMs);
2624
+ });
2625
+ }
2626
+ // Execute a gadget call asynchronously
2627
+ async execute(call) {
2628
+ const startTime = Date.now();
2629
+ this.logger.debug("Executing gadget", {
2630
+ gadgetName: call.gadgetName,
2631
+ invocationId: call.invocationId,
2632
+ parameters: call.parameters
2633
+ });
2634
+ const rawParameters = call.parameters ?? {};
2635
+ let validatedParameters = rawParameters;
2636
+ try {
2637
+ const gadget = this.registry.get(call.gadgetName);
2638
+ if (!gadget) {
2639
+ this.logger.error("Gadget not found", { gadgetName: call.gadgetName });
2640
+ const availableGadgets = this.registry.getNames();
2641
+ return {
2642
+ gadgetName: call.gadgetName,
2643
+ invocationId: call.invocationId,
2644
+ parameters: call.parameters ?? {},
2645
+ error: this.errorFormatter.formatRegistryError(call.gadgetName, availableGadgets),
2646
+ executionTimeMs: Date.now() - startTime
2647
+ };
2648
+ }
2649
+ if (call.parseError || !call.parameters) {
2650
+ this.logger.error("Gadget parameter parse error", {
2651
+ gadgetName: call.gadgetName,
2652
+ parseError: call.parseError,
2653
+ rawParameters: call.parametersRaw
2654
+ });
2655
+ const parseErrorMessage = call.parseError ?? "Failed to parse parameters";
2656
+ return {
2657
+ gadgetName: call.gadgetName,
2658
+ invocationId: call.invocationId,
2659
+ parameters: {},
2660
+ error: this.errorFormatter.formatParseError(call.gadgetName, parseErrorMessage, gadget),
2661
+ executionTimeMs: Date.now() - startTime
2662
+ };
2663
+ }
2664
+ let schemaAwareParameters = rawParameters;
2665
+ const hasBlockFormat = call.parametersRaw?.includes(this.argPrefix);
2666
+ if (gadget.parameterSchema && hasBlockFormat) {
2667
+ try {
2668
+ const cleanedRaw = stripMarkdownFences(call.parametersRaw);
2669
+ const initialParse = parseBlockParams(cleanedRaw, { argPrefix: this.argPrefix });
2670
+ const parametersWereModified = !this.deepEquals(rawParameters, initialParse);
2671
+ if (parametersWereModified) {
2672
+ this.logger.debug("Parameters modified by interceptor, skipping re-parse", {
2673
+ gadgetName: call.gadgetName
2674
+ });
2675
+ schemaAwareParameters = rawParameters;
2676
+ } else {
2677
+ schemaAwareParameters = parseBlockParams(cleanedRaw, {
2678
+ argPrefix: this.argPrefix,
2679
+ schema: gadget.parameterSchema
2680
+ });
2681
+ this.logger.debug("Re-parsed parameters with schema", {
2682
+ gadgetName: call.gadgetName,
2683
+ original: rawParameters,
2684
+ schemaAware: schemaAwareParameters
2685
+ });
2686
+ }
2687
+ } catch (error) {
2688
+ this.logger.warn("Schema-aware re-parsing failed, using original parameters", {
2689
+ gadgetName: call.gadgetName,
2690
+ error: error instanceof Error ? error.message : String(error)
2691
+ });
2692
+ schemaAwareParameters = rawParameters;
2693
+ }
2694
+ }
2695
+ if (gadget.parameterSchema) {
2696
+ const validationResult = gadget.parameterSchema.safeParse(schemaAwareParameters);
2697
+ if (!validationResult.success) {
2698
+ const validationError = this.errorFormatter.formatValidationError(
2699
+ call.gadgetName,
2700
+ validationResult.error,
2701
+ gadget
2702
+ );
2703
+ this.logger.error("Gadget parameter validation failed", {
2704
+ gadgetName: call.gadgetName,
2705
+ issueCount: validationResult.error.issues.length
2706
+ });
2707
+ return {
2708
+ gadgetName: call.gadgetName,
2709
+ invocationId: call.invocationId,
2710
+ parameters: schemaAwareParameters,
2711
+ error: validationError,
2712
+ executionTimeMs: Date.now() - startTime
2713
+ };
2714
+ }
2715
+ validatedParameters = validationResult.data;
2716
+ } else {
2717
+ validatedParameters = schemaAwareParameters;
2718
+ }
2719
+ const timeoutMs = gadget.timeoutMs ?? this.defaultGadgetTimeoutMs;
2720
+ let result;
2721
+ if (timeoutMs && timeoutMs > 0) {
2722
+ this.logger.debug("Executing gadget with timeout", {
2723
+ gadgetName: call.gadgetName,
2724
+ timeoutMs
2725
+ });
2726
+ result = await Promise.race([
2727
+ Promise.resolve(gadget.execute(validatedParameters)),
2728
+ this.createTimeoutPromise(call.gadgetName, timeoutMs)
2729
+ ]);
2730
+ } else {
2731
+ result = await Promise.resolve(gadget.execute(validatedParameters));
2732
+ }
2733
+ const executionTimeMs = Date.now() - startTime;
2734
+ this.logger.info("Gadget executed successfully", {
2735
+ gadgetName: call.gadgetName,
2736
+ invocationId: call.invocationId,
2737
+ executionTimeMs
2738
+ });
2739
+ this.logger.debug("Gadget result", {
2740
+ gadgetName: call.gadgetName,
2741
+ invocationId: call.invocationId,
2742
+ parameters: validatedParameters,
2743
+ result,
2744
+ executionTimeMs
2745
+ });
2746
+ return {
2747
+ gadgetName: call.gadgetName,
2748
+ invocationId: call.invocationId,
2749
+ parameters: validatedParameters,
2750
+ result,
2751
+ executionTimeMs
2752
+ };
2753
+ } catch (error) {
2754
+ if (error instanceof BreakLoopException) {
2755
+ this.logger.info("Gadget requested loop termination", {
2756
+ gadgetName: call.gadgetName,
2757
+ message: error.message
2758
+ });
2759
+ return {
2760
+ gadgetName: call.gadgetName,
2761
+ invocationId: call.invocationId,
2762
+ parameters: validatedParameters,
2763
+ result: error.message,
2764
+ breaksLoop: true,
2765
+ executionTimeMs: Date.now() - startTime
2766
+ };
2070
2767
  }
2071
- const metadataStartIndex = startIndex + this.startPrefix.length;
2072
- const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
2073
- if (metadataEndIndex !== -1) {
2074
- const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
2075
- const { actualName: actualGadgetName, invocationId } = this.parseGadgetName(gadgetName);
2076
- const contentStartIndex = metadataEndIndex + 1;
2077
- const parametersRaw = this.buffer.substring(contentStartIndex).trim();
2078
- const { parameters, parseError } = this.parseParameters(parametersRaw);
2079
- yield {
2080
- type: "gadget_call",
2081
- call: {
2082
- gadgetName: actualGadgetName,
2083
- invocationId,
2084
- parametersRaw,
2085
- parameters,
2086
- parseError
2768
+ if (error instanceof TimeoutException) {
2769
+ this.logger.error("Gadget execution timed out", {
2770
+ gadgetName: call.gadgetName,
2771
+ timeoutMs: error.timeoutMs,
2772
+ executionTimeMs: Date.now() - startTime
2773
+ });
2774
+ return {
2775
+ gadgetName: call.gadgetName,
2776
+ invocationId: call.invocationId,
2777
+ parameters: validatedParameters,
2778
+ error: error.message,
2779
+ executionTimeMs: Date.now() - startTime
2780
+ };
2781
+ }
2782
+ if (error instanceof HumanInputException) {
2783
+ this.logger.info("Gadget requested human input", {
2784
+ gadgetName: call.gadgetName,
2785
+ question: error.question
2786
+ });
2787
+ if (this.onHumanInputRequired) {
2788
+ try {
2789
+ const answer = await this.onHumanInputRequired(error.question);
2790
+ this.logger.debug("Human input received", {
2791
+ gadgetName: call.gadgetName,
2792
+ answerLength: answer.length
2793
+ });
2794
+ return {
2795
+ gadgetName: call.gadgetName,
2796
+ invocationId: call.invocationId,
2797
+ parameters: validatedParameters,
2798
+ result: answer,
2799
+ executionTimeMs: Date.now() - startTime
2800
+ };
2801
+ } catch (inputError) {
2802
+ this.logger.error("Human input callback error", {
2803
+ gadgetName: call.gadgetName,
2804
+ error: inputError instanceof Error ? inputError.message : String(inputError)
2805
+ });
2806
+ return {
2807
+ gadgetName: call.gadgetName,
2808
+ invocationId: call.invocationId,
2809
+ parameters: validatedParameters,
2810
+ error: inputError instanceof Error ? inputError.message : String(inputError),
2811
+ executionTimeMs: Date.now() - startTime
2812
+ };
2087
2813
  }
2814
+ }
2815
+ this.logger.warn("Human input required but no callback provided", {
2816
+ gadgetName: call.gadgetName
2817
+ });
2818
+ return {
2819
+ gadgetName: call.gadgetName,
2820
+ invocationId: call.invocationId,
2821
+ parameters: validatedParameters,
2822
+ error: "Human input required but not available (stdin is not interactive)",
2823
+ executionTimeMs: Date.now() - startTime
2088
2824
  };
2089
- return;
2090
2825
  }
2091
- }
2092
- const remainingText = this.takeTextUntil(this.buffer.length);
2093
- if (remainingText !== void 0) {
2094
- yield { type: "text", content: remainingText };
2826
+ const executionTimeMs = Date.now() - startTime;
2827
+ this.logger.error("Gadget execution failed", {
2828
+ gadgetName: call.gadgetName,
2829
+ error: error instanceof Error ? error.message : String(error),
2830
+ executionTimeMs
2831
+ });
2832
+ return {
2833
+ gadgetName: call.gadgetName,
2834
+ invocationId: call.invocationId,
2835
+ parameters: validatedParameters,
2836
+ error: error instanceof Error ? error.message : String(error),
2837
+ executionTimeMs
2838
+ };
2095
2839
  }
2096
2840
  }
2097
- // Reset parser state (note: global invocation counter is NOT reset to ensure unique IDs)
2098
- reset() {
2099
- this.buffer = "";
2100
- this.lastReportedTextLength = 0;
2841
+ // Execute multiple gadget calls in parallel
2842
+ async executeAll(calls) {
2843
+ return Promise.all(calls.map((call) => this.execute(call)));
2844
+ }
2845
+ /**
2846
+ * Deep equality check for objects/arrays.
2847
+ * Used to detect if parameters were modified by an interceptor.
2848
+ */
2849
+ deepEquals(a, b) {
2850
+ if (a === b) return true;
2851
+ if (a === null || b === null) return a === b;
2852
+ if (typeof a !== typeof b) return false;
2853
+ if (typeof a !== "object") return a === b;
2854
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
2855
+ if (Array.isArray(a) && Array.isArray(b)) {
2856
+ if (a.length !== b.length) return false;
2857
+ return a.every((val, i) => this.deepEquals(val, b[i]));
2858
+ }
2859
+ const aObj = a;
2860
+ const bObj = b;
2861
+ const aKeys = Object.keys(aObj);
2862
+ const bKeys = Object.keys(bObj);
2863
+ if (aKeys.length !== bKeys.length) return false;
2864
+ return aKeys.every((key) => this.deepEquals(aObj[key], bObj[key]));
2101
2865
  }
2102
2866
  };
2103
2867
  }
@@ -2140,7 +2904,8 @@ var init_stream_processor = __esm({
2140
2904
  options.registry,
2141
2905
  options.onHumanInputRequired,
2142
2906
  this.logger.getSubLogger({ name: "executor" }),
2143
- options.defaultGadgetTimeoutMs
2907
+ options.defaultGadgetTimeoutMs,
2908
+ { argPrefix: options.gadgetArgPrefix }
2144
2909
  );
2145
2910
  }
2146
2911
  /**
@@ -2509,6 +3274,7 @@ var init_agent = __esm({
2509
3274
  init_model_shortcuts();
2510
3275
  init_output_viewer();
2511
3276
  init_logger();
3277
+ init_manager();
2512
3278
  init_gadget_output_store();
2513
3279
  init_agent_internal_key();
2514
3280
  init_conversation_manager();
@@ -2539,6 +3305,8 @@ var init_agent = __esm({
2539
3305
  outputStore;
2540
3306
  outputLimitEnabled;
2541
3307
  outputLimitCharLimit;
3308
+ // Context compaction
3309
+ compactionManager;
2542
3310
  /**
2543
3311
  * Creates a new Agent instance.
2544
3312
  * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
@@ -2598,6 +3366,14 @@ var init_agent = __esm({
2598
3366
  if (options.userPrompt) {
2599
3367
  this.conversation.addUserMessage(options.userPrompt);
2600
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
+ }
2601
3377
  }
2602
3378
  /**
2603
3379
  * Get the gadget registry for this agent.
@@ -2620,6 +3396,53 @@ var init_agent = __esm({
2620
3396
  getRegistry() {
2621
3397
  return this.registry;
2622
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
+ }
2623
3446
  /**
2624
3447
  * Run the agent loop.
2625
3448
  * Clean, simple orchestration - all complexity is in StreamProcessor.
@@ -2640,6 +3463,30 @@ var init_agent = __esm({
2640
3463
  while (currentIteration < this.maxIterations) {
2641
3464
  this.logger.debug("Starting iteration", { iteration: currentIteration });
2642
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
+ }
2643
3490
  let llmOptions = {
2644
3491
  model: this.model,
2645
3492
  messages: this.conversation.getMessages(),
@@ -2659,6 +3506,7 @@ var init_agent = __esm({
2659
3506
  if (this.hooks.controllers?.beforeLLMCall) {
2660
3507
  const context = {
2661
3508
  iteration: currentIteration,
3509
+ maxIterations: this.maxIterations,
2662
3510
  options: llmOptions,
2663
3511
  logger: this.logger
2664
3512
  };
@@ -2723,12 +3571,17 @@ var init_agent = __esm({
2723
3571
  });
2724
3572
  let finalMessage = result.finalMessage;
2725
3573
  if (this.hooks.controllers?.afterLLMCall) {
3574
+ const gadgetCallCount = result.outputs.filter(
3575
+ (output) => output.type === "gadget_result"
3576
+ ).length;
2726
3577
  const context = {
2727
3578
  iteration: currentIteration,
3579
+ maxIterations: this.maxIterations,
2728
3580
  options: llmOptions,
2729
3581
  finishReason: result.finishReason,
2730
3582
  usage: result.usage,
2731
3583
  finalMessage: result.finalMessage,
3584
+ gadgetCallCount,
2732
3585
  logger: this.logger
2733
3586
  };
2734
3587
  const action = await this.hooks.controllers.afterLLMCall(context);
@@ -4963,6 +5816,7 @@ var init_builder = __esm({
4963
5816
  defaultGadgetTimeoutMs;
4964
5817
  gadgetOutputLimit;
4965
5818
  gadgetOutputLimitPercent;
5819
+ compactionConfig;
4966
5820
  constructor(client) {
4967
5821
  this.client = client;
4968
5822
  }
@@ -5358,6 +6212,57 @@ var init_builder = __esm({
5358
6212
  this.gadgetOutputLimitPercent = percent;
5359
6213
  return this;
5360
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
+ }
5361
6266
  /**
5362
6267
  * Add a synthetic gadget call to the conversation history.
5363
6268
  *
@@ -5473,7 +6378,8 @@ ${endPrefix}`
5473
6378
  shouldContinueAfterError: this.shouldContinueAfterError,
5474
6379
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
5475
6380
  gadgetOutputLimit: this.gadgetOutputLimit,
5476
- gadgetOutputLimitPercent: this.gadgetOutputLimitPercent
6381
+ gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
6382
+ compactionConfig: this.compactionConfig
5477
6383
  };
5478
6384
  return new Agent(AGENT_INTERNAL_KEY, options);
5479
6385
  }
@@ -5575,7 +6481,8 @@ ${endPrefix}`
5575
6481
  shouldContinueAfterError: this.shouldContinueAfterError,
5576
6482
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
5577
6483
  gadgetOutputLimit: this.gadgetOutputLimit,
5578
- gadgetOutputLimitPercent: this.gadgetOutputLimitPercent
6484
+ gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
6485
+ compactionConfig: this.compactionConfig
5579
6486
  };
5580
6487
  return new Agent(AGENT_INTERNAL_KEY, options);
5581
6488
  }
@@ -5590,8 +6497,12 @@ __export(index_exports, {
5590
6497
  AnthropicMessagesProvider: () => AnthropicMessagesProvider,
5591
6498
  BaseGadget: () => BaseGadget,
5592
6499
  BreakLoopException: () => BreakLoopException,
6500
+ CompactionManager: () => CompactionManager,
5593
6501
  ConversationManager: () => ConversationManager,
6502
+ DEFAULT_COMPACTION_CONFIG: () => DEFAULT_COMPACTION_CONFIG,
6503
+ DEFAULT_HINTS: () => DEFAULT_HINTS,
5594
6504
  DEFAULT_PROMPTS: () => DEFAULT_PROMPTS,
6505
+ DEFAULT_SUMMARIZATION_PROMPT: () => DEFAULT_SUMMARIZATION_PROMPT,
5595
6506
  Gadget: () => Gadget,
5596
6507
  GadgetExecutor: () => GadgetExecutor,
5597
6508
  GadgetOutputStore: () => GadgetOutputStore,
@@ -5599,6 +6510,7 @@ __export(index_exports, {
5599
6510
  GeminiGenerativeProvider: () => GeminiGenerativeProvider,
5600
6511
  HookPresets: () => HookPresets,
5601
6512
  HumanInputException: () => HumanInputException,
6513
+ HybridStrategy: () => HybridStrategy,
5602
6514
  LLMMessageBuilder: () => LLMMessageBuilder,
5603
6515
  LLMist: () => LLMist,
5604
6516
  MODEL_ALIASES: () => MODEL_ALIASES,
@@ -5608,8 +6520,10 @@ __export(index_exports, {
5608
6520
  ModelIdentifierParser: () => ModelIdentifierParser,
5609
6521
  ModelRegistry: () => ModelRegistry,
5610
6522
  OpenAIChatProvider: () => OpenAIChatProvider,
6523
+ SlidingWindowStrategy: () => SlidingWindowStrategy,
5611
6524
  StreamParser: () => StreamParser,
5612
6525
  StreamProcessor: () => StreamProcessor,
6526
+ SummarizationStrategy: () => SummarizationStrategy,
5613
6527
  collectEvents: () => collectEvents,
5614
6528
  collectText: () => collectText,
5615
6529
  complete: () => complete,
@@ -5617,6 +6531,7 @@ __export(index_exports, {
5617
6531
  createGadget: () => createGadget,
5618
6532
  createGadgetOutputViewer: () => createGadgetOutputViewer,
5619
6533
  createGeminiProviderFromEnv: () => createGeminiProviderFromEnv,
6534
+ createHints: () => createHints,
5620
6535
  createLogger: () => createLogger,
5621
6536
  createMockAdapter: () => createMockAdapter,
5622
6537
  createMockClient: () => createMockClient,
@@ -5629,7 +6544,10 @@ __export(index_exports, {
5629
6544
  getModelId: () => getModelId,
5630
6545
  getProvider: () => getProvider,
5631
6546
  hasProviderPrefix: () => hasProviderPrefix,
6547
+ iterationProgressHint: () => iterationProgressHint,
5632
6548
  mockLLM: () => mockLLM,
6549
+ parallelGadgetHint: () => parallelGadgetHint,
6550
+ resolveHintTemplate: () => resolveHintTemplate,
5633
6551
  resolveModel: () => resolveModel,
5634
6552
  resolvePromptTemplate: () => resolvePromptTemplate,
5635
6553
  resolveRulesTemplate: () => resolveRulesTemplate,
@@ -6144,6 +7062,51 @@ var HookPresets = class _HookPresets {
6144
7062
  }
6145
7063
  };
6146
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
+ }
6147
7110
  /**
6148
7111
  * Returns empty hook configuration for clean output without any logging.
6149
7112
  *
@@ -6374,6 +7337,113 @@ init_conversation_manager();
6374
7337
  init_stream_processor();
6375
7338
  init_gadget_output_store();
6376
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
+
6377
7447
  // src/index.ts
6378
7448
  init_client();
6379
7449
  init_messages();
@@ -7148,14 +8218,21 @@ function createMockClient(options) {
7148
8218
 
7149
8219
  // src/testing/mock-gadget.ts
7150
8220
  init_gadget();
8221
+
8222
+ // src/testing/cli-helpers.ts
8223
+ var import_node_stream = require("stream");
7151
8224
  // Annotate the CommonJS export names for ESM import in node:
7152
8225
  0 && (module.exports = {
7153
8226
  AgentBuilder,
7154
8227
  AnthropicMessagesProvider,
7155
8228
  BaseGadget,
7156
8229
  BreakLoopException,
8230
+ CompactionManager,
7157
8231
  ConversationManager,
8232
+ DEFAULT_COMPACTION_CONFIG,
8233
+ DEFAULT_HINTS,
7158
8234
  DEFAULT_PROMPTS,
8235
+ DEFAULT_SUMMARIZATION_PROMPT,
7159
8236
  Gadget,
7160
8237
  GadgetExecutor,
7161
8238
  GadgetOutputStore,
@@ -7163,6 +8240,7 @@ init_gadget();
7163
8240
  GeminiGenerativeProvider,
7164
8241
  HookPresets,
7165
8242
  HumanInputException,
8243
+ HybridStrategy,
7166
8244
  LLMMessageBuilder,
7167
8245
  LLMist,
7168
8246
  MODEL_ALIASES,
@@ -7172,8 +8250,10 @@ init_gadget();
7172
8250
  ModelIdentifierParser,
7173
8251
  ModelRegistry,
7174
8252
  OpenAIChatProvider,
8253
+ SlidingWindowStrategy,
7175
8254
  StreamParser,
7176
8255
  StreamProcessor,
8256
+ SummarizationStrategy,
7177
8257
  collectEvents,
7178
8258
  collectText,
7179
8259
  complete,
@@ -7181,6 +8261,7 @@ init_gadget();
7181
8261
  createGadget,
7182
8262
  createGadgetOutputViewer,
7183
8263
  createGeminiProviderFromEnv,
8264
+ createHints,
7184
8265
  createLogger,
7185
8266
  createMockAdapter,
7186
8267
  createMockClient,
@@ -7193,7 +8274,10 @@ init_gadget();
7193
8274
  getModelId,
7194
8275
  getProvider,
7195
8276
  hasProviderPrefix,
8277
+ iterationProgressHint,
7196
8278
  mockLLM,
8279
+ parallelGadgetHint,
8280
+ resolveHintTemplate,
7197
8281
  resolveModel,
7198
8282
  resolvePromptTemplate,
7199
8283
  resolveRulesTemplate,