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/cli.cjs CHANGED
@@ -1113,6 +1113,417 @@ var init_output_viewer = __esm({
1113
1113
  }
1114
1114
  });
1115
1115
 
1116
+ // src/agent/compaction/config.ts
1117
+ function resolveCompactionConfig(config = {}) {
1118
+ const trigger = config.triggerThresholdPercent ?? DEFAULT_COMPACTION_CONFIG.triggerThresholdPercent;
1119
+ const target = config.targetPercent ?? DEFAULT_COMPACTION_CONFIG.targetPercent;
1120
+ if (target >= trigger) {
1121
+ console.warn(
1122
+ `[llmist/compaction] targetPercent (${target}) should be less than triggerThresholdPercent (${trigger}) to be effective.`
1123
+ );
1124
+ }
1125
+ const strategy = config.strategy ?? DEFAULT_COMPACTION_CONFIG.strategy;
1126
+ const strategyName = typeof strategy === "object" && "name" in strategy ? strategy.name : strategy;
1127
+ return {
1128
+ enabled: config.enabled ?? DEFAULT_COMPACTION_CONFIG.enabled,
1129
+ strategy: strategyName,
1130
+ triggerThresholdPercent: trigger,
1131
+ targetPercent: target,
1132
+ preserveRecentTurns: config.preserveRecentTurns ?? DEFAULT_COMPACTION_CONFIG.preserveRecentTurns,
1133
+ summarizationModel: config.summarizationModel,
1134
+ summarizationPrompt: config.summarizationPrompt ?? DEFAULT_SUMMARIZATION_PROMPT,
1135
+ onCompaction: config.onCompaction
1136
+ };
1137
+ }
1138
+ var DEFAULT_COMPACTION_CONFIG, DEFAULT_SUMMARIZATION_PROMPT;
1139
+ var init_config = __esm({
1140
+ "src/agent/compaction/config.ts"() {
1141
+ "use strict";
1142
+ DEFAULT_COMPACTION_CONFIG = {
1143
+ enabled: true,
1144
+ strategy: "hybrid",
1145
+ triggerThresholdPercent: 80,
1146
+ targetPercent: 50,
1147
+ preserveRecentTurns: 5
1148
+ };
1149
+ DEFAULT_SUMMARIZATION_PROMPT = `Summarize this conversation history concisely, preserving:
1150
+ 1. Key decisions made and their rationale
1151
+ 2. Important facts and data discovered
1152
+ 3. Errors encountered and how they were resolved
1153
+ 4. Current task context and goals
1154
+
1155
+ Format as a brief narrative paragraph, not bullet points.
1156
+ Previous conversation:`;
1157
+ }
1158
+ });
1159
+
1160
+ // src/agent/compaction/strategy.ts
1161
+ function groupIntoTurns(messages) {
1162
+ const turns = [];
1163
+ let currentTurn = [];
1164
+ for (const msg of messages) {
1165
+ if (msg.role === "user" && currentTurn.length > 0) {
1166
+ turns.push({
1167
+ messages: currentTurn,
1168
+ tokenEstimate: estimateTurnTokens(currentTurn)
1169
+ });
1170
+ currentTurn = [msg];
1171
+ } else {
1172
+ currentTurn.push(msg);
1173
+ }
1174
+ }
1175
+ if (currentTurn.length > 0) {
1176
+ turns.push({
1177
+ messages: currentTurn,
1178
+ tokenEstimate: estimateTurnTokens(currentTurn)
1179
+ });
1180
+ }
1181
+ return turns;
1182
+ }
1183
+ function estimateTurnTokens(messages) {
1184
+ return Math.ceil(messages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0) / 4);
1185
+ }
1186
+ function flattenTurns(turns) {
1187
+ return turns.flatMap((turn) => turn.messages);
1188
+ }
1189
+ var init_strategy = __esm({
1190
+ "src/agent/compaction/strategy.ts"() {
1191
+ "use strict";
1192
+ }
1193
+ });
1194
+
1195
+ // src/agent/compaction/strategies/sliding-window.ts
1196
+ var TRUNCATION_MARKER_TEMPLATE, SlidingWindowStrategy;
1197
+ var init_sliding_window = __esm({
1198
+ "src/agent/compaction/strategies/sliding-window.ts"() {
1199
+ "use strict";
1200
+ init_strategy();
1201
+ TRUNCATION_MARKER_TEMPLATE = "[Previous conversation truncated. Removed {count} turn(s) to fit context window.]";
1202
+ SlidingWindowStrategy = class {
1203
+ name = "sliding-window";
1204
+ async compact(messages, config, context) {
1205
+ const turns = groupIntoTurns(messages);
1206
+ const preserveCount = Math.min(config.preserveRecentTurns, turns.length);
1207
+ if (turns.length <= preserveCount) {
1208
+ return {
1209
+ messages,
1210
+ strategyName: this.name,
1211
+ metadata: {
1212
+ originalCount: messages.length,
1213
+ compactedCount: messages.length,
1214
+ tokensBefore: context.currentTokens,
1215
+ tokensAfter: context.currentTokens
1216
+ }
1217
+ };
1218
+ }
1219
+ const turnsToKeep = turns.slice(-preserveCount);
1220
+ const turnsRemoved = turns.length - preserveCount;
1221
+ const truncationMarker = {
1222
+ role: "user",
1223
+ content: TRUNCATION_MARKER_TEMPLATE.replace("{count}", turnsRemoved.toString())
1224
+ };
1225
+ const compactedMessages = [truncationMarker, ...flattenTurns(turnsToKeep)];
1226
+ const tokensAfter = Math.ceil(
1227
+ compactedMessages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0) / 4
1228
+ );
1229
+ return {
1230
+ messages: compactedMessages,
1231
+ strategyName: this.name,
1232
+ metadata: {
1233
+ originalCount: messages.length,
1234
+ compactedCount: compactedMessages.length,
1235
+ tokensBefore: context.currentTokens,
1236
+ tokensAfter
1237
+ }
1238
+ };
1239
+ }
1240
+ };
1241
+ }
1242
+ });
1243
+
1244
+ // src/agent/compaction/strategies/summarization.ts
1245
+ var SummarizationStrategy;
1246
+ var init_summarization = __esm({
1247
+ "src/agent/compaction/strategies/summarization.ts"() {
1248
+ "use strict";
1249
+ init_strategy();
1250
+ SummarizationStrategy = class {
1251
+ name = "summarization";
1252
+ async compact(messages, config, context) {
1253
+ const turns = groupIntoTurns(messages);
1254
+ const preserveCount = Math.min(config.preserveRecentTurns, turns.length);
1255
+ if (turns.length <= preserveCount) {
1256
+ return {
1257
+ messages,
1258
+ strategyName: this.name,
1259
+ metadata: {
1260
+ originalCount: messages.length,
1261
+ compactedCount: messages.length,
1262
+ tokensBefore: context.currentTokens,
1263
+ tokensAfter: context.currentTokens
1264
+ }
1265
+ };
1266
+ }
1267
+ const turnsToSummarize = turns.slice(0, -preserveCount);
1268
+ const turnsToKeep = turns.slice(-preserveCount);
1269
+ const conversationToSummarize = this.formatTurnsForSummary(flattenTurns(turnsToSummarize));
1270
+ const summary = await this.generateSummary(conversationToSummarize, config, context);
1271
+ const summaryMessage = {
1272
+ role: "user",
1273
+ content: `[Previous conversation summary]
1274
+ ${summary}
1275
+ [End of summary - conversation continues below]`
1276
+ };
1277
+ const compactedMessages = [summaryMessage, ...flattenTurns(turnsToKeep)];
1278
+ const tokensAfter = Math.ceil(
1279
+ compactedMessages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0) / 4
1280
+ );
1281
+ return {
1282
+ messages: compactedMessages,
1283
+ summary,
1284
+ strategyName: this.name,
1285
+ metadata: {
1286
+ originalCount: messages.length,
1287
+ compactedCount: compactedMessages.length,
1288
+ tokensBefore: context.currentTokens,
1289
+ tokensAfter
1290
+ }
1291
+ };
1292
+ }
1293
+ /**
1294
+ * Formats messages into a readable conversation format for summarization.
1295
+ */
1296
+ formatTurnsForSummary(messages) {
1297
+ return messages.map((msg) => {
1298
+ const role = msg.role.charAt(0).toUpperCase() + msg.role.slice(1);
1299
+ return `${role}: ${msg.content}`;
1300
+ }).join("\n\n");
1301
+ }
1302
+ /**
1303
+ * Generates a summary using the configured LLM.
1304
+ */
1305
+ async generateSummary(conversation, config, context) {
1306
+ const model = config.summarizationModel ?? context.model;
1307
+ const prompt = `${config.summarizationPrompt}
1308
+
1309
+ ${conversation}`;
1310
+ const response = await context.client.complete(prompt, {
1311
+ model,
1312
+ temperature: 0.3
1313
+ // Low temperature for factual summarization
1314
+ });
1315
+ return response.trim();
1316
+ }
1317
+ };
1318
+ }
1319
+ });
1320
+
1321
+ // src/agent/compaction/strategies/hybrid.ts
1322
+ var MIN_TURNS_FOR_SUMMARIZATION, HybridStrategy;
1323
+ var init_hybrid = __esm({
1324
+ "src/agent/compaction/strategies/hybrid.ts"() {
1325
+ "use strict";
1326
+ init_strategy();
1327
+ init_sliding_window();
1328
+ init_summarization();
1329
+ MIN_TURNS_FOR_SUMMARIZATION = 3;
1330
+ HybridStrategy = class {
1331
+ name = "hybrid";
1332
+ slidingWindow = new SlidingWindowStrategy();
1333
+ summarization = new SummarizationStrategy();
1334
+ async compact(messages, config, context) {
1335
+ const turns = groupIntoTurns(messages);
1336
+ const preserveCount = Math.min(config.preserveRecentTurns, turns.length);
1337
+ if (turns.length <= preserveCount) {
1338
+ return {
1339
+ messages,
1340
+ strategyName: this.name,
1341
+ metadata: {
1342
+ originalCount: messages.length,
1343
+ compactedCount: messages.length,
1344
+ tokensBefore: context.currentTokens,
1345
+ tokensAfter: context.currentTokens
1346
+ }
1347
+ };
1348
+ }
1349
+ const turnsToSummarize = turns.length - preserveCount;
1350
+ if (turnsToSummarize < MIN_TURNS_FOR_SUMMARIZATION) {
1351
+ return this.slidingWindow.compact(messages, config, context);
1352
+ }
1353
+ return this.summarization.compact(messages, config, context);
1354
+ }
1355
+ };
1356
+ }
1357
+ });
1358
+
1359
+ // src/agent/compaction/strategies/index.ts
1360
+ var init_strategies = __esm({
1361
+ "src/agent/compaction/strategies/index.ts"() {
1362
+ "use strict";
1363
+ init_sliding_window();
1364
+ init_summarization();
1365
+ init_hybrid();
1366
+ }
1367
+ });
1368
+
1369
+ // src/agent/compaction/manager.ts
1370
+ function createStrategy(name) {
1371
+ switch (name) {
1372
+ case "sliding-window":
1373
+ return new SlidingWindowStrategy();
1374
+ case "summarization":
1375
+ return new SummarizationStrategy();
1376
+ case "hybrid":
1377
+ return new HybridStrategy();
1378
+ default:
1379
+ throw new Error(`Unknown compaction strategy: ${name}`);
1380
+ }
1381
+ }
1382
+ var CompactionManager;
1383
+ var init_manager = __esm({
1384
+ "src/agent/compaction/manager.ts"() {
1385
+ "use strict";
1386
+ init_config();
1387
+ init_strategies();
1388
+ CompactionManager = class {
1389
+ client;
1390
+ model;
1391
+ config;
1392
+ strategy;
1393
+ modelLimits;
1394
+ // Statistics
1395
+ totalCompactions = 0;
1396
+ totalTokensSaved = 0;
1397
+ lastTokenCount = 0;
1398
+ constructor(client, model, config = {}) {
1399
+ this.client = client;
1400
+ this.model = model;
1401
+ this.config = resolveCompactionConfig(config);
1402
+ if (typeof config.strategy === "object" && "compact" in config.strategy) {
1403
+ this.strategy = config.strategy;
1404
+ } else {
1405
+ this.strategy = createStrategy(this.config.strategy);
1406
+ }
1407
+ }
1408
+ /**
1409
+ * Check if compaction is needed and perform it if so.
1410
+ *
1411
+ * @param conversation - The conversation manager to compact
1412
+ * @param iteration - Current agent iteration (for event metadata)
1413
+ * @returns CompactionEvent if compaction was performed, null otherwise
1414
+ */
1415
+ async checkAndCompact(conversation, iteration) {
1416
+ if (!this.config.enabled) {
1417
+ return null;
1418
+ }
1419
+ if (!this.modelLimits) {
1420
+ this.modelLimits = this.client.modelRegistry.getModelLimits(this.model);
1421
+ if (!this.modelLimits) {
1422
+ return null;
1423
+ }
1424
+ }
1425
+ if (!this.client.countTokens) {
1426
+ return null;
1427
+ }
1428
+ const messages = conversation.getMessages();
1429
+ const currentTokens = await this.client.countTokens(this.model, messages);
1430
+ this.lastTokenCount = currentTokens;
1431
+ const usagePercent = currentTokens / this.modelLimits.contextWindow * 100;
1432
+ if (usagePercent < this.config.triggerThresholdPercent) {
1433
+ return null;
1434
+ }
1435
+ const historyMessages = conversation.getHistoryMessages();
1436
+ const baseMessages = conversation.getBaseMessages();
1437
+ const historyTokens = await this.client.countTokens(this.model, historyMessages);
1438
+ const baseTokens = await this.client.countTokens(this.model, baseMessages);
1439
+ return this.compact(conversation, iteration, {
1440
+ historyMessages,
1441
+ baseMessages,
1442
+ historyTokens,
1443
+ baseTokens,
1444
+ currentTokens: historyTokens + baseTokens
1445
+ });
1446
+ }
1447
+ /**
1448
+ * Force compaction regardless of threshold.
1449
+ *
1450
+ * @param conversation - The conversation manager to compact
1451
+ * @param iteration - Current agent iteration (for event metadata). Use -1 for manual compaction.
1452
+ * @param precomputed - Optional pre-computed token counts (passed from checkAndCompact for efficiency)
1453
+ * @returns CompactionEvent with compaction details
1454
+ */
1455
+ async compact(conversation, iteration, precomputed) {
1456
+ if (!this.modelLimits) {
1457
+ this.modelLimits = this.client.modelRegistry.getModelLimits(this.model);
1458
+ if (!this.modelLimits) {
1459
+ return null;
1460
+ }
1461
+ }
1462
+ const historyMessages = precomputed?.historyMessages ?? conversation.getHistoryMessages();
1463
+ const baseMessages = precomputed?.baseMessages ?? conversation.getBaseMessages();
1464
+ const historyTokens = precomputed?.historyTokens ?? await this.client.countTokens(this.model, historyMessages);
1465
+ const baseTokens = precomputed?.baseTokens ?? await this.client.countTokens(this.model, baseMessages);
1466
+ const currentTokens = precomputed?.currentTokens ?? historyTokens + baseTokens;
1467
+ const targetTotalTokens = Math.floor(
1468
+ this.modelLimits.contextWindow * this.config.targetPercent / 100
1469
+ );
1470
+ const targetHistoryTokens = Math.max(0, targetTotalTokens - baseTokens);
1471
+ const result = await this.strategy.compact(historyMessages, this.config, {
1472
+ currentTokens: historyTokens,
1473
+ targetTokens: targetHistoryTokens,
1474
+ modelLimits: this.modelLimits,
1475
+ client: this.client,
1476
+ model: this.config.summarizationModel ?? this.model
1477
+ });
1478
+ conversation.replaceHistory(result.messages);
1479
+ const afterTokens = await this.client.countTokens(this.model, conversation.getMessages());
1480
+ const tokensSaved = currentTokens - afterTokens;
1481
+ this.totalCompactions++;
1482
+ this.totalTokensSaved += tokensSaved;
1483
+ this.lastTokenCount = afterTokens;
1484
+ const event = {
1485
+ strategy: result.strategyName,
1486
+ tokensBefore: currentTokens,
1487
+ tokensAfter: afterTokens,
1488
+ messagesBefore: historyMessages.length + baseMessages.length,
1489
+ messagesAfter: result.messages.length + baseMessages.length,
1490
+ summary: result.summary,
1491
+ iteration
1492
+ };
1493
+ if (this.config.onCompaction) {
1494
+ try {
1495
+ this.config.onCompaction(event);
1496
+ } catch (err) {
1497
+ console.warn("[llmist/compaction] onCompaction callback error:", err);
1498
+ }
1499
+ }
1500
+ return event;
1501
+ }
1502
+ /**
1503
+ * Get compaction statistics.
1504
+ */
1505
+ getStats() {
1506
+ const contextWindow = this.modelLimits?.contextWindow ?? 0;
1507
+ return {
1508
+ totalCompactions: this.totalCompactions,
1509
+ totalTokensSaved: this.totalTokensSaved,
1510
+ currentUsage: {
1511
+ tokens: this.lastTokenCount,
1512
+ percent: contextWindow > 0 ? this.lastTokenCount / contextWindow * 100 : 0
1513
+ },
1514
+ contextWindow
1515
+ };
1516
+ }
1517
+ /**
1518
+ * Check if compaction is enabled.
1519
+ */
1520
+ isEnabled() {
1521
+ return this.config.enabled;
1522
+ }
1523
+ };
1524
+ }
1525
+ });
1526
+
1116
1527
  // src/agent/gadget-output-store.ts
1117
1528
  var import_node_crypto, GadgetOutputStore;
1118
1529
  var init_gadget_output_store = __esm({
@@ -1215,10 +1626,16 @@ var init_conversation_manager = __esm({
1215
1626
  baseMessages;
1216
1627
  initialMessages;
1217
1628
  historyBuilder;
1629
+ startPrefix;
1630
+ endPrefix;
1631
+ argPrefix;
1218
1632
  constructor(baseMessages, initialMessages, options = {}) {
1219
1633
  this.baseMessages = baseMessages;
1220
1634
  this.initialMessages = initialMessages;
1221
1635
  this.historyBuilder = new LLMMessageBuilder();
1636
+ this.startPrefix = options.startPrefix;
1637
+ this.endPrefix = options.endPrefix;
1638
+ this.argPrefix = options.argPrefix;
1222
1639
  if (options.startPrefix && options.endPrefix) {
1223
1640
  this.historyBuilder.withPrefixes(options.startPrefix, options.endPrefix, options.argPrefix);
1224
1641
  }
@@ -1235,6 +1652,25 @@ var init_conversation_manager = __esm({
1235
1652
  getMessages() {
1236
1653
  return [...this.baseMessages, ...this.initialMessages, ...this.historyBuilder.build()];
1237
1654
  }
1655
+ getHistoryMessages() {
1656
+ return this.historyBuilder.build();
1657
+ }
1658
+ getBaseMessages() {
1659
+ return [...this.baseMessages, ...this.initialMessages];
1660
+ }
1661
+ replaceHistory(newHistory) {
1662
+ this.historyBuilder = new LLMMessageBuilder();
1663
+ if (this.startPrefix && this.endPrefix) {
1664
+ this.historyBuilder.withPrefixes(this.startPrefix, this.endPrefix, this.argPrefix);
1665
+ }
1666
+ for (const msg of newHistory) {
1667
+ if (msg.role === "user") {
1668
+ this.historyBuilder.addUser(msg.content);
1669
+ } else if (msg.role === "assistant") {
1670
+ this.historyBuilder.addAssistant(msg.content);
1671
+ }
1672
+ }
1673
+ }
1238
1674
  };
1239
1675
  }
1240
1676
  });
@@ -1447,334 +1883,241 @@ var init_hook_validators = __esm({
1447
1883
  }
1448
1884
  });
1449
1885
 
1450
- // src/gadgets/error-formatter.ts
1451
- var GadgetErrorFormatter;
1452
- var init_error_formatter = __esm({
1453
- "src/gadgets/error-formatter.ts"() {
1886
+ // src/gadgets/schema-introspector.ts
1887
+ function getDef(schema) {
1888
+ return schema._def;
1889
+ }
1890
+ function getTypeName(schema) {
1891
+ const def = getDef(schema);
1892
+ return def?.type ?? def?.typeName;
1893
+ }
1894
+ function getShape(schema) {
1895
+ const def = getDef(schema);
1896
+ if (typeof def?.shape === "function") {
1897
+ return def.shape();
1898
+ }
1899
+ return def?.shape;
1900
+ }
1901
+ var SchemaIntrospector;
1902
+ var init_schema_introspector = __esm({
1903
+ "src/gadgets/schema-introspector.ts"() {
1454
1904
  "use strict";
1455
- init_constants();
1456
- GadgetErrorFormatter = class {
1457
- argPrefix;
1458
- startPrefix;
1459
- endPrefix;
1460
- constructor(options = {}) {
1461
- this.argPrefix = options.argPrefix ?? GADGET_ARG_PREFIX;
1462
- this.startPrefix = options.startPrefix ?? GADGET_START_PREFIX;
1463
- this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
1905
+ SchemaIntrospector = class {
1906
+ schema;
1907
+ cache = /* @__PURE__ */ new Map();
1908
+ constructor(schema) {
1909
+ this.schema = schema;
1464
1910
  }
1465
1911
  /**
1466
- * Format a Zod validation error with full gadget instructions.
1912
+ * Get the expected type at a JSON pointer path.
1467
1913
  *
1468
- * @param gadgetName - Name of the gadget that was called
1469
- * @param zodError - The Zod validation error
1470
- * @param gadget - The gadget instance (for generating instructions)
1471
- * @returns Formatted error message with usage instructions
1914
+ * @param pointer - JSON pointer path without leading / (e.g., "config/timeout", "items/0")
1915
+ * @returns Type hint for coercion decision
1472
1916
  */
1473
- formatValidationError(gadgetName, zodError, gadget) {
1474
- const parts = [];
1475
- parts.push(`Error: Invalid parameters for '${gadgetName}':`);
1476
- for (const issue of zodError.issues) {
1477
- const path2 = issue.path.join(".") || "root";
1478
- parts.push(` - ${path2}: ${issue.message}`);
1917
+ getTypeAtPath(pointer) {
1918
+ const cached = this.cache.get(pointer);
1919
+ if (cached !== void 0) {
1920
+ return cached;
1479
1921
  }
1480
- parts.push("");
1481
- parts.push("Gadget Usage:");
1482
- parts.push(gadget.getInstruction(this.argPrefix));
1483
- return parts.join("\n");
1922
+ const result = this.resolveTypeAtPath(pointer);
1923
+ this.cache.set(pointer, result);
1924
+ return result;
1484
1925
  }
1485
1926
  /**
1486
- * Format a parse error with block format reference.
1487
- *
1488
- * @param gadgetName - Name of the gadget that was called
1489
- * @param parseError - The parse error message
1490
- * @param gadget - The gadget instance if found (for generating instructions)
1491
- * @returns Formatted error message with format reference
1927
+ * Internal method to resolve type at path without caching.
1492
1928
  */
1493
- formatParseError(gadgetName, parseError, gadget) {
1494
- const parts = [];
1495
- parts.push(`Error: Failed to parse parameters for '${gadgetName}':`);
1496
- parts.push(` ${parseError}`);
1497
- if (gadget) {
1498
- parts.push("");
1499
- parts.push("Gadget Usage:");
1500
- parts.push(gadget.getInstruction(this.argPrefix));
1929
+ resolveTypeAtPath(pointer) {
1930
+ if (!pointer) {
1931
+ return this.getBaseType(this.schema);
1932
+ }
1933
+ const segments = pointer.split("/");
1934
+ let current = this.schema;
1935
+ for (const segment of segments) {
1936
+ current = this.unwrapSchema(current);
1937
+ const typeName = getTypeName(current);
1938
+ if (typeName === "object" || typeName === "ZodObject") {
1939
+ const shape = getShape(current);
1940
+ if (!shape || !(segment in shape)) {
1941
+ return "unknown";
1942
+ }
1943
+ current = shape[segment];
1944
+ } else if (typeName === "array" || typeName === "ZodArray") {
1945
+ if (!/^\d+$/.test(segment)) {
1946
+ return "unknown";
1947
+ }
1948
+ const def = getDef(current);
1949
+ const elementType = def?.element ?? def?.type;
1950
+ if (!elementType) {
1951
+ return "unknown";
1952
+ }
1953
+ current = elementType;
1954
+ } else if (typeName === "tuple" || typeName === "ZodTuple") {
1955
+ if (!/^\d+$/.test(segment)) {
1956
+ return "unknown";
1957
+ }
1958
+ const index = parseInt(segment, 10);
1959
+ const def = getDef(current);
1960
+ const items = def?.items;
1961
+ if (!items || index >= items.length) {
1962
+ return "unknown";
1963
+ }
1964
+ current = items[index];
1965
+ } else if (typeName === "record" || typeName === "ZodRecord") {
1966
+ const def = getDef(current);
1967
+ const valueType = def?.valueType;
1968
+ if (!valueType) {
1969
+ return "unknown";
1970
+ }
1971
+ current = valueType;
1972
+ } else {
1973
+ return "unknown";
1974
+ }
1501
1975
  }
1502
- parts.push("");
1503
- parts.push("Block Format Reference:");
1504
- parts.push(` ${this.startPrefix}${gadgetName}`);
1505
- parts.push(` ${this.argPrefix}parameterName`);
1506
- parts.push(" parameter value here");
1507
- parts.push(` ${this.endPrefix}`);
1508
- return parts.join("\n");
1976
+ return this.getBaseType(current);
1509
1977
  }
1510
1978
  /**
1511
- * Format a registry error (gadget not found) with available gadgets list.
1512
- *
1513
- * @param gadgetName - Name of the gadget that was not found
1514
- * @param availableGadgets - List of available gadget names
1515
- * @returns Formatted error message with available gadgets
1979
+ * Unwrap schema modifiers (optional, default, nullable, branded, etc.)
1980
+ * to get to the underlying type.
1516
1981
  */
1517
- formatRegistryError(gadgetName, availableGadgets) {
1518
- const parts = [];
1519
- parts.push(`Error: Gadget '${gadgetName}' not found.`);
1520
- if (availableGadgets.length > 0) {
1521
- parts.push("");
1522
- parts.push(`Available gadgets: ${availableGadgets.join(", ")}`);
1523
- } else {
1524
- parts.push("");
1525
- parts.push("No gadgets are currently registered.");
1982
+ unwrapSchema(schema) {
1983
+ let current = schema;
1984
+ let iterations = 0;
1985
+ const maxIterations = 20;
1986
+ while (iterations < maxIterations) {
1987
+ const typeName = getTypeName(current);
1988
+ const wrapperTypes = [
1989
+ "optional",
1990
+ "nullable",
1991
+ "default",
1992
+ "catch",
1993
+ "branded",
1994
+ "readonly",
1995
+ "pipeline",
1996
+ "ZodOptional",
1997
+ "ZodNullable",
1998
+ "ZodDefault",
1999
+ "ZodCatch",
2000
+ "ZodBranded",
2001
+ "ZodReadonly",
2002
+ "ZodPipeline"
2003
+ ];
2004
+ if (typeName && wrapperTypes.includes(typeName)) {
2005
+ const def = getDef(current);
2006
+ const inner = def?.innerType ?? def?.in ?? def?.type;
2007
+ if (!inner || inner === current) break;
2008
+ current = inner;
2009
+ iterations++;
2010
+ continue;
2011
+ }
2012
+ break;
1526
2013
  }
1527
- return parts.join("\n");
1528
- }
1529
- };
1530
- }
1531
- });
1532
-
1533
- // src/gadgets/exceptions.ts
1534
- var BreakLoopException, HumanInputException, TimeoutException;
1535
- var init_exceptions = __esm({
1536
- "src/gadgets/exceptions.ts"() {
1537
- "use strict";
1538
- BreakLoopException = class extends Error {
1539
- constructor(message) {
1540
- super(message ?? "Agent loop terminated by gadget");
1541
- this.name = "BreakLoopException";
1542
- }
1543
- };
1544
- HumanInputException = class extends Error {
1545
- question;
1546
- constructor(question) {
1547
- super(`Human input required: ${question}`);
1548
- this.name = "HumanInputException";
1549
- this.question = question;
2014
+ return current;
1550
2015
  }
1551
- };
1552
- TimeoutException = class extends Error {
1553
- timeoutMs;
1554
- gadgetName;
1555
- constructor(gadgetName, timeoutMs) {
1556
- super(`Gadget '${gadgetName}' execution exceeded timeout of ${timeoutMs}ms`);
1557
- this.name = "TimeoutException";
1558
- this.gadgetName = gadgetName;
1559
- this.timeoutMs = timeoutMs;
1560
- }
1561
- };
1562
- }
1563
- });
1564
-
1565
- // src/gadgets/executor.ts
1566
- var GadgetExecutor;
1567
- var init_executor = __esm({
1568
- "src/gadgets/executor.ts"() {
1569
- "use strict";
1570
- init_logger();
1571
- init_error_formatter();
1572
- init_exceptions();
1573
- GadgetExecutor = class {
1574
- constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions) {
1575
- this.registry = registry;
1576
- this.onHumanInputRequired = onHumanInputRequired;
1577
- this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
1578
- this.logger = logger ?? createLogger({ name: "llmist:executor" });
1579
- this.errorFormatter = new GadgetErrorFormatter(errorFormatterOptions);
1580
- }
1581
- logger;
1582
- errorFormatter;
1583
- /**
1584
- * Creates a promise that rejects with a TimeoutException after the specified timeout.
1585
- */
1586
- createTimeoutPromise(gadgetName, timeoutMs) {
1587
- return new Promise((_, reject) => {
1588
- setTimeout(() => {
1589
- reject(new TimeoutException(gadgetName, timeoutMs));
1590
- }, timeoutMs);
1591
- });
1592
- }
1593
- // Execute a gadget call asynchronously
1594
- async execute(call) {
1595
- const startTime = Date.now();
1596
- this.logger.debug("Executing gadget", {
1597
- gadgetName: call.gadgetName,
1598
- invocationId: call.invocationId,
1599
- parameters: call.parameters
1600
- });
1601
- const rawParameters = call.parameters ?? {};
1602
- let validatedParameters = rawParameters;
1603
- try {
1604
- const gadget = this.registry.get(call.gadgetName);
1605
- if (!gadget) {
1606
- this.logger.error("Gadget not found", { gadgetName: call.gadgetName });
1607
- const availableGadgets = this.registry.getNames();
1608
- return {
1609
- gadgetName: call.gadgetName,
1610
- invocationId: call.invocationId,
1611
- parameters: call.parameters ?? {},
1612
- error: this.errorFormatter.formatRegistryError(call.gadgetName, availableGadgets),
1613
- executionTimeMs: Date.now() - startTime
1614
- };
1615
- }
1616
- if (call.parseError || !call.parameters) {
1617
- this.logger.error("Gadget parameter parse error", {
1618
- gadgetName: call.gadgetName,
1619
- parseError: call.parseError,
1620
- rawParameters: call.parametersRaw
1621
- });
1622
- const parseErrorMessage = call.parseError ?? "Failed to parse parameters";
1623
- return {
1624
- gadgetName: call.gadgetName,
1625
- invocationId: call.invocationId,
1626
- parameters: {},
1627
- error: this.errorFormatter.formatParseError(call.gadgetName, parseErrorMessage, gadget),
1628
- executionTimeMs: Date.now() - startTime
1629
- };
1630
- }
1631
- if (gadget.parameterSchema) {
1632
- const validationResult = gadget.parameterSchema.safeParse(rawParameters);
1633
- if (!validationResult.success) {
1634
- const validationError = this.errorFormatter.formatValidationError(
1635
- call.gadgetName,
1636
- validationResult.error,
1637
- gadget
1638
- );
1639
- this.logger.error("Gadget parameter validation failed", {
1640
- gadgetName: call.gadgetName,
1641
- issueCount: validationResult.error.issues.length
1642
- });
1643
- return {
1644
- gadgetName: call.gadgetName,
1645
- invocationId: call.invocationId,
1646
- parameters: rawParameters,
1647
- error: validationError,
1648
- executionTimeMs: Date.now() - startTime
1649
- };
1650
- }
1651
- validatedParameters = validationResult.data;
1652
- }
1653
- const timeoutMs = gadget.timeoutMs ?? this.defaultGadgetTimeoutMs;
1654
- let result;
1655
- if (timeoutMs && timeoutMs > 0) {
1656
- this.logger.debug("Executing gadget with timeout", {
1657
- gadgetName: call.gadgetName,
1658
- timeoutMs
1659
- });
1660
- result = await Promise.race([
1661
- Promise.resolve(gadget.execute(validatedParameters)),
1662
- this.createTimeoutPromise(call.gadgetName, timeoutMs)
1663
- ]);
1664
- } else {
1665
- result = await Promise.resolve(gadget.execute(validatedParameters));
1666
- }
1667
- const executionTimeMs = Date.now() - startTime;
1668
- this.logger.info("Gadget executed successfully", {
1669
- gadgetName: call.gadgetName,
1670
- invocationId: call.invocationId,
1671
- executionTimeMs
1672
- });
1673
- this.logger.debug("Gadget result", {
1674
- gadgetName: call.gadgetName,
1675
- invocationId: call.invocationId,
1676
- parameters: validatedParameters,
1677
- result,
1678
- executionTimeMs
1679
- });
1680
- return {
1681
- gadgetName: call.gadgetName,
1682
- invocationId: call.invocationId,
1683
- parameters: validatedParameters,
1684
- result,
1685
- executionTimeMs
1686
- };
1687
- } catch (error) {
1688
- if (error instanceof BreakLoopException) {
1689
- this.logger.info("Gadget requested loop termination", {
1690
- gadgetName: call.gadgetName,
1691
- message: error.message
1692
- });
1693
- return {
1694
- gadgetName: call.gadgetName,
1695
- invocationId: call.invocationId,
1696
- parameters: validatedParameters,
1697
- result: error.message,
1698
- breaksLoop: true,
1699
- executionTimeMs: Date.now() - startTime
1700
- };
1701
- }
1702
- if (error instanceof TimeoutException) {
1703
- this.logger.error("Gadget execution timed out", {
1704
- gadgetName: call.gadgetName,
1705
- timeoutMs: error.timeoutMs,
1706
- executionTimeMs: Date.now() - startTime
1707
- });
1708
- return {
1709
- gadgetName: call.gadgetName,
1710
- invocationId: call.invocationId,
1711
- parameters: validatedParameters,
1712
- error: error.message,
1713
- executionTimeMs: Date.now() - startTime
1714
- };
1715
- }
1716
- if (error instanceof HumanInputException) {
1717
- this.logger.info("Gadget requested human input", {
1718
- gadgetName: call.gadgetName,
1719
- question: error.question
1720
- });
1721
- if (this.onHumanInputRequired) {
1722
- try {
1723
- const answer = await this.onHumanInputRequired(error.question);
1724
- this.logger.debug("Human input received", {
1725
- gadgetName: call.gadgetName,
1726
- answerLength: answer.length
1727
- });
1728
- return {
1729
- gadgetName: call.gadgetName,
1730
- invocationId: call.invocationId,
1731
- parameters: validatedParameters,
1732
- result: answer,
1733
- executionTimeMs: Date.now() - startTime
1734
- };
1735
- } catch (inputError) {
1736
- this.logger.error("Human input callback error", {
1737
- gadgetName: call.gadgetName,
1738
- error: inputError instanceof Error ? inputError.message : String(inputError)
1739
- });
1740
- return {
1741
- gadgetName: call.gadgetName,
1742
- invocationId: call.invocationId,
1743
- parameters: validatedParameters,
1744
- error: inputError instanceof Error ? inputError.message : String(inputError),
1745
- executionTimeMs: Date.now() - startTime
1746
- };
1747
- }
1748
- }
1749
- this.logger.warn("Human input required but no callback provided", {
1750
- gadgetName: call.gadgetName
1751
- });
1752
- return {
1753
- gadgetName: call.gadgetName,
1754
- invocationId: call.invocationId,
1755
- parameters: validatedParameters,
1756
- error: "Human input required but not available (stdin is not interactive)",
1757
- executionTimeMs: Date.now() - startTime
1758
- };
1759
- }
1760
- const executionTimeMs = Date.now() - startTime;
1761
- this.logger.error("Gadget execution failed", {
1762
- gadgetName: call.gadgetName,
1763
- error: error instanceof Error ? error.message : String(error),
1764
- executionTimeMs
1765
- });
1766
- return {
1767
- gadgetName: call.gadgetName,
1768
- invocationId: call.invocationId,
1769
- parameters: validatedParameters,
1770
- error: error instanceof Error ? error.message : String(error),
1771
- executionTimeMs
1772
- };
1773
- }
1774
- }
1775
- // Execute multiple gadget calls in parallel
1776
- async executeAll(calls) {
1777
- return Promise.all(calls.map((call) => this.execute(call)));
2016
+ /**
2017
+ * Get the primitive type hint from an unwrapped schema.
2018
+ */
2019
+ getBaseType(schema) {
2020
+ const unwrapped = this.unwrapSchema(schema);
2021
+ const typeName = getTypeName(unwrapped);
2022
+ switch (typeName) {
2023
+ // Primitive types
2024
+ case "string":
2025
+ case "ZodString":
2026
+ return "string";
2027
+ case "number":
2028
+ case "ZodNumber":
2029
+ case "bigint":
2030
+ case "ZodBigInt":
2031
+ return "number";
2032
+ case "boolean":
2033
+ case "ZodBoolean":
2034
+ return "boolean";
2035
+ // Literal types - check the literal value type
2036
+ case "literal":
2037
+ case "ZodLiteral": {
2038
+ const def = getDef(unwrapped);
2039
+ const values = def?.values;
2040
+ const value = values?.[0] ?? def?.value;
2041
+ if (typeof value === "string") return "string";
2042
+ if (typeof value === "number" || typeof value === "bigint")
2043
+ return "number";
2044
+ if (typeof value === "boolean") return "boolean";
2045
+ return "unknown";
2046
+ }
2047
+ // Enum - always string keys
2048
+ case "enum":
2049
+ case "ZodEnum":
2050
+ case "nativeEnum":
2051
+ case "ZodNativeEnum":
2052
+ return "string";
2053
+ // Union - return 'unknown' to let auto-coercion decide
2054
+ // Since multiple types are valid, we can't definitively say what the LLM intended
2055
+ // Auto-coercion will handle common cases (numbers, booleans) appropriately
2056
+ case "union":
2057
+ case "ZodUnion":
2058
+ return "unknown";
2059
+ // Discriminated union - complex, return unknown
2060
+ case "discriminatedUnion":
2061
+ case "ZodDiscriminatedUnion":
2062
+ return "unknown";
2063
+ // Intersection - check both sides
2064
+ case "intersection":
2065
+ case "ZodIntersection": {
2066
+ const def = getDef(unwrapped);
2067
+ const left = def?.left;
2068
+ const right = def?.right;
2069
+ if (!left || !right) return "unknown";
2070
+ const leftType = this.getBaseType(left);
2071
+ const rightType = this.getBaseType(right);
2072
+ if (leftType === rightType) return leftType;
2073
+ if (leftType === "string" || rightType === "string") return "string";
2074
+ return "unknown";
2075
+ }
2076
+ // Effects/transforms - return unknown to let Zod handle it
2077
+ case "effects":
2078
+ case "ZodEffects":
2079
+ return "unknown";
2080
+ // Lazy - can't resolve without evaluating
2081
+ case "lazy":
2082
+ case "ZodLazy":
2083
+ return "unknown";
2084
+ // Complex types - return unknown
2085
+ case "object":
2086
+ case "ZodObject":
2087
+ case "array":
2088
+ case "ZodArray":
2089
+ case "tuple":
2090
+ case "ZodTuple":
2091
+ case "record":
2092
+ case "ZodRecord":
2093
+ case "map":
2094
+ case "ZodMap":
2095
+ case "set":
2096
+ case "ZodSet":
2097
+ case "function":
2098
+ case "ZodFunction":
2099
+ case "promise":
2100
+ case "ZodPromise":
2101
+ case "date":
2102
+ case "ZodDate":
2103
+ return "unknown";
2104
+ // Unknown/any/never/void/undefined/null
2105
+ case "unknown":
2106
+ case "ZodUnknown":
2107
+ case "any":
2108
+ case "ZodAny":
2109
+ case "never":
2110
+ case "ZodNever":
2111
+ case "void":
2112
+ case "ZodVoid":
2113
+ case "undefined":
2114
+ case "ZodUndefined":
2115
+ case "null":
2116
+ case "ZodNull":
2117
+ return "unknown";
2118
+ default:
2119
+ return "unknown";
2120
+ }
1778
2121
  }
1779
2122
  };
1780
2123
  }
@@ -1785,6 +2128,7 @@ function parseBlockParams(content, options) {
1785
2128
  const argPrefix = options?.argPrefix ?? GADGET_ARG_PREFIX;
1786
2129
  const result = {};
1787
2130
  const seenPointers = /* @__PURE__ */ new Set();
2131
+ const introspector = options?.schema ? new SchemaIntrospector(options.schema) : void 0;
1788
2132
  const parts = content.split(argPrefix);
1789
2133
  for (let i = 1; i < parts.length; i++) {
1790
2134
  const part = parts[i];
@@ -1796,7 +2140,7 @@ function parseBlockParams(content, options) {
1796
2140
  throw new Error(`Duplicate pointer: ${pointer2}`);
1797
2141
  }
1798
2142
  seenPointers.add(pointer2);
1799
- setByPointer(result, pointer2, "");
2143
+ setByPointer(result, pointer2, "", introspector);
1800
2144
  }
1801
2145
  continue;
1802
2146
  }
@@ -1812,15 +2156,30 @@ function parseBlockParams(content, options) {
1812
2156
  throw new Error(`Duplicate pointer: ${pointer}`);
1813
2157
  }
1814
2158
  seenPointers.add(pointer);
1815
- setByPointer(result, pointer, value);
2159
+ setByPointer(result, pointer, value, introspector);
1816
2160
  }
1817
2161
  return result;
1818
2162
  }
1819
- function coerceValue(value) {
2163
+ function coerceValue(value, expectedType) {
1820
2164
  if (value.includes("\n")) {
1821
2165
  return value;
1822
2166
  }
1823
2167
  const trimmed = value.trim();
2168
+ if (expectedType === "string") {
2169
+ return value;
2170
+ }
2171
+ if (expectedType === "boolean") {
2172
+ if (trimmed === "true") return true;
2173
+ if (trimmed === "false") return false;
2174
+ return value;
2175
+ }
2176
+ if (expectedType === "number") {
2177
+ const num = Number(trimmed);
2178
+ if (!isNaN(num) && isFinite(num) && trimmed !== "") {
2179
+ return num;
2180
+ }
2181
+ return value;
2182
+ }
1824
2183
  if (trimmed === "true") return true;
1825
2184
  if (trimmed === "false") return false;
1826
2185
  if (trimmed !== "" && /^-?\d+(\.\d+)?$/.test(trimmed)) {
@@ -1831,7 +2190,7 @@ function coerceValue(value) {
1831
2190
  }
1832
2191
  return value;
1833
2192
  }
1834
- function setByPointer(obj, pointer, value) {
2193
+ function setByPointer(obj, pointer, value, introspector) {
1835
2194
  const segments = pointer.split("/");
1836
2195
  let current = obj;
1837
2196
  for (let i = 0; i < segments.length - 1; i++) {
@@ -1859,7 +2218,8 @@ function setByPointer(obj, pointer, value) {
1859
2218
  }
1860
2219
  }
1861
2220
  const lastSegment = segments[segments.length - 1];
1862
- const coercedValue = coerceValue(value);
2221
+ const expectedType = introspector?.getTypeAtPath(pointer);
2222
+ const coercedValue = coerceValue(value, expectedType);
1863
2223
  if (Array.isArray(current)) {
1864
2224
  const index = parseInt(lastSegment, 10);
1865
2225
  if (isNaN(index) || index < 0) {
@@ -1877,6 +2237,122 @@ var init_block_params = __esm({
1877
2237
  "src/gadgets/block-params.ts"() {
1878
2238
  "use strict";
1879
2239
  init_constants();
2240
+ init_schema_introspector();
2241
+ }
2242
+ });
2243
+
2244
+ // src/gadgets/error-formatter.ts
2245
+ var GadgetErrorFormatter;
2246
+ var init_error_formatter = __esm({
2247
+ "src/gadgets/error-formatter.ts"() {
2248
+ "use strict";
2249
+ init_constants();
2250
+ GadgetErrorFormatter = class {
2251
+ argPrefix;
2252
+ startPrefix;
2253
+ endPrefix;
2254
+ constructor(options = {}) {
2255
+ this.argPrefix = options.argPrefix ?? GADGET_ARG_PREFIX;
2256
+ this.startPrefix = options.startPrefix ?? GADGET_START_PREFIX;
2257
+ this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
2258
+ }
2259
+ /**
2260
+ * Format a Zod validation error with full gadget instructions.
2261
+ *
2262
+ * @param gadgetName - Name of the gadget that was called
2263
+ * @param zodError - The Zod validation error
2264
+ * @param gadget - The gadget instance (for generating instructions)
2265
+ * @returns Formatted error message with usage instructions
2266
+ */
2267
+ formatValidationError(gadgetName, zodError, gadget) {
2268
+ const parts = [];
2269
+ parts.push(`Error: Invalid parameters for '${gadgetName}':`);
2270
+ for (const issue of zodError.issues) {
2271
+ const path2 = issue.path.join(".") || "root";
2272
+ parts.push(` - ${path2}: ${issue.message}`);
2273
+ }
2274
+ parts.push("");
2275
+ parts.push("Gadget Usage:");
2276
+ parts.push(gadget.getInstruction(this.argPrefix));
2277
+ return parts.join("\n");
2278
+ }
2279
+ /**
2280
+ * Format a parse error with block format reference.
2281
+ *
2282
+ * @param gadgetName - Name of the gadget that was called
2283
+ * @param parseError - The parse error message
2284
+ * @param gadget - The gadget instance if found (for generating instructions)
2285
+ * @returns Formatted error message with format reference
2286
+ */
2287
+ formatParseError(gadgetName, parseError, gadget) {
2288
+ const parts = [];
2289
+ parts.push(`Error: Failed to parse parameters for '${gadgetName}':`);
2290
+ parts.push(` ${parseError}`);
2291
+ if (gadget) {
2292
+ parts.push("");
2293
+ parts.push("Gadget Usage:");
2294
+ parts.push(gadget.getInstruction(this.argPrefix));
2295
+ }
2296
+ parts.push("");
2297
+ parts.push("Block Format Reference:");
2298
+ parts.push(` ${this.startPrefix}${gadgetName}`);
2299
+ parts.push(` ${this.argPrefix}parameterName`);
2300
+ parts.push(" parameter value here");
2301
+ parts.push(` ${this.endPrefix}`);
2302
+ return parts.join("\n");
2303
+ }
2304
+ /**
2305
+ * Format a registry error (gadget not found) with available gadgets list.
2306
+ *
2307
+ * @param gadgetName - Name of the gadget that was not found
2308
+ * @param availableGadgets - List of available gadget names
2309
+ * @returns Formatted error message with available gadgets
2310
+ */
2311
+ formatRegistryError(gadgetName, availableGadgets) {
2312
+ const parts = [];
2313
+ parts.push(`Error: Gadget '${gadgetName}' not found.`);
2314
+ if (availableGadgets.length > 0) {
2315
+ parts.push("");
2316
+ parts.push(`Available gadgets: ${availableGadgets.join(", ")}`);
2317
+ } else {
2318
+ parts.push("");
2319
+ parts.push("No gadgets are currently registered.");
2320
+ }
2321
+ return parts.join("\n");
2322
+ }
2323
+ };
2324
+ }
2325
+ });
2326
+
2327
+ // src/gadgets/exceptions.ts
2328
+ var BreakLoopException, HumanInputException, TimeoutException;
2329
+ var init_exceptions = __esm({
2330
+ "src/gadgets/exceptions.ts"() {
2331
+ "use strict";
2332
+ BreakLoopException = class extends Error {
2333
+ constructor(message) {
2334
+ super(message ?? "Agent loop terminated by gadget");
2335
+ this.name = "BreakLoopException";
2336
+ }
2337
+ };
2338
+ HumanInputException = class extends Error {
2339
+ question;
2340
+ constructor(question) {
2341
+ super(`Human input required: ${question}`);
2342
+ this.name = "HumanInputException";
2343
+ this.question = question;
2344
+ }
2345
+ };
2346
+ TimeoutException = class extends Error {
2347
+ timeoutMs;
2348
+ gadgetName;
2349
+ constructor(gadgetName, timeoutMs) {
2350
+ super(`Gadget '${gadgetName}' execution exceeded timeout of ${timeoutMs}ms`);
2351
+ this.name = "TimeoutException";
2352
+ this.gadgetName = gadgetName;
2353
+ this.timeoutMs = timeoutMs;
2354
+ }
2355
+ };
1880
2356
  }
1881
2357
  });
1882
2358
 
@@ -1994,64 +2470,341 @@ var init_parser = __esm({
1994
2470
  break;
1995
2471
  }
1996
2472
  }
1997
- const parametersRaw = this.buffer.substring(contentStartIndex, partEndIndex).trim();
1998
- const { parameters, parseError } = this.parseParameters(parametersRaw);
1999
- yield {
2000
- type: "gadget_call",
2001
- call: {
2002
- gadgetName: actualGadgetName,
2003
- invocationId,
2004
- parametersRaw,
2005
- parameters,
2006
- parseError
2007
- }
2473
+ const parametersRaw = this.buffer.substring(contentStartIndex, partEndIndex).trim();
2474
+ const { parameters, parseError } = this.parseParameters(parametersRaw);
2475
+ yield {
2476
+ type: "gadget_call",
2477
+ call: {
2478
+ gadgetName: actualGadgetName,
2479
+ invocationId,
2480
+ parametersRaw,
2481
+ parameters,
2482
+ parseError
2483
+ }
2484
+ };
2485
+ startIndex = partEndIndex + endMarkerLength;
2486
+ this.lastReportedTextLength = startIndex;
2487
+ }
2488
+ if (startIndex > 0) {
2489
+ this.buffer = this.buffer.substring(startIndex);
2490
+ this.lastReportedTextLength = 0;
2491
+ }
2492
+ }
2493
+ // Finalize parsing and return remaining text or incomplete gadgets
2494
+ *finalize() {
2495
+ const startIndex = this.buffer.indexOf(this.startPrefix, this.lastReportedTextLength);
2496
+ if (startIndex !== -1) {
2497
+ const textBefore = this.takeTextUntil(startIndex);
2498
+ if (textBefore !== void 0) {
2499
+ yield { type: "text", content: textBefore };
2500
+ }
2501
+ const metadataStartIndex = startIndex + this.startPrefix.length;
2502
+ const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
2503
+ if (metadataEndIndex !== -1) {
2504
+ const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
2505
+ const { actualName: actualGadgetName, invocationId } = this.parseGadgetName(gadgetName);
2506
+ const contentStartIndex = metadataEndIndex + 1;
2507
+ const parametersRaw = this.buffer.substring(contentStartIndex).trim();
2508
+ const { parameters, parseError } = this.parseParameters(parametersRaw);
2509
+ yield {
2510
+ type: "gadget_call",
2511
+ call: {
2512
+ gadgetName: actualGadgetName,
2513
+ invocationId,
2514
+ parametersRaw,
2515
+ parameters,
2516
+ parseError
2517
+ }
2518
+ };
2519
+ return;
2520
+ }
2521
+ }
2522
+ const remainingText = this.takeTextUntil(this.buffer.length);
2523
+ if (remainingText !== void 0) {
2524
+ yield { type: "text", content: remainingText };
2525
+ }
2526
+ }
2527
+ // Reset parser state (note: global invocation counter is NOT reset to ensure unique IDs)
2528
+ reset() {
2529
+ this.buffer = "";
2530
+ this.lastReportedTextLength = 0;
2531
+ }
2532
+ };
2533
+ }
2534
+ });
2535
+
2536
+ // src/gadgets/executor.ts
2537
+ var GadgetExecutor;
2538
+ var init_executor = __esm({
2539
+ "src/gadgets/executor.ts"() {
2540
+ "use strict";
2541
+ init_constants();
2542
+ init_logger();
2543
+ init_block_params();
2544
+ init_error_formatter();
2545
+ init_exceptions();
2546
+ init_parser();
2547
+ GadgetExecutor = class {
2548
+ constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions) {
2549
+ this.registry = registry;
2550
+ this.onHumanInputRequired = onHumanInputRequired;
2551
+ this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
2552
+ this.logger = logger ?? createLogger({ name: "llmist:executor" });
2553
+ this.errorFormatter = new GadgetErrorFormatter(errorFormatterOptions);
2554
+ this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
2555
+ }
2556
+ logger;
2557
+ errorFormatter;
2558
+ argPrefix;
2559
+ /**
2560
+ * Creates a promise that rejects with a TimeoutException after the specified timeout.
2561
+ */
2562
+ createTimeoutPromise(gadgetName, timeoutMs) {
2563
+ return new Promise((_, reject) => {
2564
+ setTimeout(() => {
2565
+ reject(new TimeoutException(gadgetName, timeoutMs));
2566
+ }, timeoutMs);
2567
+ });
2568
+ }
2569
+ // Execute a gadget call asynchronously
2570
+ async execute(call) {
2571
+ const startTime = Date.now();
2572
+ this.logger.debug("Executing gadget", {
2573
+ gadgetName: call.gadgetName,
2574
+ invocationId: call.invocationId,
2575
+ parameters: call.parameters
2576
+ });
2577
+ const rawParameters = call.parameters ?? {};
2578
+ let validatedParameters = rawParameters;
2579
+ try {
2580
+ const gadget = this.registry.get(call.gadgetName);
2581
+ if (!gadget) {
2582
+ this.logger.error("Gadget not found", { gadgetName: call.gadgetName });
2583
+ const availableGadgets = this.registry.getNames();
2584
+ return {
2585
+ gadgetName: call.gadgetName,
2586
+ invocationId: call.invocationId,
2587
+ parameters: call.parameters ?? {},
2588
+ error: this.errorFormatter.formatRegistryError(call.gadgetName, availableGadgets),
2589
+ executionTimeMs: Date.now() - startTime
2590
+ };
2591
+ }
2592
+ if (call.parseError || !call.parameters) {
2593
+ this.logger.error("Gadget parameter parse error", {
2594
+ gadgetName: call.gadgetName,
2595
+ parseError: call.parseError,
2596
+ rawParameters: call.parametersRaw
2597
+ });
2598
+ const parseErrorMessage = call.parseError ?? "Failed to parse parameters";
2599
+ return {
2600
+ gadgetName: call.gadgetName,
2601
+ invocationId: call.invocationId,
2602
+ parameters: {},
2603
+ error: this.errorFormatter.formatParseError(call.gadgetName, parseErrorMessage, gadget),
2604
+ executionTimeMs: Date.now() - startTime
2605
+ };
2606
+ }
2607
+ let schemaAwareParameters = rawParameters;
2608
+ const hasBlockFormat = call.parametersRaw?.includes(this.argPrefix);
2609
+ if (gadget.parameterSchema && hasBlockFormat) {
2610
+ try {
2611
+ const cleanedRaw = stripMarkdownFences(call.parametersRaw);
2612
+ const initialParse = parseBlockParams(cleanedRaw, { argPrefix: this.argPrefix });
2613
+ const parametersWereModified = !this.deepEquals(rawParameters, initialParse);
2614
+ if (parametersWereModified) {
2615
+ this.logger.debug("Parameters modified by interceptor, skipping re-parse", {
2616
+ gadgetName: call.gadgetName
2617
+ });
2618
+ schemaAwareParameters = rawParameters;
2619
+ } else {
2620
+ schemaAwareParameters = parseBlockParams(cleanedRaw, {
2621
+ argPrefix: this.argPrefix,
2622
+ schema: gadget.parameterSchema
2623
+ });
2624
+ this.logger.debug("Re-parsed parameters with schema", {
2625
+ gadgetName: call.gadgetName,
2626
+ original: rawParameters,
2627
+ schemaAware: schemaAwareParameters
2628
+ });
2629
+ }
2630
+ } catch (error) {
2631
+ this.logger.warn("Schema-aware re-parsing failed, using original parameters", {
2632
+ gadgetName: call.gadgetName,
2633
+ error: error instanceof Error ? error.message : String(error)
2634
+ });
2635
+ schemaAwareParameters = rawParameters;
2636
+ }
2637
+ }
2638
+ if (gadget.parameterSchema) {
2639
+ const validationResult = gadget.parameterSchema.safeParse(schemaAwareParameters);
2640
+ if (!validationResult.success) {
2641
+ const validationError = this.errorFormatter.formatValidationError(
2642
+ call.gadgetName,
2643
+ validationResult.error,
2644
+ gadget
2645
+ );
2646
+ this.logger.error("Gadget parameter validation failed", {
2647
+ gadgetName: call.gadgetName,
2648
+ issueCount: validationResult.error.issues.length
2649
+ });
2650
+ return {
2651
+ gadgetName: call.gadgetName,
2652
+ invocationId: call.invocationId,
2653
+ parameters: schemaAwareParameters,
2654
+ error: validationError,
2655
+ executionTimeMs: Date.now() - startTime
2656
+ };
2657
+ }
2658
+ validatedParameters = validationResult.data;
2659
+ } else {
2660
+ validatedParameters = schemaAwareParameters;
2661
+ }
2662
+ const timeoutMs = gadget.timeoutMs ?? this.defaultGadgetTimeoutMs;
2663
+ let result;
2664
+ if (timeoutMs && timeoutMs > 0) {
2665
+ this.logger.debug("Executing gadget with timeout", {
2666
+ gadgetName: call.gadgetName,
2667
+ timeoutMs
2668
+ });
2669
+ result = await Promise.race([
2670
+ Promise.resolve(gadget.execute(validatedParameters)),
2671
+ this.createTimeoutPromise(call.gadgetName, timeoutMs)
2672
+ ]);
2673
+ } else {
2674
+ result = await Promise.resolve(gadget.execute(validatedParameters));
2675
+ }
2676
+ const executionTimeMs = Date.now() - startTime;
2677
+ this.logger.info("Gadget executed successfully", {
2678
+ gadgetName: call.gadgetName,
2679
+ invocationId: call.invocationId,
2680
+ executionTimeMs
2681
+ });
2682
+ this.logger.debug("Gadget result", {
2683
+ gadgetName: call.gadgetName,
2684
+ invocationId: call.invocationId,
2685
+ parameters: validatedParameters,
2686
+ result,
2687
+ executionTimeMs
2688
+ });
2689
+ return {
2690
+ gadgetName: call.gadgetName,
2691
+ invocationId: call.invocationId,
2692
+ parameters: validatedParameters,
2693
+ result,
2694
+ executionTimeMs
2008
2695
  };
2009
- startIndex = partEndIndex + endMarkerLength;
2010
- this.lastReportedTextLength = startIndex;
2011
- }
2012
- if (startIndex > 0) {
2013
- this.buffer = this.buffer.substring(startIndex);
2014
- this.lastReportedTextLength = 0;
2015
- }
2016
- }
2017
- // Finalize parsing and return remaining text or incomplete gadgets
2018
- *finalize() {
2019
- const startIndex = this.buffer.indexOf(this.startPrefix, this.lastReportedTextLength);
2020
- if (startIndex !== -1) {
2021
- const textBefore = this.takeTextUntil(startIndex);
2022
- if (textBefore !== void 0) {
2023
- yield { type: "text", content: textBefore };
2696
+ } catch (error) {
2697
+ if (error instanceof BreakLoopException) {
2698
+ this.logger.info("Gadget requested loop termination", {
2699
+ gadgetName: call.gadgetName,
2700
+ message: error.message
2701
+ });
2702
+ return {
2703
+ gadgetName: call.gadgetName,
2704
+ invocationId: call.invocationId,
2705
+ parameters: validatedParameters,
2706
+ result: error.message,
2707
+ breaksLoop: true,
2708
+ executionTimeMs: Date.now() - startTime
2709
+ };
2024
2710
  }
2025
- const metadataStartIndex = startIndex + this.startPrefix.length;
2026
- const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
2027
- if (metadataEndIndex !== -1) {
2028
- const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
2029
- const { actualName: actualGadgetName, invocationId } = this.parseGadgetName(gadgetName);
2030
- const contentStartIndex = metadataEndIndex + 1;
2031
- const parametersRaw = this.buffer.substring(contentStartIndex).trim();
2032
- const { parameters, parseError } = this.parseParameters(parametersRaw);
2033
- yield {
2034
- type: "gadget_call",
2035
- call: {
2036
- gadgetName: actualGadgetName,
2037
- invocationId,
2038
- parametersRaw,
2039
- parameters,
2040
- parseError
2711
+ if (error instanceof TimeoutException) {
2712
+ this.logger.error("Gadget execution timed out", {
2713
+ gadgetName: call.gadgetName,
2714
+ timeoutMs: error.timeoutMs,
2715
+ executionTimeMs: Date.now() - startTime
2716
+ });
2717
+ return {
2718
+ gadgetName: call.gadgetName,
2719
+ invocationId: call.invocationId,
2720
+ parameters: validatedParameters,
2721
+ error: error.message,
2722
+ executionTimeMs: Date.now() - startTime
2723
+ };
2724
+ }
2725
+ if (error instanceof HumanInputException) {
2726
+ this.logger.info("Gadget requested human input", {
2727
+ gadgetName: call.gadgetName,
2728
+ question: error.question
2729
+ });
2730
+ if (this.onHumanInputRequired) {
2731
+ try {
2732
+ const answer = await this.onHumanInputRequired(error.question);
2733
+ this.logger.debug("Human input received", {
2734
+ gadgetName: call.gadgetName,
2735
+ answerLength: answer.length
2736
+ });
2737
+ return {
2738
+ gadgetName: call.gadgetName,
2739
+ invocationId: call.invocationId,
2740
+ parameters: validatedParameters,
2741
+ result: answer,
2742
+ executionTimeMs: Date.now() - startTime
2743
+ };
2744
+ } catch (inputError) {
2745
+ this.logger.error("Human input callback error", {
2746
+ gadgetName: call.gadgetName,
2747
+ error: inputError instanceof Error ? inputError.message : String(inputError)
2748
+ });
2749
+ return {
2750
+ gadgetName: call.gadgetName,
2751
+ invocationId: call.invocationId,
2752
+ parameters: validatedParameters,
2753
+ error: inputError instanceof Error ? inputError.message : String(inputError),
2754
+ executionTimeMs: Date.now() - startTime
2755
+ };
2041
2756
  }
2757
+ }
2758
+ this.logger.warn("Human input required but no callback provided", {
2759
+ gadgetName: call.gadgetName
2760
+ });
2761
+ return {
2762
+ gadgetName: call.gadgetName,
2763
+ invocationId: call.invocationId,
2764
+ parameters: validatedParameters,
2765
+ error: "Human input required but not available (stdin is not interactive)",
2766
+ executionTimeMs: Date.now() - startTime
2042
2767
  };
2043
- return;
2044
2768
  }
2045
- }
2046
- const remainingText = this.takeTextUntil(this.buffer.length);
2047
- if (remainingText !== void 0) {
2048
- yield { type: "text", content: remainingText };
2769
+ const executionTimeMs = Date.now() - startTime;
2770
+ this.logger.error("Gadget execution failed", {
2771
+ gadgetName: call.gadgetName,
2772
+ error: error instanceof Error ? error.message : String(error),
2773
+ executionTimeMs
2774
+ });
2775
+ return {
2776
+ gadgetName: call.gadgetName,
2777
+ invocationId: call.invocationId,
2778
+ parameters: validatedParameters,
2779
+ error: error instanceof Error ? error.message : String(error),
2780
+ executionTimeMs
2781
+ };
2049
2782
  }
2050
2783
  }
2051
- // Reset parser state (note: global invocation counter is NOT reset to ensure unique IDs)
2052
- reset() {
2053
- this.buffer = "";
2054
- this.lastReportedTextLength = 0;
2784
+ // Execute multiple gadget calls in parallel
2785
+ async executeAll(calls) {
2786
+ return Promise.all(calls.map((call) => this.execute(call)));
2787
+ }
2788
+ /**
2789
+ * Deep equality check for objects/arrays.
2790
+ * Used to detect if parameters were modified by an interceptor.
2791
+ */
2792
+ deepEquals(a, b) {
2793
+ if (a === b) return true;
2794
+ if (a === null || b === null) return a === b;
2795
+ if (typeof a !== typeof b) return false;
2796
+ if (typeof a !== "object") return a === b;
2797
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
2798
+ if (Array.isArray(a) && Array.isArray(b)) {
2799
+ if (a.length !== b.length) return false;
2800
+ return a.every((val, i) => this.deepEquals(val, b[i]));
2801
+ }
2802
+ const aObj = a;
2803
+ const bObj = b;
2804
+ const aKeys = Object.keys(aObj);
2805
+ const bKeys = Object.keys(bObj);
2806
+ if (aKeys.length !== bKeys.length) return false;
2807
+ return aKeys.every((key) => this.deepEquals(aObj[key], bObj[key]));
2055
2808
  }
2056
2809
  };
2057
2810
  }
@@ -2094,7 +2847,8 @@ var init_stream_processor = __esm({
2094
2847
  options.registry,
2095
2848
  options.onHumanInputRequired,
2096
2849
  this.logger.getSubLogger({ name: "executor" }),
2097
- options.defaultGadgetTimeoutMs
2850
+ options.defaultGadgetTimeoutMs,
2851
+ { argPrefix: options.gadgetArgPrefix }
2098
2852
  );
2099
2853
  }
2100
2854
  /**
@@ -2463,6 +3217,7 @@ var init_agent = __esm({
2463
3217
  init_model_shortcuts();
2464
3218
  init_output_viewer();
2465
3219
  init_logger();
3220
+ init_manager();
2466
3221
  init_gadget_output_store();
2467
3222
  init_agent_internal_key();
2468
3223
  init_conversation_manager();
@@ -2493,6 +3248,8 @@ var init_agent = __esm({
2493
3248
  outputStore;
2494
3249
  outputLimitEnabled;
2495
3250
  outputLimitCharLimit;
3251
+ // Context compaction
3252
+ compactionManager;
2496
3253
  /**
2497
3254
  * Creates a new Agent instance.
2498
3255
  * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
@@ -2552,6 +3309,14 @@ var init_agent = __esm({
2552
3309
  if (options.userPrompt) {
2553
3310
  this.conversation.addUserMessage(options.userPrompt);
2554
3311
  }
3312
+ const compactionEnabled = options.compactionConfig?.enabled ?? true;
3313
+ if (compactionEnabled) {
3314
+ this.compactionManager = new CompactionManager(
3315
+ this.client,
3316
+ this.model,
3317
+ options.compactionConfig
3318
+ );
3319
+ }
2555
3320
  }
2556
3321
  /**
2557
3322
  * Get the gadget registry for this agent.
@@ -2574,6 +3339,53 @@ var init_agent = __esm({
2574
3339
  getRegistry() {
2575
3340
  return this.registry;
2576
3341
  }
3342
+ /**
3343
+ * Manually trigger context compaction.
3344
+ *
3345
+ * Forces compaction regardless of threshold. Useful for:
3346
+ * - Pre-emptive context management before expected long operations
3347
+ * - Testing compaction behavior
3348
+ *
3349
+ * @returns CompactionEvent if compaction was performed, null if not configured or no history
3350
+ *
3351
+ * @example
3352
+ * ```typescript
3353
+ * const agent = await LLMist.createAgent()
3354
+ * .withModel('sonnet')
3355
+ * .withCompaction()
3356
+ * .ask('...');
3357
+ *
3358
+ * // Manually compact before a long operation
3359
+ * const event = await agent.compact();
3360
+ * if (event) {
3361
+ * console.log(`Saved ${event.tokensBefore - event.tokensAfter} tokens`);
3362
+ * }
3363
+ * ```
3364
+ */
3365
+ async compact() {
3366
+ if (!this.compactionManager) {
3367
+ return null;
3368
+ }
3369
+ return this.compactionManager.compact(this.conversation, -1);
3370
+ }
3371
+ /**
3372
+ * Get compaction statistics.
3373
+ *
3374
+ * @returns CompactionStats if compaction is enabled, null otherwise
3375
+ *
3376
+ * @example
3377
+ * ```typescript
3378
+ * const stats = agent.getCompactionStats();
3379
+ * if (stats) {
3380
+ * console.log(`Total compactions: ${stats.totalCompactions}`);
3381
+ * console.log(`Tokens saved: ${stats.totalTokensSaved}`);
3382
+ * console.log(`Current usage: ${stats.currentUsage.percent.toFixed(1)}%`);
3383
+ * }
3384
+ * ```
3385
+ */
3386
+ getCompactionStats() {
3387
+ return this.compactionManager?.getStats() ?? null;
3388
+ }
2577
3389
  /**
2578
3390
  * Run the agent loop.
2579
3391
  * Clean, simple orchestration - all complexity is in StreamProcessor.
@@ -2594,6 +3406,30 @@ var init_agent = __esm({
2594
3406
  while (currentIteration < this.maxIterations) {
2595
3407
  this.logger.debug("Starting iteration", { iteration: currentIteration });
2596
3408
  try {
3409
+ if (this.compactionManager) {
3410
+ const compactionEvent = await this.compactionManager.checkAndCompact(
3411
+ this.conversation,
3412
+ currentIteration
3413
+ );
3414
+ if (compactionEvent) {
3415
+ this.logger.info("Context compacted", {
3416
+ strategy: compactionEvent.strategy,
3417
+ tokensBefore: compactionEvent.tokensBefore,
3418
+ tokensAfter: compactionEvent.tokensAfter
3419
+ });
3420
+ yield { type: "compaction", event: compactionEvent };
3421
+ await this.safeObserve(async () => {
3422
+ if (this.hooks.observers?.onCompaction) {
3423
+ await this.hooks.observers.onCompaction({
3424
+ iteration: currentIteration,
3425
+ event: compactionEvent,
3426
+ stats: this.compactionManager.getStats(),
3427
+ logger: this.logger
3428
+ });
3429
+ }
3430
+ });
3431
+ }
3432
+ }
2597
3433
  let llmOptions = {
2598
3434
  model: this.model,
2599
3435
  messages: this.conversation.getMessages(),
@@ -2613,6 +3449,7 @@ var init_agent = __esm({
2613
3449
  if (this.hooks.controllers?.beforeLLMCall) {
2614
3450
  const context = {
2615
3451
  iteration: currentIteration,
3452
+ maxIterations: this.maxIterations,
2616
3453
  options: llmOptions,
2617
3454
  logger: this.logger
2618
3455
  };
@@ -2677,12 +3514,17 @@ var init_agent = __esm({
2677
3514
  });
2678
3515
  let finalMessage = result.finalMessage;
2679
3516
  if (this.hooks.controllers?.afterLLMCall) {
3517
+ const gadgetCallCount = result.outputs.filter(
3518
+ (output) => output.type === "gadget_result"
3519
+ ).length;
2680
3520
  const context = {
2681
3521
  iteration: currentIteration,
3522
+ maxIterations: this.maxIterations,
2682
3523
  options: llmOptions,
2683
3524
  finishReason: result.finishReason,
2684
3525
  usage: result.usage,
2685
3526
  finalMessage: result.finalMessage,
3527
+ gadgetCallCount,
2686
3528
  logger: this.logger
2687
3529
  };
2688
3530
  const action = await this.hooks.controllers.afterLLMCall(context);
@@ -4917,6 +5759,7 @@ var init_builder = __esm({
4917
5759
  defaultGadgetTimeoutMs;
4918
5760
  gadgetOutputLimit;
4919
5761
  gadgetOutputLimitPercent;
5762
+ compactionConfig;
4920
5763
  constructor(client) {
4921
5764
  this.client = client;
4922
5765
  }
@@ -5312,6 +6155,57 @@ var init_builder = __esm({
5312
6155
  this.gadgetOutputLimitPercent = percent;
5313
6156
  return this;
5314
6157
  }
6158
+ /**
6159
+ * Configure context compaction.
6160
+ *
6161
+ * Context compaction automatically manages conversation history to prevent
6162
+ * context window overflow in long-running agent conversations.
6163
+ *
6164
+ * @param config - Compaction configuration options
6165
+ * @returns This builder for chaining
6166
+ *
6167
+ * @example
6168
+ * ```typescript
6169
+ * // Custom thresholds
6170
+ * .withCompaction({
6171
+ * triggerThresholdPercent: 70,
6172
+ * targetPercent: 40,
6173
+ * preserveRecentTurns: 10,
6174
+ * })
6175
+ *
6176
+ * // Different strategy
6177
+ * .withCompaction({
6178
+ * strategy: 'sliding-window',
6179
+ * })
6180
+ *
6181
+ * // With callback
6182
+ * .withCompaction({
6183
+ * onCompaction: (event) => {
6184
+ * console.log(`Saved ${event.tokensBefore - event.tokensAfter} tokens`);
6185
+ * }
6186
+ * })
6187
+ * ```
6188
+ */
6189
+ withCompaction(config) {
6190
+ this.compactionConfig = { ...config, enabled: config.enabled ?? true };
6191
+ return this;
6192
+ }
6193
+ /**
6194
+ * Disable context compaction.
6195
+ *
6196
+ * By default, compaction is enabled. Use this method to explicitly disable it.
6197
+ *
6198
+ * @returns This builder for chaining
6199
+ *
6200
+ * @example
6201
+ * ```typescript
6202
+ * .withoutCompaction() // Disable automatic compaction
6203
+ * ```
6204
+ */
6205
+ withoutCompaction() {
6206
+ this.compactionConfig = { enabled: false };
6207
+ return this;
6208
+ }
5315
6209
  /**
5316
6210
  * Add a synthetic gadget call to the conversation history.
5317
6211
  *
@@ -5427,7 +6321,8 @@ ${endPrefix}`
5427
6321
  shouldContinueAfterError: this.shouldContinueAfterError,
5428
6322
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
5429
6323
  gadgetOutputLimit: this.gadgetOutputLimit,
5430
- gadgetOutputLimitPercent: this.gadgetOutputLimitPercent
6324
+ gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
6325
+ compactionConfig: this.compactionConfig
5431
6326
  };
5432
6327
  return new Agent(AGENT_INTERNAL_KEY, options);
5433
6328
  }
@@ -5529,7 +6424,8 @@ ${endPrefix}`
5529
6424
  shouldContinueAfterError: this.shouldContinueAfterError,
5530
6425
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
5531
6426
  gadgetOutputLimit: this.gadgetOutputLimit,
5532
- gadgetOutputLimitPercent: this.gadgetOutputLimitPercent
6427
+ gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
6428
+ compactionConfig: this.compactionConfig
5533
6429
  };
5534
6430
  return new Agent(AGENT_INTERNAL_KEY, options);
5535
6431
  }
@@ -5588,7 +6484,7 @@ var import_commander2 = require("commander");
5588
6484
  // package.json
5589
6485
  var package_default = {
5590
6486
  name: "llmist",
5591
- version: "1.0.0",
6487
+ version: "1.2.0",
5592
6488
  description: "Universal TypeScript LLM client with streaming-first agent framework. Works with any model - no structured outputs or native tool calling required. Implements its own flexible grammar for function calling.",
5593
6489
  type: "module",
5594
6490
  main: "dist/index.cjs",
@@ -5735,26 +6631,16 @@ var askUser = createGadget({
5735
6631
  });
5736
6632
  var tellUser = createGadget({
5737
6633
  name: "TellUser",
5738
- description: "Tell the user something important. Set done=true when your work is complete and you want to end the conversation.",
6634
+ description: "Tell the user something important.",
5739
6635
  schema: import_zod2.z.object({
5740
6636
  message: import_zod2.z.string().optional().describe("The message to display to the user in Markdown"),
5741
- done: import_zod2.z.boolean().default(false).describe("Set to true to end the conversation, false to continue"),
5742
6637
  type: import_zod2.z.enum(["info", "success", "warning", "error"]).default("info").describe("Message type: info, success, warning, or error")
5743
6638
  }),
5744
6639
  examples: [
5745
6640
  {
5746
- comment: "Report successful completion and end the conversation",
5747
- params: {
5748
- message: "I've completed the refactoring. All tests pass.",
5749
- done: true,
5750
- type: "success"
5751
- }
5752
- },
5753
- {
5754
- comment: "Warn the user about something without ending",
6641
+ comment: "Warn the user about something",
5755
6642
  params: {
5756
6643
  message: "Found 3 files with potential issues. Continuing analysis...",
5757
- done: false,
5758
6644
  type: "warning"
5759
6645
  }
5760
6646
  },
@@ -5762,12 +6648,11 @@ var tellUser = createGadget({
5762
6648
  comment: "Share detailed analysis with bullet points (use heredoc for multiline)",
5763
6649
  params: {
5764
6650
  message: "Here's what I found in the codebase:\n\n1. **Main entry point**: `src/index.ts` exports all public APIs\n2. **Core logic**: Located in `src/core/` with 5 modules\n3. **Tests**: Good coverage in `src/__tests__/`\n\nI'll continue exploring the core modules.",
5765
- done: false,
5766
6651
  type: "info"
5767
6652
  }
5768
6653
  }
5769
6654
  ],
5770
- execute: ({ message, done, type }) => {
6655
+ execute: ({ message, type }) => {
5771
6656
  if (!message || message.trim() === "") {
5772
6657
  return "\u26A0\uFE0F TellUser was called without a message. Please provide content in the 'message' field.";
5773
6658
  }
@@ -5777,14 +6662,24 @@ var tellUser = createGadget({
5777
6662
  warning: "\u26A0\uFE0F ",
5778
6663
  error: "\u274C "
5779
6664
  };
5780
- const plainResult = prefixes[type] + message;
5781
- if (done) {
5782
- throw new BreakLoopException(plainResult);
6665
+ return prefixes[type] + message;
6666
+ }
6667
+ });
6668
+ var finish = createGadget({
6669
+ name: "Finish",
6670
+ description: "Signal that you have completed your task. Call this when your work is done.",
6671
+ schema: import_zod2.z.object({}),
6672
+ examples: [
6673
+ {
6674
+ comment: "Signal task completion",
6675
+ params: {}
5783
6676
  }
5784
- return plainResult;
6677
+ ],
6678
+ execute: () => {
6679
+ throw new BreakLoopException("Task completed");
5785
6680
  }
5786
6681
  });
5787
- var builtinGadgets = [askUser, tellUser];
6682
+ var builtinGadgets = [askUser, tellUser, finish];
5788
6683
 
5789
6684
  // src/cli/gadgets.ts
5790
6685
  var import_node_fs2 = __toESM(require("fs"), 1);
@@ -6340,6 +7235,17 @@ var StreamProgress = class {
6340
7235
  } else {
6341
7236
  parts.push(iterPart);
6342
7237
  }
7238
+ const usagePercent = this.getContextUsagePercent();
7239
+ if (usagePercent !== null) {
7240
+ const formatted = `${Math.round(usagePercent)}%`;
7241
+ if (usagePercent >= 80) {
7242
+ parts.push(import_chalk2.default.red(formatted));
7243
+ } else if (usagePercent >= 50) {
7244
+ parts.push(import_chalk2.default.yellow(formatted));
7245
+ } else {
7246
+ parts.push(import_chalk2.default.green(formatted));
7247
+ }
7248
+ }
6343
7249
  if (this.callInputTokens > 0) {
6344
7250
  const prefix = this.callInputTokensEstimated ? "~" : "";
6345
7251
  parts.push(import_chalk2.default.dim("\u2191") + import_chalk2.default.yellow(` ${prefix}${formatTokens(this.callInputTokens)}`));
@@ -6375,6 +7281,21 @@ var StreamProgress = class {
6375
7281
  return 0;
6376
7282
  }
6377
7283
  }
7284
+ /**
7285
+ * Calculates context window usage percentage.
7286
+ * Returns null if model is unknown or context window unavailable.
7287
+ */
7288
+ getContextUsagePercent() {
7289
+ if (!this.modelRegistry || !this.model || this.callInputTokens === 0) {
7290
+ return null;
7291
+ }
7292
+ const modelName = this.model.includes(":") ? this.model.split(":")[1] : this.model;
7293
+ const limits = this.modelRegistry.getModelLimits(modelName);
7294
+ if (!limits?.contextWindow) {
7295
+ return null;
7296
+ }
7297
+ return this.callInputTokens / limits.contextWindow * 100;
7298
+ }
6378
7299
  renderCumulativeMode(spinner) {
6379
7300
  const elapsed = ((Date.now() - this.totalStartTime) / 1e3).toFixed(1);
6380
7301
  const parts = [];
@@ -7002,7 +7923,9 @@ function resolveTemplate(eta, template, context = {}, configPath) {
7002
7923
  try {
7003
7924
  const fullContext = {
7004
7925
  ...context,
7005
- env: process.env
7926
+ env: process.env,
7927
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
7928
+ // "2025-12-01"
7006
7929
  };
7007
7930
  return eta.renderString(template, fullContext);
7008
7931
  } catch (error) {