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.
@@ -1112,6 +1112,417 @@ var init_output_viewer = __esm({
1112
1112
  }
1113
1113
  });
1114
1114
 
1115
+ // src/agent/compaction/config.ts
1116
+ function resolveCompactionConfig(config = {}) {
1117
+ const trigger = config.triggerThresholdPercent ?? DEFAULT_COMPACTION_CONFIG.triggerThresholdPercent;
1118
+ const target = config.targetPercent ?? DEFAULT_COMPACTION_CONFIG.targetPercent;
1119
+ if (target >= trigger) {
1120
+ console.warn(
1121
+ `[llmist/compaction] targetPercent (${target}) should be less than triggerThresholdPercent (${trigger}) to be effective.`
1122
+ );
1123
+ }
1124
+ const strategy = config.strategy ?? DEFAULT_COMPACTION_CONFIG.strategy;
1125
+ const strategyName = typeof strategy === "object" && "name" in strategy ? strategy.name : strategy;
1126
+ return {
1127
+ enabled: config.enabled ?? DEFAULT_COMPACTION_CONFIG.enabled,
1128
+ strategy: strategyName,
1129
+ triggerThresholdPercent: trigger,
1130
+ targetPercent: target,
1131
+ preserveRecentTurns: config.preserveRecentTurns ?? DEFAULT_COMPACTION_CONFIG.preserveRecentTurns,
1132
+ summarizationModel: config.summarizationModel,
1133
+ summarizationPrompt: config.summarizationPrompt ?? DEFAULT_SUMMARIZATION_PROMPT,
1134
+ onCompaction: config.onCompaction
1135
+ };
1136
+ }
1137
+ var DEFAULT_COMPACTION_CONFIG, DEFAULT_SUMMARIZATION_PROMPT;
1138
+ var init_config = __esm({
1139
+ "src/agent/compaction/config.ts"() {
1140
+ "use strict";
1141
+ DEFAULT_COMPACTION_CONFIG = {
1142
+ enabled: true,
1143
+ strategy: "hybrid",
1144
+ triggerThresholdPercent: 80,
1145
+ targetPercent: 50,
1146
+ preserveRecentTurns: 5
1147
+ };
1148
+ DEFAULT_SUMMARIZATION_PROMPT = `Summarize this conversation history concisely, preserving:
1149
+ 1. Key decisions made and their rationale
1150
+ 2. Important facts and data discovered
1151
+ 3. Errors encountered and how they were resolved
1152
+ 4. Current task context and goals
1153
+
1154
+ Format as a brief narrative paragraph, not bullet points.
1155
+ Previous conversation:`;
1156
+ }
1157
+ });
1158
+
1159
+ // src/agent/compaction/strategy.ts
1160
+ function groupIntoTurns(messages) {
1161
+ const turns = [];
1162
+ let currentTurn = [];
1163
+ for (const msg of messages) {
1164
+ if (msg.role === "user" && currentTurn.length > 0) {
1165
+ turns.push({
1166
+ messages: currentTurn,
1167
+ tokenEstimate: estimateTurnTokens(currentTurn)
1168
+ });
1169
+ currentTurn = [msg];
1170
+ } else {
1171
+ currentTurn.push(msg);
1172
+ }
1173
+ }
1174
+ if (currentTurn.length > 0) {
1175
+ turns.push({
1176
+ messages: currentTurn,
1177
+ tokenEstimate: estimateTurnTokens(currentTurn)
1178
+ });
1179
+ }
1180
+ return turns;
1181
+ }
1182
+ function estimateTurnTokens(messages) {
1183
+ return Math.ceil(messages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0) / 4);
1184
+ }
1185
+ function flattenTurns(turns) {
1186
+ return turns.flatMap((turn) => turn.messages);
1187
+ }
1188
+ var init_strategy = __esm({
1189
+ "src/agent/compaction/strategy.ts"() {
1190
+ "use strict";
1191
+ }
1192
+ });
1193
+
1194
+ // src/agent/compaction/strategies/sliding-window.ts
1195
+ var TRUNCATION_MARKER_TEMPLATE, SlidingWindowStrategy;
1196
+ var init_sliding_window = __esm({
1197
+ "src/agent/compaction/strategies/sliding-window.ts"() {
1198
+ "use strict";
1199
+ init_strategy();
1200
+ TRUNCATION_MARKER_TEMPLATE = "[Previous conversation truncated. Removed {count} turn(s) to fit context window.]";
1201
+ SlidingWindowStrategy = class {
1202
+ name = "sliding-window";
1203
+ async compact(messages, config, context) {
1204
+ const turns = groupIntoTurns(messages);
1205
+ const preserveCount = Math.min(config.preserveRecentTurns, turns.length);
1206
+ if (turns.length <= preserveCount) {
1207
+ return {
1208
+ messages,
1209
+ strategyName: this.name,
1210
+ metadata: {
1211
+ originalCount: messages.length,
1212
+ compactedCount: messages.length,
1213
+ tokensBefore: context.currentTokens,
1214
+ tokensAfter: context.currentTokens
1215
+ }
1216
+ };
1217
+ }
1218
+ const turnsToKeep = turns.slice(-preserveCount);
1219
+ const turnsRemoved = turns.length - preserveCount;
1220
+ const truncationMarker = {
1221
+ role: "user",
1222
+ content: TRUNCATION_MARKER_TEMPLATE.replace("{count}", turnsRemoved.toString())
1223
+ };
1224
+ const compactedMessages = [truncationMarker, ...flattenTurns(turnsToKeep)];
1225
+ const tokensAfter = Math.ceil(
1226
+ compactedMessages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0) / 4
1227
+ );
1228
+ return {
1229
+ messages: compactedMessages,
1230
+ strategyName: this.name,
1231
+ metadata: {
1232
+ originalCount: messages.length,
1233
+ compactedCount: compactedMessages.length,
1234
+ tokensBefore: context.currentTokens,
1235
+ tokensAfter
1236
+ }
1237
+ };
1238
+ }
1239
+ };
1240
+ }
1241
+ });
1242
+
1243
+ // src/agent/compaction/strategies/summarization.ts
1244
+ var SummarizationStrategy;
1245
+ var init_summarization = __esm({
1246
+ "src/agent/compaction/strategies/summarization.ts"() {
1247
+ "use strict";
1248
+ init_strategy();
1249
+ SummarizationStrategy = class {
1250
+ name = "summarization";
1251
+ async compact(messages, config, context) {
1252
+ const turns = groupIntoTurns(messages);
1253
+ const preserveCount = Math.min(config.preserveRecentTurns, turns.length);
1254
+ if (turns.length <= preserveCount) {
1255
+ return {
1256
+ messages,
1257
+ strategyName: this.name,
1258
+ metadata: {
1259
+ originalCount: messages.length,
1260
+ compactedCount: messages.length,
1261
+ tokensBefore: context.currentTokens,
1262
+ tokensAfter: context.currentTokens
1263
+ }
1264
+ };
1265
+ }
1266
+ const turnsToSummarize = turns.slice(0, -preserveCount);
1267
+ const turnsToKeep = turns.slice(-preserveCount);
1268
+ const conversationToSummarize = this.formatTurnsForSummary(flattenTurns(turnsToSummarize));
1269
+ const summary = await this.generateSummary(conversationToSummarize, config, context);
1270
+ const summaryMessage = {
1271
+ role: "user",
1272
+ content: `[Previous conversation summary]
1273
+ ${summary}
1274
+ [End of summary - conversation continues below]`
1275
+ };
1276
+ const compactedMessages = [summaryMessage, ...flattenTurns(turnsToKeep)];
1277
+ const tokensAfter = Math.ceil(
1278
+ compactedMessages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0) / 4
1279
+ );
1280
+ return {
1281
+ messages: compactedMessages,
1282
+ summary,
1283
+ strategyName: this.name,
1284
+ metadata: {
1285
+ originalCount: messages.length,
1286
+ compactedCount: compactedMessages.length,
1287
+ tokensBefore: context.currentTokens,
1288
+ tokensAfter
1289
+ }
1290
+ };
1291
+ }
1292
+ /**
1293
+ * Formats messages into a readable conversation format for summarization.
1294
+ */
1295
+ formatTurnsForSummary(messages) {
1296
+ return messages.map((msg) => {
1297
+ const role = msg.role.charAt(0).toUpperCase() + msg.role.slice(1);
1298
+ return `${role}: ${msg.content}`;
1299
+ }).join("\n\n");
1300
+ }
1301
+ /**
1302
+ * Generates a summary using the configured LLM.
1303
+ */
1304
+ async generateSummary(conversation, config, context) {
1305
+ const model = config.summarizationModel ?? context.model;
1306
+ const prompt = `${config.summarizationPrompt}
1307
+
1308
+ ${conversation}`;
1309
+ const response = await context.client.complete(prompt, {
1310
+ model,
1311
+ temperature: 0.3
1312
+ // Low temperature for factual summarization
1313
+ });
1314
+ return response.trim();
1315
+ }
1316
+ };
1317
+ }
1318
+ });
1319
+
1320
+ // src/agent/compaction/strategies/hybrid.ts
1321
+ var MIN_TURNS_FOR_SUMMARIZATION, HybridStrategy;
1322
+ var init_hybrid = __esm({
1323
+ "src/agent/compaction/strategies/hybrid.ts"() {
1324
+ "use strict";
1325
+ init_strategy();
1326
+ init_sliding_window();
1327
+ init_summarization();
1328
+ MIN_TURNS_FOR_SUMMARIZATION = 3;
1329
+ HybridStrategy = class {
1330
+ name = "hybrid";
1331
+ slidingWindow = new SlidingWindowStrategy();
1332
+ summarization = new SummarizationStrategy();
1333
+ async compact(messages, config, context) {
1334
+ const turns = groupIntoTurns(messages);
1335
+ const preserveCount = Math.min(config.preserveRecentTurns, turns.length);
1336
+ if (turns.length <= preserveCount) {
1337
+ return {
1338
+ messages,
1339
+ strategyName: this.name,
1340
+ metadata: {
1341
+ originalCount: messages.length,
1342
+ compactedCount: messages.length,
1343
+ tokensBefore: context.currentTokens,
1344
+ tokensAfter: context.currentTokens
1345
+ }
1346
+ };
1347
+ }
1348
+ const turnsToSummarize = turns.length - preserveCount;
1349
+ if (turnsToSummarize < MIN_TURNS_FOR_SUMMARIZATION) {
1350
+ return this.slidingWindow.compact(messages, config, context);
1351
+ }
1352
+ return this.summarization.compact(messages, config, context);
1353
+ }
1354
+ };
1355
+ }
1356
+ });
1357
+
1358
+ // src/agent/compaction/strategies/index.ts
1359
+ var init_strategies = __esm({
1360
+ "src/agent/compaction/strategies/index.ts"() {
1361
+ "use strict";
1362
+ init_sliding_window();
1363
+ init_summarization();
1364
+ init_hybrid();
1365
+ }
1366
+ });
1367
+
1368
+ // src/agent/compaction/manager.ts
1369
+ function createStrategy(name) {
1370
+ switch (name) {
1371
+ case "sliding-window":
1372
+ return new SlidingWindowStrategy();
1373
+ case "summarization":
1374
+ return new SummarizationStrategy();
1375
+ case "hybrid":
1376
+ return new HybridStrategy();
1377
+ default:
1378
+ throw new Error(`Unknown compaction strategy: ${name}`);
1379
+ }
1380
+ }
1381
+ var CompactionManager;
1382
+ var init_manager = __esm({
1383
+ "src/agent/compaction/manager.ts"() {
1384
+ "use strict";
1385
+ init_config();
1386
+ init_strategies();
1387
+ CompactionManager = class {
1388
+ client;
1389
+ model;
1390
+ config;
1391
+ strategy;
1392
+ modelLimits;
1393
+ // Statistics
1394
+ totalCompactions = 0;
1395
+ totalTokensSaved = 0;
1396
+ lastTokenCount = 0;
1397
+ constructor(client, model, config = {}) {
1398
+ this.client = client;
1399
+ this.model = model;
1400
+ this.config = resolveCompactionConfig(config);
1401
+ if (typeof config.strategy === "object" && "compact" in config.strategy) {
1402
+ this.strategy = config.strategy;
1403
+ } else {
1404
+ this.strategy = createStrategy(this.config.strategy);
1405
+ }
1406
+ }
1407
+ /**
1408
+ * Check if compaction is needed and perform it if so.
1409
+ *
1410
+ * @param conversation - The conversation manager to compact
1411
+ * @param iteration - Current agent iteration (for event metadata)
1412
+ * @returns CompactionEvent if compaction was performed, null otherwise
1413
+ */
1414
+ async checkAndCompact(conversation, iteration) {
1415
+ if (!this.config.enabled) {
1416
+ return null;
1417
+ }
1418
+ if (!this.modelLimits) {
1419
+ this.modelLimits = this.client.modelRegistry.getModelLimits(this.model);
1420
+ if (!this.modelLimits) {
1421
+ return null;
1422
+ }
1423
+ }
1424
+ if (!this.client.countTokens) {
1425
+ return null;
1426
+ }
1427
+ const messages = conversation.getMessages();
1428
+ const currentTokens = await this.client.countTokens(this.model, messages);
1429
+ this.lastTokenCount = currentTokens;
1430
+ const usagePercent = currentTokens / this.modelLimits.contextWindow * 100;
1431
+ if (usagePercent < this.config.triggerThresholdPercent) {
1432
+ return null;
1433
+ }
1434
+ const historyMessages = conversation.getHistoryMessages();
1435
+ const baseMessages = conversation.getBaseMessages();
1436
+ const historyTokens = await this.client.countTokens(this.model, historyMessages);
1437
+ const baseTokens = await this.client.countTokens(this.model, baseMessages);
1438
+ return this.compact(conversation, iteration, {
1439
+ historyMessages,
1440
+ baseMessages,
1441
+ historyTokens,
1442
+ baseTokens,
1443
+ currentTokens: historyTokens + baseTokens
1444
+ });
1445
+ }
1446
+ /**
1447
+ * Force compaction regardless of threshold.
1448
+ *
1449
+ * @param conversation - The conversation manager to compact
1450
+ * @param iteration - Current agent iteration (for event metadata). Use -1 for manual compaction.
1451
+ * @param precomputed - Optional pre-computed token counts (passed from checkAndCompact for efficiency)
1452
+ * @returns CompactionEvent with compaction details
1453
+ */
1454
+ async compact(conversation, iteration, precomputed) {
1455
+ if (!this.modelLimits) {
1456
+ this.modelLimits = this.client.modelRegistry.getModelLimits(this.model);
1457
+ if (!this.modelLimits) {
1458
+ return null;
1459
+ }
1460
+ }
1461
+ const historyMessages = precomputed?.historyMessages ?? conversation.getHistoryMessages();
1462
+ const baseMessages = precomputed?.baseMessages ?? conversation.getBaseMessages();
1463
+ const historyTokens = precomputed?.historyTokens ?? await this.client.countTokens(this.model, historyMessages);
1464
+ const baseTokens = precomputed?.baseTokens ?? await this.client.countTokens(this.model, baseMessages);
1465
+ const currentTokens = precomputed?.currentTokens ?? historyTokens + baseTokens;
1466
+ const targetTotalTokens = Math.floor(
1467
+ this.modelLimits.contextWindow * this.config.targetPercent / 100
1468
+ );
1469
+ const targetHistoryTokens = Math.max(0, targetTotalTokens - baseTokens);
1470
+ const result = await this.strategy.compact(historyMessages, this.config, {
1471
+ currentTokens: historyTokens,
1472
+ targetTokens: targetHistoryTokens,
1473
+ modelLimits: this.modelLimits,
1474
+ client: this.client,
1475
+ model: this.config.summarizationModel ?? this.model
1476
+ });
1477
+ conversation.replaceHistory(result.messages);
1478
+ const afterTokens = await this.client.countTokens(this.model, conversation.getMessages());
1479
+ const tokensSaved = currentTokens - afterTokens;
1480
+ this.totalCompactions++;
1481
+ this.totalTokensSaved += tokensSaved;
1482
+ this.lastTokenCount = afterTokens;
1483
+ const event = {
1484
+ strategy: result.strategyName,
1485
+ tokensBefore: currentTokens,
1486
+ tokensAfter: afterTokens,
1487
+ messagesBefore: historyMessages.length + baseMessages.length,
1488
+ messagesAfter: result.messages.length + baseMessages.length,
1489
+ summary: result.summary,
1490
+ iteration
1491
+ };
1492
+ if (this.config.onCompaction) {
1493
+ try {
1494
+ this.config.onCompaction(event);
1495
+ } catch (err) {
1496
+ console.warn("[llmist/compaction] onCompaction callback error:", err);
1497
+ }
1498
+ }
1499
+ return event;
1500
+ }
1501
+ /**
1502
+ * Get compaction statistics.
1503
+ */
1504
+ getStats() {
1505
+ const contextWindow = this.modelLimits?.contextWindow ?? 0;
1506
+ return {
1507
+ totalCompactions: this.totalCompactions,
1508
+ totalTokensSaved: this.totalTokensSaved,
1509
+ currentUsage: {
1510
+ tokens: this.lastTokenCount,
1511
+ percent: contextWindow > 0 ? this.lastTokenCount / contextWindow * 100 : 0
1512
+ },
1513
+ contextWindow
1514
+ };
1515
+ }
1516
+ /**
1517
+ * Check if compaction is enabled.
1518
+ */
1519
+ isEnabled() {
1520
+ return this.config.enabled;
1521
+ }
1522
+ };
1523
+ }
1524
+ });
1525
+
1115
1526
  // src/agent/gadget-output-store.ts
1116
1527
  var import_node_crypto, GadgetOutputStore;
1117
1528
  var init_gadget_output_store = __esm({
@@ -1214,10 +1625,16 @@ var init_conversation_manager = __esm({
1214
1625
  baseMessages;
1215
1626
  initialMessages;
1216
1627
  historyBuilder;
1628
+ startPrefix;
1629
+ endPrefix;
1630
+ argPrefix;
1217
1631
  constructor(baseMessages, initialMessages, options = {}) {
1218
1632
  this.baseMessages = baseMessages;
1219
1633
  this.initialMessages = initialMessages;
1220
1634
  this.historyBuilder = new LLMMessageBuilder();
1635
+ this.startPrefix = options.startPrefix;
1636
+ this.endPrefix = options.endPrefix;
1637
+ this.argPrefix = options.argPrefix;
1221
1638
  if (options.startPrefix && options.endPrefix) {
1222
1639
  this.historyBuilder.withPrefixes(options.startPrefix, options.endPrefix, options.argPrefix);
1223
1640
  }
@@ -1234,6 +1651,25 @@ var init_conversation_manager = __esm({
1234
1651
  getMessages() {
1235
1652
  return [...this.baseMessages, ...this.initialMessages, ...this.historyBuilder.build()];
1236
1653
  }
1654
+ getHistoryMessages() {
1655
+ return this.historyBuilder.build();
1656
+ }
1657
+ getBaseMessages() {
1658
+ return [...this.baseMessages, ...this.initialMessages];
1659
+ }
1660
+ replaceHistory(newHistory) {
1661
+ this.historyBuilder = new LLMMessageBuilder();
1662
+ if (this.startPrefix && this.endPrefix) {
1663
+ this.historyBuilder.withPrefixes(this.startPrefix, this.endPrefix, this.argPrefix);
1664
+ }
1665
+ for (const msg of newHistory) {
1666
+ if (msg.role === "user") {
1667
+ this.historyBuilder.addUser(msg.content);
1668
+ } else if (msg.role === "assistant") {
1669
+ this.historyBuilder.addAssistant(msg.content);
1670
+ }
1671
+ }
1672
+ }
1237
1673
  };
1238
1674
  }
1239
1675
  });
@@ -1446,334 +1882,241 @@ var init_hook_validators = __esm({
1446
1882
  }
1447
1883
  });
1448
1884
 
1449
- // src/gadgets/error-formatter.ts
1450
- var GadgetErrorFormatter;
1451
- var init_error_formatter = __esm({
1452
- "src/gadgets/error-formatter.ts"() {
1885
+ // src/gadgets/schema-introspector.ts
1886
+ function getDef(schema) {
1887
+ return schema._def;
1888
+ }
1889
+ function getTypeName(schema) {
1890
+ const def = getDef(schema);
1891
+ return def?.type ?? def?.typeName;
1892
+ }
1893
+ function getShape(schema) {
1894
+ const def = getDef(schema);
1895
+ if (typeof def?.shape === "function") {
1896
+ return def.shape();
1897
+ }
1898
+ return def?.shape;
1899
+ }
1900
+ var SchemaIntrospector;
1901
+ var init_schema_introspector = __esm({
1902
+ "src/gadgets/schema-introspector.ts"() {
1453
1903
  "use strict";
1454
- init_constants();
1455
- GadgetErrorFormatter = class {
1456
- argPrefix;
1457
- startPrefix;
1458
- endPrefix;
1459
- constructor(options = {}) {
1460
- this.argPrefix = options.argPrefix ?? GADGET_ARG_PREFIX;
1461
- this.startPrefix = options.startPrefix ?? GADGET_START_PREFIX;
1462
- this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
1904
+ SchemaIntrospector = class {
1905
+ schema;
1906
+ cache = /* @__PURE__ */ new Map();
1907
+ constructor(schema) {
1908
+ this.schema = schema;
1463
1909
  }
1464
1910
  /**
1465
- * Format a Zod validation error with full gadget instructions.
1911
+ * Get the expected type at a JSON pointer path.
1466
1912
  *
1467
- * @param gadgetName - Name of the gadget that was called
1468
- * @param zodError - The Zod validation error
1469
- * @param gadget - The gadget instance (for generating instructions)
1470
- * @returns Formatted error message with usage instructions
1913
+ * @param pointer - JSON pointer path without leading / (e.g., "config/timeout", "items/0")
1914
+ * @returns Type hint for coercion decision
1471
1915
  */
1472
- formatValidationError(gadgetName, zodError, gadget) {
1473
- const parts = [];
1474
- parts.push(`Error: Invalid parameters for '${gadgetName}':`);
1475
- for (const issue of zodError.issues) {
1476
- const path = issue.path.join(".") || "root";
1477
- parts.push(` - ${path}: ${issue.message}`);
1916
+ getTypeAtPath(pointer) {
1917
+ const cached = this.cache.get(pointer);
1918
+ if (cached !== void 0) {
1919
+ return cached;
1478
1920
  }
1479
- parts.push("");
1480
- parts.push("Gadget Usage:");
1481
- parts.push(gadget.getInstruction(this.argPrefix));
1482
- return parts.join("\n");
1921
+ const result = this.resolveTypeAtPath(pointer);
1922
+ this.cache.set(pointer, result);
1923
+ return result;
1483
1924
  }
1484
1925
  /**
1485
- * Format a parse error with block format reference.
1486
- *
1487
- * @param gadgetName - Name of the gadget that was called
1488
- * @param parseError - The parse error message
1489
- * @param gadget - The gadget instance if found (for generating instructions)
1490
- * @returns Formatted error message with format reference
1926
+ * Internal method to resolve type at path without caching.
1491
1927
  */
1492
- formatParseError(gadgetName, parseError, gadget) {
1493
- const parts = [];
1494
- parts.push(`Error: Failed to parse parameters for '${gadgetName}':`);
1495
- parts.push(` ${parseError}`);
1496
- if (gadget) {
1497
- parts.push("");
1498
- parts.push("Gadget Usage:");
1499
- parts.push(gadget.getInstruction(this.argPrefix));
1928
+ resolveTypeAtPath(pointer) {
1929
+ if (!pointer) {
1930
+ return this.getBaseType(this.schema);
1931
+ }
1932
+ const segments = pointer.split("/");
1933
+ let current = this.schema;
1934
+ for (const segment of segments) {
1935
+ current = this.unwrapSchema(current);
1936
+ const typeName = getTypeName(current);
1937
+ if (typeName === "object" || typeName === "ZodObject") {
1938
+ const shape = getShape(current);
1939
+ if (!shape || !(segment in shape)) {
1940
+ return "unknown";
1941
+ }
1942
+ current = shape[segment];
1943
+ } else if (typeName === "array" || typeName === "ZodArray") {
1944
+ if (!/^\d+$/.test(segment)) {
1945
+ return "unknown";
1946
+ }
1947
+ const def = getDef(current);
1948
+ const elementType = def?.element ?? def?.type;
1949
+ if (!elementType) {
1950
+ return "unknown";
1951
+ }
1952
+ current = elementType;
1953
+ } else if (typeName === "tuple" || typeName === "ZodTuple") {
1954
+ if (!/^\d+$/.test(segment)) {
1955
+ return "unknown";
1956
+ }
1957
+ const index = parseInt(segment, 10);
1958
+ const def = getDef(current);
1959
+ const items = def?.items;
1960
+ if (!items || index >= items.length) {
1961
+ return "unknown";
1962
+ }
1963
+ current = items[index];
1964
+ } else if (typeName === "record" || typeName === "ZodRecord") {
1965
+ const def = getDef(current);
1966
+ const valueType = def?.valueType;
1967
+ if (!valueType) {
1968
+ return "unknown";
1969
+ }
1970
+ current = valueType;
1971
+ } else {
1972
+ return "unknown";
1973
+ }
1500
1974
  }
1501
- parts.push("");
1502
- parts.push("Block Format Reference:");
1503
- parts.push(` ${this.startPrefix}${gadgetName}`);
1504
- parts.push(` ${this.argPrefix}parameterName`);
1505
- parts.push(" parameter value here");
1506
- parts.push(` ${this.endPrefix}`);
1507
- return parts.join("\n");
1975
+ return this.getBaseType(current);
1508
1976
  }
1509
1977
  /**
1510
- * Format a registry error (gadget not found) with available gadgets list.
1511
- *
1512
- * @param gadgetName - Name of the gadget that was not found
1513
- * @param availableGadgets - List of available gadget names
1514
- * @returns Formatted error message with available gadgets
1978
+ * Unwrap schema modifiers (optional, default, nullable, branded, etc.)
1979
+ * to get to the underlying type.
1515
1980
  */
1516
- formatRegistryError(gadgetName, availableGadgets) {
1517
- const parts = [];
1518
- parts.push(`Error: Gadget '${gadgetName}' not found.`);
1519
- if (availableGadgets.length > 0) {
1520
- parts.push("");
1521
- parts.push(`Available gadgets: ${availableGadgets.join(", ")}`);
1522
- } else {
1523
- parts.push("");
1524
- parts.push("No gadgets are currently registered.");
1981
+ unwrapSchema(schema) {
1982
+ let current = schema;
1983
+ let iterations = 0;
1984
+ const maxIterations = 20;
1985
+ while (iterations < maxIterations) {
1986
+ const typeName = getTypeName(current);
1987
+ const wrapperTypes = [
1988
+ "optional",
1989
+ "nullable",
1990
+ "default",
1991
+ "catch",
1992
+ "branded",
1993
+ "readonly",
1994
+ "pipeline",
1995
+ "ZodOptional",
1996
+ "ZodNullable",
1997
+ "ZodDefault",
1998
+ "ZodCatch",
1999
+ "ZodBranded",
2000
+ "ZodReadonly",
2001
+ "ZodPipeline"
2002
+ ];
2003
+ if (typeName && wrapperTypes.includes(typeName)) {
2004
+ const def = getDef(current);
2005
+ const inner = def?.innerType ?? def?.in ?? def?.type;
2006
+ if (!inner || inner === current) break;
2007
+ current = inner;
2008
+ iterations++;
2009
+ continue;
2010
+ }
2011
+ break;
1525
2012
  }
1526
- return parts.join("\n");
1527
- }
1528
- };
1529
- }
1530
- });
1531
-
1532
- // src/gadgets/exceptions.ts
1533
- var BreakLoopException, HumanInputException, TimeoutException;
1534
- var init_exceptions = __esm({
1535
- "src/gadgets/exceptions.ts"() {
1536
- "use strict";
1537
- BreakLoopException = class extends Error {
1538
- constructor(message) {
1539
- super(message ?? "Agent loop terminated by gadget");
1540
- this.name = "BreakLoopException";
1541
- }
1542
- };
1543
- HumanInputException = class extends Error {
1544
- question;
1545
- constructor(question) {
1546
- super(`Human input required: ${question}`);
1547
- this.name = "HumanInputException";
1548
- this.question = question;
2013
+ return current;
1549
2014
  }
1550
- };
1551
- TimeoutException = class extends Error {
1552
- timeoutMs;
1553
- gadgetName;
1554
- constructor(gadgetName, timeoutMs) {
1555
- super(`Gadget '${gadgetName}' execution exceeded timeout of ${timeoutMs}ms`);
1556
- this.name = "TimeoutException";
1557
- this.gadgetName = gadgetName;
1558
- this.timeoutMs = timeoutMs;
1559
- }
1560
- };
1561
- }
1562
- });
1563
-
1564
- // src/gadgets/executor.ts
1565
- var GadgetExecutor;
1566
- var init_executor = __esm({
1567
- "src/gadgets/executor.ts"() {
1568
- "use strict";
1569
- init_logger();
1570
- init_error_formatter();
1571
- init_exceptions();
1572
- GadgetExecutor = class {
1573
- constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions) {
1574
- this.registry = registry;
1575
- this.onHumanInputRequired = onHumanInputRequired;
1576
- this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
1577
- this.logger = logger ?? createLogger({ name: "llmist:executor" });
1578
- this.errorFormatter = new GadgetErrorFormatter(errorFormatterOptions);
1579
- }
1580
- logger;
1581
- errorFormatter;
1582
- /**
1583
- * Creates a promise that rejects with a TimeoutException after the specified timeout.
1584
- */
1585
- createTimeoutPromise(gadgetName, timeoutMs) {
1586
- return new Promise((_, reject) => {
1587
- setTimeout(() => {
1588
- reject(new TimeoutException(gadgetName, timeoutMs));
1589
- }, timeoutMs);
1590
- });
1591
- }
1592
- // Execute a gadget call asynchronously
1593
- async execute(call) {
1594
- const startTime = Date.now();
1595
- this.logger.debug("Executing gadget", {
1596
- gadgetName: call.gadgetName,
1597
- invocationId: call.invocationId,
1598
- parameters: call.parameters
1599
- });
1600
- const rawParameters = call.parameters ?? {};
1601
- let validatedParameters = rawParameters;
1602
- try {
1603
- const gadget = this.registry.get(call.gadgetName);
1604
- if (!gadget) {
1605
- this.logger.error("Gadget not found", { gadgetName: call.gadgetName });
1606
- const availableGadgets = this.registry.getNames();
1607
- return {
1608
- gadgetName: call.gadgetName,
1609
- invocationId: call.invocationId,
1610
- parameters: call.parameters ?? {},
1611
- error: this.errorFormatter.formatRegistryError(call.gadgetName, availableGadgets),
1612
- executionTimeMs: Date.now() - startTime
1613
- };
1614
- }
1615
- if (call.parseError || !call.parameters) {
1616
- this.logger.error("Gadget parameter parse error", {
1617
- gadgetName: call.gadgetName,
1618
- parseError: call.parseError,
1619
- rawParameters: call.parametersRaw
1620
- });
1621
- const parseErrorMessage = call.parseError ?? "Failed to parse parameters";
1622
- return {
1623
- gadgetName: call.gadgetName,
1624
- invocationId: call.invocationId,
1625
- parameters: {},
1626
- error: this.errorFormatter.formatParseError(call.gadgetName, parseErrorMessage, gadget),
1627
- executionTimeMs: Date.now() - startTime
1628
- };
1629
- }
1630
- if (gadget.parameterSchema) {
1631
- const validationResult = gadget.parameterSchema.safeParse(rawParameters);
1632
- if (!validationResult.success) {
1633
- const validationError = this.errorFormatter.formatValidationError(
1634
- call.gadgetName,
1635
- validationResult.error,
1636
- gadget
1637
- );
1638
- this.logger.error("Gadget parameter validation failed", {
1639
- gadgetName: call.gadgetName,
1640
- issueCount: validationResult.error.issues.length
1641
- });
1642
- return {
1643
- gadgetName: call.gadgetName,
1644
- invocationId: call.invocationId,
1645
- parameters: rawParameters,
1646
- error: validationError,
1647
- executionTimeMs: Date.now() - startTime
1648
- };
1649
- }
1650
- validatedParameters = validationResult.data;
1651
- }
1652
- const timeoutMs = gadget.timeoutMs ?? this.defaultGadgetTimeoutMs;
1653
- let result;
1654
- if (timeoutMs && timeoutMs > 0) {
1655
- this.logger.debug("Executing gadget with timeout", {
1656
- gadgetName: call.gadgetName,
1657
- timeoutMs
1658
- });
1659
- result = await Promise.race([
1660
- Promise.resolve(gadget.execute(validatedParameters)),
1661
- this.createTimeoutPromise(call.gadgetName, timeoutMs)
1662
- ]);
1663
- } else {
1664
- result = await Promise.resolve(gadget.execute(validatedParameters));
1665
- }
1666
- const executionTimeMs = Date.now() - startTime;
1667
- this.logger.info("Gadget executed successfully", {
1668
- gadgetName: call.gadgetName,
1669
- invocationId: call.invocationId,
1670
- executionTimeMs
1671
- });
1672
- this.logger.debug("Gadget result", {
1673
- gadgetName: call.gadgetName,
1674
- invocationId: call.invocationId,
1675
- parameters: validatedParameters,
1676
- result,
1677
- executionTimeMs
1678
- });
1679
- return {
1680
- gadgetName: call.gadgetName,
1681
- invocationId: call.invocationId,
1682
- parameters: validatedParameters,
1683
- result,
1684
- executionTimeMs
1685
- };
1686
- } catch (error) {
1687
- if (error instanceof BreakLoopException) {
1688
- this.logger.info("Gadget requested loop termination", {
1689
- gadgetName: call.gadgetName,
1690
- message: error.message
1691
- });
1692
- return {
1693
- gadgetName: call.gadgetName,
1694
- invocationId: call.invocationId,
1695
- parameters: validatedParameters,
1696
- result: error.message,
1697
- breaksLoop: true,
1698
- executionTimeMs: Date.now() - startTime
1699
- };
1700
- }
1701
- if (error instanceof TimeoutException) {
1702
- this.logger.error("Gadget execution timed out", {
1703
- gadgetName: call.gadgetName,
1704
- timeoutMs: error.timeoutMs,
1705
- executionTimeMs: Date.now() - startTime
1706
- });
1707
- return {
1708
- gadgetName: call.gadgetName,
1709
- invocationId: call.invocationId,
1710
- parameters: validatedParameters,
1711
- error: error.message,
1712
- executionTimeMs: Date.now() - startTime
1713
- };
1714
- }
1715
- if (error instanceof HumanInputException) {
1716
- this.logger.info("Gadget requested human input", {
1717
- gadgetName: call.gadgetName,
1718
- question: error.question
1719
- });
1720
- if (this.onHumanInputRequired) {
1721
- try {
1722
- const answer = await this.onHumanInputRequired(error.question);
1723
- this.logger.debug("Human input received", {
1724
- gadgetName: call.gadgetName,
1725
- answerLength: answer.length
1726
- });
1727
- return {
1728
- gadgetName: call.gadgetName,
1729
- invocationId: call.invocationId,
1730
- parameters: validatedParameters,
1731
- result: answer,
1732
- executionTimeMs: Date.now() - startTime
1733
- };
1734
- } catch (inputError) {
1735
- this.logger.error("Human input callback error", {
1736
- gadgetName: call.gadgetName,
1737
- error: inputError instanceof Error ? inputError.message : String(inputError)
1738
- });
1739
- return {
1740
- gadgetName: call.gadgetName,
1741
- invocationId: call.invocationId,
1742
- parameters: validatedParameters,
1743
- error: inputError instanceof Error ? inputError.message : String(inputError),
1744
- executionTimeMs: Date.now() - startTime
1745
- };
1746
- }
1747
- }
1748
- this.logger.warn("Human input required but no callback provided", {
1749
- gadgetName: call.gadgetName
1750
- });
1751
- return {
1752
- gadgetName: call.gadgetName,
1753
- invocationId: call.invocationId,
1754
- parameters: validatedParameters,
1755
- error: "Human input required but not available (stdin is not interactive)",
1756
- executionTimeMs: Date.now() - startTime
1757
- };
1758
- }
1759
- const executionTimeMs = Date.now() - startTime;
1760
- this.logger.error("Gadget execution failed", {
1761
- gadgetName: call.gadgetName,
1762
- error: error instanceof Error ? error.message : String(error),
1763
- executionTimeMs
1764
- });
1765
- return {
1766
- gadgetName: call.gadgetName,
1767
- invocationId: call.invocationId,
1768
- parameters: validatedParameters,
1769
- error: error instanceof Error ? error.message : String(error),
1770
- executionTimeMs
1771
- };
1772
- }
1773
- }
1774
- // Execute multiple gadget calls in parallel
1775
- async executeAll(calls) {
1776
- return Promise.all(calls.map((call) => this.execute(call)));
2015
+ /**
2016
+ * Get the primitive type hint from an unwrapped schema.
2017
+ */
2018
+ getBaseType(schema) {
2019
+ const unwrapped = this.unwrapSchema(schema);
2020
+ const typeName = getTypeName(unwrapped);
2021
+ switch (typeName) {
2022
+ // Primitive types
2023
+ case "string":
2024
+ case "ZodString":
2025
+ return "string";
2026
+ case "number":
2027
+ case "ZodNumber":
2028
+ case "bigint":
2029
+ case "ZodBigInt":
2030
+ return "number";
2031
+ case "boolean":
2032
+ case "ZodBoolean":
2033
+ return "boolean";
2034
+ // Literal types - check the literal value type
2035
+ case "literal":
2036
+ case "ZodLiteral": {
2037
+ const def = getDef(unwrapped);
2038
+ const values = def?.values;
2039
+ const value = values?.[0] ?? def?.value;
2040
+ if (typeof value === "string") return "string";
2041
+ if (typeof value === "number" || typeof value === "bigint")
2042
+ return "number";
2043
+ if (typeof value === "boolean") return "boolean";
2044
+ return "unknown";
2045
+ }
2046
+ // Enum - always string keys
2047
+ case "enum":
2048
+ case "ZodEnum":
2049
+ case "nativeEnum":
2050
+ case "ZodNativeEnum":
2051
+ return "string";
2052
+ // Union - return 'unknown' to let auto-coercion decide
2053
+ // Since multiple types are valid, we can't definitively say what the LLM intended
2054
+ // Auto-coercion will handle common cases (numbers, booleans) appropriately
2055
+ case "union":
2056
+ case "ZodUnion":
2057
+ return "unknown";
2058
+ // Discriminated union - complex, return unknown
2059
+ case "discriminatedUnion":
2060
+ case "ZodDiscriminatedUnion":
2061
+ return "unknown";
2062
+ // Intersection - check both sides
2063
+ case "intersection":
2064
+ case "ZodIntersection": {
2065
+ const def = getDef(unwrapped);
2066
+ const left = def?.left;
2067
+ const right = def?.right;
2068
+ if (!left || !right) return "unknown";
2069
+ const leftType = this.getBaseType(left);
2070
+ const rightType = this.getBaseType(right);
2071
+ if (leftType === rightType) return leftType;
2072
+ if (leftType === "string" || rightType === "string") return "string";
2073
+ return "unknown";
2074
+ }
2075
+ // Effects/transforms - return unknown to let Zod handle it
2076
+ case "effects":
2077
+ case "ZodEffects":
2078
+ return "unknown";
2079
+ // Lazy - can't resolve without evaluating
2080
+ case "lazy":
2081
+ case "ZodLazy":
2082
+ return "unknown";
2083
+ // Complex types - return unknown
2084
+ case "object":
2085
+ case "ZodObject":
2086
+ case "array":
2087
+ case "ZodArray":
2088
+ case "tuple":
2089
+ case "ZodTuple":
2090
+ case "record":
2091
+ case "ZodRecord":
2092
+ case "map":
2093
+ case "ZodMap":
2094
+ case "set":
2095
+ case "ZodSet":
2096
+ case "function":
2097
+ case "ZodFunction":
2098
+ case "promise":
2099
+ case "ZodPromise":
2100
+ case "date":
2101
+ case "ZodDate":
2102
+ return "unknown";
2103
+ // Unknown/any/never/void/undefined/null
2104
+ case "unknown":
2105
+ case "ZodUnknown":
2106
+ case "any":
2107
+ case "ZodAny":
2108
+ case "never":
2109
+ case "ZodNever":
2110
+ case "void":
2111
+ case "ZodVoid":
2112
+ case "undefined":
2113
+ case "ZodUndefined":
2114
+ case "null":
2115
+ case "ZodNull":
2116
+ return "unknown";
2117
+ default:
2118
+ return "unknown";
2119
+ }
1777
2120
  }
1778
2121
  };
1779
2122
  }
@@ -1784,6 +2127,7 @@ function parseBlockParams(content, options) {
1784
2127
  const argPrefix = options?.argPrefix ?? GADGET_ARG_PREFIX;
1785
2128
  const result = {};
1786
2129
  const seenPointers = /* @__PURE__ */ new Set();
2130
+ const introspector = options?.schema ? new SchemaIntrospector(options.schema) : void 0;
1787
2131
  const parts = content.split(argPrefix);
1788
2132
  for (let i = 1; i < parts.length; i++) {
1789
2133
  const part = parts[i];
@@ -1795,7 +2139,7 @@ function parseBlockParams(content, options) {
1795
2139
  throw new Error(`Duplicate pointer: ${pointer2}`);
1796
2140
  }
1797
2141
  seenPointers.add(pointer2);
1798
- setByPointer(result, pointer2, "");
2142
+ setByPointer(result, pointer2, "", introspector);
1799
2143
  }
1800
2144
  continue;
1801
2145
  }
@@ -1811,15 +2155,30 @@ function parseBlockParams(content, options) {
1811
2155
  throw new Error(`Duplicate pointer: ${pointer}`);
1812
2156
  }
1813
2157
  seenPointers.add(pointer);
1814
- setByPointer(result, pointer, value);
2158
+ setByPointer(result, pointer, value, introspector);
1815
2159
  }
1816
2160
  return result;
1817
2161
  }
1818
- function coerceValue(value) {
2162
+ function coerceValue(value, expectedType) {
1819
2163
  if (value.includes("\n")) {
1820
2164
  return value;
1821
2165
  }
1822
2166
  const trimmed = value.trim();
2167
+ if (expectedType === "string") {
2168
+ return value;
2169
+ }
2170
+ if (expectedType === "boolean") {
2171
+ if (trimmed === "true") return true;
2172
+ if (trimmed === "false") return false;
2173
+ return value;
2174
+ }
2175
+ if (expectedType === "number") {
2176
+ const num = Number(trimmed);
2177
+ if (!isNaN(num) && isFinite(num) && trimmed !== "") {
2178
+ return num;
2179
+ }
2180
+ return value;
2181
+ }
1823
2182
  if (trimmed === "true") return true;
1824
2183
  if (trimmed === "false") return false;
1825
2184
  if (trimmed !== "" && /^-?\d+(\.\d+)?$/.test(trimmed)) {
@@ -1830,7 +2189,7 @@ function coerceValue(value) {
1830
2189
  }
1831
2190
  return value;
1832
2191
  }
1833
- function setByPointer(obj, pointer, value) {
2192
+ function setByPointer(obj, pointer, value, introspector) {
1834
2193
  const segments = pointer.split("/");
1835
2194
  let current = obj;
1836
2195
  for (let i = 0; i < segments.length - 1; i++) {
@@ -1842,40 +2201,157 @@ function setByPointer(obj, pointer, value) {
1842
2201
  if (isNaN(index) || index < 0) {
1843
2202
  throw new Error(`Invalid array index: ${segment}`);
1844
2203
  }
1845
- if (index > current.length) {
1846
- throw new Error(`Array index gap: expected ${current.length}, got ${index}`);
2204
+ if (index > current.length) {
2205
+ throw new Error(`Array index gap: expected ${current.length}, got ${index}`);
2206
+ }
2207
+ if (current[index] === void 0) {
2208
+ current[index] = nextIsArrayIndex ? [] : {};
2209
+ }
2210
+ current = current[index];
2211
+ } else {
2212
+ const rec = current;
2213
+ if (rec[segment] === void 0) {
2214
+ rec[segment] = nextIsArrayIndex ? [] : {};
2215
+ }
2216
+ current = rec[segment];
2217
+ }
2218
+ }
2219
+ const lastSegment = segments[segments.length - 1];
2220
+ const expectedType = introspector?.getTypeAtPath(pointer);
2221
+ const coercedValue = coerceValue(value, expectedType);
2222
+ if (Array.isArray(current)) {
2223
+ const index = parseInt(lastSegment, 10);
2224
+ if (isNaN(index) || index < 0) {
2225
+ throw new Error(`Invalid array index: ${lastSegment}`);
2226
+ }
2227
+ if (index > current.length) {
2228
+ throw new Error(`Array index gap: expected ${current.length}, got ${index}`);
2229
+ }
2230
+ current[index] = coercedValue;
2231
+ } else {
2232
+ current[lastSegment] = coercedValue;
2233
+ }
2234
+ }
2235
+ var init_block_params = __esm({
2236
+ "src/gadgets/block-params.ts"() {
2237
+ "use strict";
2238
+ init_constants();
2239
+ init_schema_introspector();
2240
+ }
2241
+ });
2242
+
2243
+ // src/gadgets/error-formatter.ts
2244
+ var GadgetErrorFormatter;
2245
+ var init_error_formatter = __esm({
2246
+ "src/gadgets/error-formatter.ts"() {
2247
+ "use strict";
2248
+ init_constants();
2249
+ GadgetErrorFormatter = class {
2250
+ argPrefix;
2251
+ startPrefix;
2252
+ endPrefix;
2253
+ constructor(options = {}) {
2254
+ this.argPrefix = options.argPrefix ?? GADGET_ARG_PREFIX;
2255
+ this.startPrefix = options.startPrefix ?? GADGET_START_PREFIX;
2256
+ this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
2257
+ }
2258
+ /**
2259
+ * Format a Zod validation error with full gadget instructions.
2260
+ *
2261
+ * @param gadgetName - Name of the gadget that was called
2262
+ * @param zodError - The Zod validation error
2263
+ * @param gadget - The gadget instance (for generating instructions)
2264
+ * @returns Formatted error message with usage instructions
2265
+ */
2266
+ formatValidationError(gadgetName, zodError, gadget) {
2267
+ const parts = [];
2268
+ parts.push(`Error: Invalid parameters for '${gadgetName}':`);
2269
+ for (const issue of zodError.issues) {
2270
+ const path = issue.path.join(".") || "root";
2271
+ parts.push(` - ${path}: ${issue.message}`);
2272
+ }
2273
+ parts.push("");
2274
+ parts.push("Gadget Usage:");
2275
+ parts.push(gadget.getInstruction(this.argPrefix));
2276
+ return parts.join("\n");
2277
+ }
2278
+ /**
2279
+ * Format a parse error with block format reference.
2280
+ *
2281
+ * @param gadgetName - Name of the gadget that was called
2282
+ * @param parseError - The parse error message
2283
+ * @param gadget - The gadget instance if found (for generating instructions)
2284
+ * @returns Formatted error message with format reference
2285
+ */
2286
+ formatParseError(gadgetName, parseError, gadget) {
2287
+ const parts = [];
2288
+ parts.push(`Error: Failed to parse parameters for '${gadgetName}':`);
2289
+ parts.push(` ${parseError}`);
2290
+ if (gadget) {
2291
+ parts.push("");
2292
+ parts.push("Gadget Usage:");
2293
+ parts.push(gadget.getInstruction(this.argPrefix));
2294
+ }
2295
+ parts.push("");
2296
+ parts.push("Block Format Reference:");
2297
+ parts.push(` ${this.startPrefix}${gadgetName}`);
2298
+ parts.push(` ${this.argPrefix}parameterName`);
2299
+ parts.push(" parameter value here");
2300
+ parts.push(` ${this.endPrefix}`);
2301
+ return parts.join("\n");
2302
+ }
2303
+ /**
2304
+ * Format a registry error (gadget not found) with available gadgets list.
2305
+ *
2306
+ * @param gadgetName - Name of the gadget that was not found
2307
+ * @param availableGadgets - List of available gadget names
2308
+ * @returns Formatted error message with available gadgets
2309
+ */
2310
+ formatRegistryError(gadgetName, availableGadgets) {
2311
+ const parts = [];
2312
+ parts.push(`Error: Gadget '${gadgetName}' not found.`);
2313
+ if (availableGadgets.length > 0) {
2314
+ parts.push("");
2315
+ parts.push(`Available gadgets: ${availableGadgets.join(", ")}`);
2316
+ } else {
2317
+ parts.push("");
2318
+ parts.push("No gadgets are currently registered.");
2319
+ }
2320
+ return parts.join("\n");
2321
+ }
2322
+ };
2323
+ }
2324
+ });
2325
+
2326
+ // src/gadgets/exceptions.ts
2327
+ var BreakLoopException, HumanInputException, TimeoutException;
2328
+ var init_exceptions = __esm({
2329
+ "src/gadgets/exceptions.ts"() {
2330
+ "use strict";
2331
+ BreakLoopException = class extends Error {
2332
+ constructor(message) {
2333
+ super(message ?? "Agent loop terminated by gadget");
2334
+ this.name = "BreakLoopException";
1847
2335
  }
1848
- if (current[index] === void 0) {
1849
- current[index] = nextIsArrayIndex ? [] : {};
2336
+ };
2337
+ HumanInputException = class extends Error {
2338
+ question;
2339
+ constructor(question) {
2340
+ super(`Human input required: ${question}`);
2341
+ this.name = "HumanInputException";
2342
+ this.question = question;
1850
2343
  }
1851
- current = current[index];
1852
- } else {
1853
- const rec = current;
1854
- if (rec[segment] === void 0) {
1855
- rec[segment] = nextIsArrayIndex ? [] : {};
2344
+ };
2345
+ TimeoutException = class extends Error {
2346
+ timeoutMs;
2347
+ gadgetName;
2348
+ constructor(gadgetName, timeoutMs) {
2349
+ super(`Gadget '${gadgetName}' execution exceeded timeout of ${timeoutMs}ms`);
2350
+ this.name = "TimeoutException";
2351
+ this.gadgetName = gadgetName;
2352
+ this.timeoutMs = timeoutMs;
1856
2353
  }
1857
- current = rec[segment];
1858
- }
1859
- }
1860
- const lastSegment = segments[segments.length - 1];
1861
- const coercedValue = coerceValue(value);
1862
- if (Array.isArray(current)) {
1863
- const index = parseInt(lastSegment, 10);
1864
- if (isNaN(index) || index < 0) {
1865
- throw new Error(`Invalid array index: ${lastSegment}`);
1866
- }
1867
- if (index > current.length) {
1868
- throw new Error(`Array index gap: expected ${current.length}, got ${index}`);
1869
- }
1870
- current[index] = coercedValue;
1871
- } else {
1872
- current[lastSegment] = coercedValue;
1873
- }
1874
- }
1875
- var init_block_params = __esm({
1876
- "src/gadgets/block-params.ts"() {
1877
- "use strict";
1878
- init_constants();
2354
+ };
1879
2355
  }
1880
2356
  });
1881
2357
 
@@ -2039,18 +2515,295 @@ var init_parser = __esm({
2039
2515
  parseError
2040
2516
  }
2041
2517
  };
2042
- return;
2518
+ return;
2519
+ }
2520
+ }
2521
+ const remainingText = this.takeTextUntil(this.buffer.length);
2522
+ if (remainingText !== void 0) {
2523
+ yield { type: "text", content: remainingText };
2524
+ }
2525
+ }
2526
+ // Reset parser state (note: global invocation counter is NOT reset to ensure unique IDs)
2527
+ reset() {
2528
+ this.buffer = "";
2529
+ this.lastReportedTextLength = 0;
2530
+ }
2531
+ };
2532
+ }
2533
+ });
2534
+
2535
+ // src/gadgets/executor.ts
2536
+ var GadgetExecutor;
2537
+ var init_executor = __esm({
2538
+ "src/gadgets/executor.ts"() {
2539
+ "use strict";
2540
+ init_constants();
2541
+ init_logger();
2542
+ init_block_params();
2543
+ init_error_formatter();
2544
+ init_exceptions();
2545
+ init_parser();
2546
+ GadgetExecutor = class {
2547
+ constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions) {
2548
+ this.registry = registry;
2549
+ this.onHumanInputRequired = onHumanInputRequired;
2550
+ this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
2551
+ this.logger = logger ?? createLogger({ name: "llmist:executor" });
2552
+ this.errorFormatter = new GadgetErrorFormatter(errorFormatterOptions);
2553
+ this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
2554
+ }
2555
+ logger;
2556
+ errorFormatter;
2557
+ argPrefix;
2558
+ /**
2559
+ * Creates a promise that rejects with a TimeoutException after the specified timeout.
2560
+ */
2561
+ createTimeoutPromise(gadgetName, timeoutMs) {
2562
+ return new Promise((_, reject) => {
2563
+ setTimeout(() => {
2564
+ reject(new TimeoutException(gadgetName, timeoutMs));
2565
+ }, timeoutMs);
2566
+ });
2567
+ }
2568
+ // Execute a gadget call asynchronously
2569
+ async execute(call) {
2570
+ const startTime = Date.now();
2571
+ this.logger.debug("Executing gadget", {
2572
+ gadgetName: call.gadgetName,
2573
+ invocationId: call.invocationId,
2574
+ parameters: call.parameters
2575
+ });
2576
+ const rawParameters = call.parameters ?? {};
2577
+ let validatedParameters = rawParameters;
2578
+ try {
2579
+ const gadget = this.registry.get(call.gadgetName);
2580
+ if (!gadget) {
2581
+ this.logger.error("Gadget not found", { gadgetName: call.gadgetName });
2582
+ const availableGadgets = this.registry.getNames();
2583
+ return {
2584
+ gadgetName: call.gadgetName,
2585
+ invocationId: call.invocationId,
2586
+ parameters: call.parameters ?? {},
2587
+ error: this.errorFormatter.formatRegistryError(call.gadgetName, availableGadgets),
2588
+ executionTimeMs: Date.now() - startTime
2589
+ };
2590
+ }
2591
+ if (call.parseError || !call.parameters) {
2592
+ this.logger.error("Gadget parameter parse error", {
2593
+ gadgetName: call.gadgetName,
2594
+ parseError: call.parseError,
2595
+ rawParameters: call.parametersRaw
2596
+ });
2597
+ const parseErrorMessage = call.parseError ?? "Failed to parse parameters";
2598
+ return {
2599
+ gadgetName: call.gadgetName,
2600
+ invocationId: call.invocationId,
2601
+ parameters: {},
2602
+ error: this.errorFormatter.formatParseError(call.gadgetName, parseErrorMessage, gadget),
2603
+ executionTimeMs: Date.now() - startTime
2604
+ };
2605
+ }
2606
+ let schemaAwareParameters = rawParameters;
2607
+ const hasBlockFormat = call.parametersRaw?.includes(this.argPrefix);
2608
+ if (gadget.parameterSchema && hasBlockFormat) {
2609
+ try {
2610
+ const cleanedRaw = stripMarkdownFences(call.parametersRaw);
2611
+ const initialParse = parseBlockParams(cleanedRaw, { argPrefix: this.argPrefix });
2612
+ const parametersWereModified = !this.deepEquals(rawParameters, initialParse);
2613
+ if (parametersWereModified) {
2614
+ this.logger.debug("Parameters modified by interceptor, skipping re-parse", {
2615
+ gadgetName: call.gadgetName
2616
+ });
2617
+ schemaAwareParameters = rawParameters;
2618
+ } else {
2619
+ schemaAwareParameters = parseBlockParams(cleanedRaw, {
2620
+ argPrefix: this.argPrefix,
2621
+ schema: gadget.parameterSchema
2622
+ });
2623
+ this.logger.debug("Re-parsed parameters with schema", {
2624
+ gadgetName: call.gadgetName,
2625
+ original: rawParameters,
2626
+ schemaAware: schemaAwareParameters
2627
+ });
2628
+ }
2629
+ } catch (error) {
2630
+ this.logger.warn("Schema-aware re-parsing failed, using original parameters", {
2631
+ gadgetName: call.gadgetName,
2632
+ error: error instanceof Error ? error.message : String(error)
2633
+ });
2634
+ schemaAwareParameters = rawParameters;
2635
+ }
2636
+ }
2637
+ if (gadget.parameterSchema) {
2638
+ const validationResult = gadget.parameterSchema.safeParse(schemaAwareParameters);
2639
+ if (!validationResult.success) {
2640
+ const validationError = this.errorFormatter.formatValidationError(
2641
+ call.gadgetName,
2642
+ validationResult.error,
2643
+ gadget
2644
+ );
2645
+ this.logger.error("Gadget parameter validation failed", {
2646
+ gadgetName: call.gadgetName,
2647
+ issueCount: validationResult.error.issues.length
2648
+ });
2649
+ return {
2650
+ gadgetName: call.gadgetName,
2651
+ invocationId: call.invocationId,
2652
+ parameters: schemaAwareParameters,
2653
+ error: validationError,
2654
+ executionTimeMs: Date.now() - startTime
2655
+ };
2656
+ }
2657
+ validatedParameters = validationResult.data;
2658
+ } else {
2659
+ validatedParameters = schemaAwareParameters;
2660
+ }
2661
+ const timeoutMs = gadget.timeoutMs ?? this.defaultGadgetTimeoutMs;
2662
+ let result;
2663
+ if (timeoutMs && timeoutMs > 0) {
2664
+ this.logger.debug("Executing gadget with timeout", {
2665
+ gadgetName: call.gadgetName,
2666
+ timeoutMs
2667
+ });
2668
+ result = await Promise.race([
2669
+ Promise.resolve(gadget.execute(validatedParameters)),
2670
+ this.createTimeoutPromise(call.gadgetName, timeoutMs)
2671
+ ]);
2672
+ } else {
2673
+ result = await Promise.resolve(gadget.execute(validatedParameters));
2674
+ }
2675
+ const executionTimeMs = Date.now() - startTime;
2676
+ this.logger.info("Gadget executed successfully", {
2677
+ gadgetName: call.gadgetName,
2678
+ invocationId: call.invocationId,
2679
+ executionTimeMs
2680
+ });
2681
+ this.logger.debug("Gadget result", {
2682
+ gadgetName: call.gadgetName,
2683
+ invocationId: call.invocationId,
2684
+ parameters: validatedParameters,
2685
+ result,
2686
+ executionTimeMs
2687
+ });
2688
+ return {
2689
+ gadgetName: call.gadgetName,
2690
+ invocationId: call.invocationId,
2691
+ parameters: validatedParameters,
2692
+ result,
2693
+ executionTimeMs
2694
+ };
2695
+ } catch (error) {
2696
+ if (error instanceof BreakLoopException) {
2697
+ this.logger.info("Gadget requested loop termination", {
2698
+ gadgetName: call.gadgetName,
2699
+ message: error.message
2700
+ });
2701
+ return {
2702
+ gadgetName: call.gadgetName,
2703
+ invocationId: call.invocationId,
2704
+ parameters: validatedParameters,
2705
+ result: error.message,
2706
+ breaksLoop: true,
2707
+ executionTimeMs: Date.now() - startTime
2708
+ };
2709
+ }
2710
+ if (error instanceof TimeoutException) {
2711
+ this.logger.error("Gadget execution timed out", {
2712
+ gadgetName: call.gadgetName,
2713
+ timeoutMs: error.timeoutMs,
2714
+ executionTimeMs: Date.now() - startTime
2715
+ });
2716
+ return {
2717
+ gadgetName: call.gadgetName,
2718
+ invocationId: call.invocationId,
2719
+ parameters: validatedParameters,
2720
+ error: error.message,
2721
+ executionTimeMs: Date.now() - startTime
2722
+ };
2723
+ }
2724
+ if (error instanceof HumanInputException) {
2725
+ this.logger.info("Gadget requested human input", {
2726
+ gadgetName: call.gadgetName,
2727
+ question: error.question
2728
+ });
2729
+ if (this.onHumanInputRequired) {
2730
+ try {
2731
+ const answer = await this.onHumanInputRequired(error.question);
2732
+ this.logger.debug("Human input received", {
2733
+ gadgetName: call.gadgetName,
2734
+ answerLength: answer.length
2735
+ });
2736
+ return {
2737
+ gadgetName: call.gadgetName,
2738
+ invocationId: call.invocationId,
2739
+ parameters: validatedParameters,
2740
+ result: answer,
2741
+ executionTimeMs: Date.now() - startTime
2742
+ };
2743
+ } catch (inputError) {
2744
+ this.logger.error("Human input callback error", {
2745
+ gadgetName: call.gadgetName,
2746
+ error: inputError instanceof Error ? inputError.message : String(inputError)
2747
+ });
2748
+ return {
2749
+ gadgetName: call.gadgetName,
2750
+ invocationId: call.invocationId,
2751
+ parameters: validatedParameters,
2752
+ error: inputError instanceof Error ? inputError.message : String(inputError),
2753
+ executionTimeMs: Date.now() - startTime
2754
+ };
2755
+ }
2756
+ }
2757
+ this.logger.warn("Human input required but no callback provided", {
2758
+ gadgetName: call.gadgetName
2759
+ });
2760
+ return {
2761
+ gadgetName: call.gadgetName,
2762
+ invocationId: call.invocationId,
2763
+ parameters: validatedParameters,
2764
+ error: "Human input required but not available (stdin is not interactive)",
2765
+ executionTimeMs: Date.now() - startTime
2766
+ };
2043
2767
  }
2044
- }
2045
- const remainingText = this.takeTextUntil(this.buffer.length);
2046
- if (remainingText !== void 0) {
2047
- yield { type: "text", content: remainingText };
2768
+ const executionTimeMs = Date.now() - startTime;
2769
+ this.logger.error("Gadget execution failed", {
2770
+ gadgetName: call.gadgetName,
2771
+ error: error instanceof Error ? error.message : String(error),
2772
+ executionTimeMs
2773
+ });
2774
+ return {
2775
+ gadgetName: call.gadgetName,
2776
+ invocationId: call.invocationId,
2777
+ parameters: validatedParameters,
2778
+ error: error instanceof Error ? error.message : String(error),
2779
+ executionTimeMs
2780
+ };
2048
2781
  }
2049
2782
  }
2050
- // Reset parser state (note: global invocation counter is NOT reset to ensure unique IDs)
2051
- reset() {
2052
- this.buffer = "";
2053
- this.lastReportedTextLength = 0;
2783
+ // Execute multiple gadget calls in parallel
2784
+ async executeAll(calls) {
2785
+ return Promise.all(calls.map((call) => this.execute(call)));
2786
+ }
2787
+ /**
2788
+ * Deep equality check for objects/arrays.
2789
+ * Used to detect if parameters were modified by an interceptor.
2790
+ */
2791
+ deepEquals(a, b) {
2792
+ if (a === b) return true;
2793
+ if (a === null || b === null) return a === b;
2794
+ if (typeof a !== typeof b) return false;
2795
+ if (typeof a !== "object") return a === b;
2796
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
2797
+ if (Array.isArray(a) && Array.isArray(b)) {
2798
+ if (a.length !== b.length) return false;
2799
+ return a.every((val, i) => this.deepEquals(val, b[i]));
2800
+ }
2801
+ const aObj = a;
2802
+ const bObj = b;
2803
+ const aKeys = Object.keys(aObj);
2804
+ const bKeys = Object.keys(bObj);
2805
+ if (aKeys.length !== bKeys.length) return false;
2806
+ return aKeys.every((key) => this.deepEquals(aObj[key], bObj[key]));
2054
2807
  }
2055
2808
  };
2056
2809
  }
@@ -2093,7 +2846,8 @@ var init_stream_processor = __esm({
2093
2846
  options.registry,
2094
2847
  options.onHumanInputRequired,
2095
2848
  this.logger.getSubLogger({ name: "executor" }),
2096
- options.defaultGadgetTimeoutMs
2849
+ options.defaultGadgetTimeoutMs,
2850
+ { argPrefix: options.gadgetArgPrefix }
2097
2851
  );
2098
2852
  }
2099
2853
  /**
@@ -2462,6 +3216,7 @@ var init_agent = __esm({
2462
3216
  init_model_shortcuts();
2463
3217
  init_output_viewer();
2464
3218
  init_logger();
3219
+ init_manager();
2465
3220
  init_gadget_output_store();
2466
3221
  init_agent_internal_key();
2467
3222
  init_conversation_manager();
@@ -2492,6 +3247,8 @@ var init_agent = __esm({
2492
3247
  outputStore;
2493
3248
  outputLimitEnabled;
2494
3249
  outputLimitCharLimit;
3250
+ // Context compaction
3251
+ compactionManager;
2495
3252
  /**
2496
3253
  * Creates a new Agent instance.
2497
3254
  * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
@@ -2551,6 +3308,14 @@ var init_agent = __esm({
2551
3308
  if (options.userPrompt) {
2552
3309
  this.conversation.addUserMessage(options.userPrompt);
2553
3310
  }
3311
+ const compactionEnabled = options.compactionConfig?.enabled ?? true;
3312
+ if (compactionEnabled) {
3313
+ this.compactionManager = new CompactionManager(
3314
+ this.client,
3315
+ this.model,
3316
+ options.compactionConfig
3317
+ );
3318
+ }
2554
3319
  }
2555
3320
  /**
2556
3321
  * Get the gadget registry for this agent.
@@ -2573,6 +3338,53 @@ var init_agent = __esm({
2573
3338
  getRegistry() {
2574
3339
  return this.registry;
2575
3340
  }
3341
+ /**
3342
+ * Manually trigger context compaction.
3343
+ *
3344
+ * Forces compaction regardless of threshold. Useful for:
3345
+ * - Pre-emptive context management before expected long operations
3346
+ * - Testing compaction behavior
3347
+ *
3348
+ * @returns CompactionEvent if compaction was performed, null if not configured or no history
3349
+ *
3350
+ * @example
3351
+ * ```typescript
3352
+ * const agent = await LLMist.createAgent()
3353
+ * .withModel('sonnet')
3354
+ * .withCompaction()
3355
+ * .ask('...');
3356
+ *
3357
+ * // Manually compact before a long operation
3358
+ * const event = await agent.compact();
3359
+ * if (event) {
3360
+ * console.log(`Saved ${event.tokensBefore - event.tokensAfter} tokens`);
3361
+ * }
3362
+ * ```
3363
+ */
3364
+ async compact() {
3365
+ if (!this.compactionManager) {
3366
+ return null;
3367
+ }
3368
+ return this.compactionManager.compact(this.conversation, -1);
3369
+ }
3370
+ /**
3371
+ * Get compaction statistics.
3372
+ *
3373
+ * @returns CompactionStats if compaction is enabled, null otherwise
3374
+ *
3375
+ * @example
3376
+ * ```typescript
3377
+ * const stats = agent.getCompactionStats();
3378
+ * if (stats) {
3379
+ * console.log(`Total compactions: ${stats.totalCompactions}`);
3380
+ * console.log(`Tokens saved: ${stats.totalTokensSaved}`);
3381
+ * console.log(`Current usage: ${stats.currentUsage.percent.toFixed(1)}%`);
3382
+ * }
3383
+ * ```
3384
+ */
3385
+ getCompactionStats() {
3386
+ return this.compactionManager?.getStats() ?? null;
3387
+ }
2576
3388
  /**
2577
3389
  * Run the agent loop.
2578
3390
  * Clean, simple orchestration - all complexity is in StreamProcessor.
@@ -2593,6 +3405,30 @@ var init_agent = __esm({
2593
3405
  while (currentIteration < this.maxIterations) {
2594
3406
  this.logger.debug("Starting iteration", { iteration: currentIteration });
2595
3407
  try {
3408
+ if (this.compactionManager) {
3409
+ const compactionEvent = await this.compactionManager.checkAndCompact(
3410
+ this.conversation,
3411
+ currentIteration
3412
+ );
3413
+ if (compactionEvent) {
3414
+ this.logger.info("Context compacted", {
3415
+ strategy: compactionEvent.strategy,
3416
+ tokensBefore: compactionEvent.tokensBefore,
3417
+ tokensAfter: compactionEvent.tokensAfter
3418
+ });
3419
+ yield { type: "compaction", event: compactionEvent };
3420
+ await this.safeObserve(async () => {
3421
+ if (this.hooks.observers?.onCompaction) {
3422
+ await this.hooks.observers.onCompaction({
3423
+ iteration: currentIteration,
3424
+ event: compactionEvent,
3425
+ stats: this.compactionManager.getStats(),
3426
+ logger: this.logger
3427
+ });
3428
+ }
3429
+ });
3430
+ }
3431
+ }
2596
3432
  let llmOptions = {
2597
3433
  model: this.model,
2598
3434
  messages: this.conversation.getMessages(),
@@ -2612,6 +3448,7 @@ var init_agent = __esm({
2612
3448
  if (this.hooks.controllers?.beforeLLMCall) {
2613
3449
  const context = {
2614
3450
  iteration: currentIteration,
3451
+ maxIterations: this.maxIterations,
2615
3452
  options: llmOptions,
2616
3453
  logger: this.logger
2617
3454
  };
@@ -2676,12 +3513,17 @@ var init_agent = __esm({
2676
3513
  });
2677
3514
  let finalMessage = result.finalMessage;
2678
3515
  if (this.hooks.controllers?.afterLLMCall) {
3516
+ const gadgetCallCount = result.outputs.filter(
3517
+ (output) => output.type === "gadget_result"
3518
+ ).length;
2679
3519
  const context = {
2680
3520
  iteration: currentIteration,
3521
+ maxIterations: this.maxIterations,
2681
3522
  options: llmOptions,
2682
3523
  finishReason: result.finishReason,
2683
3524
  usage: result.usage,
2684
3525
  finalMessage: result.finalMessage,
3526
+ gadgetCallCount,
2685
3527
  logger: this.logger
2686
3528
  };
2687
3529
  const action = await this.hooks.controllers.afterLLMCall(context);
@@ -2943,6 +3785,7 @@ var init_builder = __esm({
2943
3785
  defaultGadgetTimeoutMs;
2944
3786
  gadgetOutputLimit;
2945
3787
  gadgetOutputLimitPercent;
3788
+ compactionConfig;
2946
3789
  constructor(client) {
2947
3790
  this.client = client;
2948
3791
  }
@@ -3338,6 +4181,57 @@ var init_builder = __esm({
3338
4181
  this.gadgetOutputLimitPercent = percent;
3339
4182
  return this;
3340
4183
  }
4184
+ /**
4185
+ * Configure context compaction.
4186
+ *
4187
+ * Context compaction automatically manages conversation history to prevent
4188
+ * context window overflow in long-running agent conversations.
4189
+ *
4190
+ * @param config - Compaction configuration options
4191
+ * @returns This builder for chaining
4192
+ *
4193
+ * @example
4194
+ * ```typescript
4195
+ * // Custom thresholds
4196
+ * .withCompaction({
4197
+ * triggerThresholdPercent: 70,
4198
+ * targetPercent: 40,
4199
+ * preserveRecentTurns: 10,
4200
+ * })
4201
+ *
4202
+ * // Different strategy
4203
+ * .withCompaction({
4204
+ * strategy: 'sliding-window',
4205
+ * })
4206
+ *
4207
+ * // With callback
4208
+ * .withCompaction({
4209
+ * onCompaction: (event) => {
4210
+ * console.log(`Saved ${event.tokensBefore - event.tokensAfter} tokens`);
4211
+ * }
4212
+ * })
4213
+ * ```
4214
+ */
4215
+ withCompaction(config) {
4216
+ this.compactionConfig = { ...config, enabled: config.enabled ?? true };
4217
+ return this;
4218
+ }
4219
+ /**
4220
+ * Disable context compaction.
4221
+ *
4222
+ * By default, compaction is enabled. Use this method to explicitly disable it.
4223
+ *
4224
+ * @returns This builder for chaining
4225
+ *
4226
+ * @example
4227
+ * ```typescript
4228
+ * .withoutCompaction() // Disable automatic compaction
4229
+ * ```
4230
+ */
4231
+ withoutCompaction() {
4232
+ this.compactionConfig = { enabled: false };
4233
+ return this;
4234
+ }
3341
4235
  /**
3342
4236
  * Add a synthetic gadget call to the conversation history.
3343
4237
  *
@@ -3453,7 +4347,8 @@ ${endPrefix}`
3453
4347
  shouldContinueAfterError: this.shouldContinueAfterError,
3454
4348
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
3455
4349
  gadgetOutputLimit: this.gadgetOutputLimit,
3456
- gadgetOutputLimitPercent: this.gadgetOutputLimitPercent
4350
+ gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
4351
+ compactionConfig: this.compactionConfig
3457
4352
  };
3458
4353
  return new Agent(AGENT_INTERNAL_KEY, options);
3459
4354
  }
@@ -3555,7 +4450,8 @@ ${endPrefix}`
3555
4450
  shouldContinueAfterError: this.shouldContinueAfterError,
3556
4451
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
3557
4452
  gadgetOutputLimit: this.gadgetOutputLimit,
3558
- gadgetOutputLimitPercent: this.gadgetOutputLimitPercent
4453
+ gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
4454
+ compactionConfig: this.compactionConfig
3559
4455
  };
3560
4456
  return new Agent(AGENT_INTERNAL_KEY, options);
3561
4457
  }
@@ -5540,19 +6436,44 @@ var init_client = __esm({
5540
6436
  var testing_exports = {};
5541
6437
  __export(testing_exports, {
5542
6438
  MockBuilder: () => MockBuilder,
6439
+ MockConversationManager: () => MockConversationManager,
5543
6440
  MockGadgetBuilder: () => MockGadgetBuilder,
5544
6441
  MockManager: () => MockManager,
6442
+ MockPromptRecorder: () => MockPromptRecorder,
5545
6443
  MockProviderAdapter: () => MockProviderAdapter,
6444
+ collectOutput: () => collectOutput,
6445
+ collectStream: () => collectStream,
6446
+ collectStreamText: () => collectStreamText,
6447
+ createAssistantMessage: () => createAssistantMessage,
6448
+ createConversation: () => createConversation,
6449
+ createConversationWithGadgets: () => createConversationWithGadgets,
6450
+ createEmptyStream: () => createEmptyStream,
6451
+ createErrorStream: () => createErrorStream,
6452
+ createLargeConversation: () => createLargeConversation,
6453
+ createMinimalConversation: () => createMinimalConversation,
5546
6454
  createMockAdapter: () => createMockAdapter,
5547
6455
  createMockClient: () => createMockClient,
6456
+ createMockConversationManager: () => createMockConversationManager,
5548
6457
  createMockGadget: () => createMockGadget,
6458
+ createMockPrompt: () => createMockPrompt,
6459
+ createMockReadable: () => createMockReadable,
5549
6460
  createMockStream: () => createMockStream,
6461
+ createMockWritable: () => createMockWritable,
6462
+ createSystemMessage: () => createSystemMessage,
6463
+ createTestEnvironment: () => createTestEnvironment,
6464
+ createTestStream: () => createTestStream,
5550
6465
  createTextMockStream: () => createTextMockStream,
6466
+ createTextStream: () => createTextStream,
6467
+ createUserMessage: () => createUserMessage,
6468
+ estimateTokens: () => estimateTokens,
6469
+ getBufferedOutput: () => getBufferedOutput,
5551
6470
  getMockManager: () => getMockManager,
6471
+ getStreamFinalChunk: () => getStreamFinalChunk,
5552
6472
  mockGadget: () => mockGadget,
5553
6473
  mockLLM: () => mockLLM,
5554
6474
  testGadget: () => testGadget,
5555
- testGadgetBatch: () => testGadgetBatch
6475
+ testGadgetBatch: () => testGadgetBatch,
6476
+ waitFor: () => waitFor
5556
6477
  });
5557
6478
  module.exports = __toCommonJS(testing_exports);
5558
6479
 
@@ -6461,21 +7382,477 @@ var MockGadgetBuilder = class {
6461
7382
  function mockGadget() {
6462
7383
  return new MockGadgetBuilder();
6463
7384
  }
7385
+
7386
+ // src/testing/stream-helpers.ts
7387
+ function createTestStream(chunks) {
7388
+ return async function* () {
7389
+ for (const chunk of chunks) {
7390
+ yield chunk;
7391
+ }
7392
+ }();
7393
+ }
7394
+ function createTextStream(text, options) {
7395
+ return async function* () {
7396
+ if (options?.delayMs) {
7397
+ await sleep2(options.delayMs);
7398
+ }
7399
+ const chunkSize = options?.chunkSize ?? text.length;
7400
+ const chunks = [];
7401
+ for (let i = 0; i < text.length; i += chunkSize) {
7402
+ chunks.push(text.slice(i, i + chunkSize));
7403
+ }
7404
+ for (let i = 0; i < chunks.length; i++) {
7405
+ const isLast = i === chunks.length - 1;
7406
+ const chunk = { text: chunks[i] };
7407
+ if (isLast) {
7408
+ chunk.finishReason = options?.finishReason ?? "stop";
7409
+ const inputTokens = Math.ceil(text.length / 4);
7410
+ const outputTokens = Math.ceil(text.length / 4);
7411
+ chunk.usage = options?.usage ?? {
7412
+ inputTokens,
7413
+ outputTokens,
7414
+ totalTokens: inputTokens + outputTokens
7415
+ };
7416
+ }
7417
+ yield chunk;
7418
+ if (options?.chunkDelayMs && !isLast) {
7419
+ await sleep2(options.chunkDelayMs);
7420
+ }
7421
+ }
7422
+ }();
7423
+ }
7424
+ async function collectStream(stream2) {
7425
+ const chunks = [];
7426
+ for await (const chunk of stream2) {
7427
+ chunks.push(chunk);
7428
+ }
7429
+ return chunks;
7430
+ }
7431
+ async function collectStreamText(stream2) {
7432
+ let text = "";
7433
+ for await (const chunk of stream2) {
7434
+ text += chunk.text ?? "";
7435
+ }
7436
+ return text;
7437
+ }
7438
+ async function getStreamFinalChunk(stream2) {
7439
+ let lastChunk;
7440
+ for await (const chunk of stream2) {
7441
+ lastChunk = chunk;
7442
+ }
7443
+ return lastChunk;
7444
+ }
7445
+ function createEmptyStream() {
7446
+ return async function* () {
7447
+ }();
7448
+ }
7449
+ function createErrorStream(chunksBeforeError, error) {
7450
+ return async function* () {
7451
+ for (const chunk of chunksBeforeError) {
7452
+ yield chunk;
7453
+ }
7454
+ throw error;
7455
+ }();
7456
+ }
7457
+ function sleep2(ms) {
7458
+ return new Promise((resolve) => setTimeout(resolve, ms));
7459
+ }
7460
+
7461
+ // src/testing/conversation-fixtures.ts
7462
+ function createConversation(turnCount, options) {
7463
+ const messages = [];
7464
+ const userPrefix = options?.userPrefix ?? "User message";
7465
+ const assistantPrefix = options?.assistantPrefix ?? "Assistant response";
7466
+ const contentLength = options?.contentLength ?? 100;
7467
+ for (let i = 0; i < turnCount; i++) {
7468
+ const padding = " ".repeat(Math.max(0, contentLength - 30));
7469
+ messages.push({
7470
+ role: "user",
7471
+ content: `${userPrefix} ${i + 1}: This is turn ${i + 1} of the conversation.${padding}`
7472
+ });
7473
+ messages.push({
7474
+ role: "assistant",
7475
+ content: `${assistantPrefix} ${i + 1}: I acknowledge turn ${i + 1}.${padding}`
7476
+ });
7477
+ }
7478
+ return messages;
7479
+ }
7480
+ function createConversationWithGadgets(turnCount, gadgetCallsPerTurn = 1, options) {
7481
+ const messages = [];
7482
+ const gadgetNames = options?.gadgetNames ?? ["search", "calculate", "read"];
7483
+ const contentLength = options?.contentLength ?? 50;
7484
+ let gadgetIndex = 0;
7485
+ for (let turn = 0; turn < turnCount; turn++) {
7486
+ messages.push({
7487
+ role: "user",
7488
+ content: `User request ${turn + 1}${"x".repeat(contentLength)}`
7489
+ });
7490
+ for (let g = 0; g < gadgetCallsPerTurn; g++) {
7491
+ const gadgetName = gadgetNames[gadgetIndex % gadgetNames.length];
7492
+ gadgetIndex++;
7493
+ messages.push({
7494
+ role: "assistant",
7495
+ content: `!!!GADGET_START:${gadgetName}
7496
+ !!!ARG:query
7497
+ test query ${turn}-${g}
7498
+ !!!GADGET_END`
7499
+ });
7500
+ messages.push({
7501
+ role: "user",
7502
+ content: `Result: Gadget ${gadgetName} returned result for query ${turn}-${g}`
7503
+ });
7504
+ }
7505
+ messages.push({
7506
+ role: "assistant",
7507
+ content: `Final response for turn ${turn + 1}${"y".repeat(contentLength)}`
7508
+ });
7509
+ }
7510
+ return messages;
7511
+ }
7512
+ function estimateTokens(messages) {
7513
+ return Math.ceil(
7514
+ messages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0) / 4
7515
+ );
7516
+ }
7517
+ function createUserMessage(content) {
7518
+ return { role: "user", content };
7519
+ }
7520
+ function createAssistantMessage(content) {
7521
+ return { role: "assistant", content };
7522
+ }
7523
+ function createSystemMessage(content) {
7524
+ return { role: "system", content };
7525
+ }
7526
+ function createMinimalConversation() {
7527
+ return [
7528
+ { role: "user", content: "Hello" },
7529
+ { role: "assistant", content: "Hi there!" }
7530
+ ];
7531
+ }
7532
+ function createLargeConversation(targetTokens, options) {
7533
+ const tokensPerTurn = options?.tokensPerTurn ?? 200;
7534
+ const turnsNeeded = Math.ceil(targetTokens / tokensPerTurn);
7535
+ const charsPerMessage = Math.floor(tokensPerTurn * 4 / 2);
7536
+ return createConversation(turnsNeeded, {
7537
+ contentLength: charsPerMessage
7538
+ });
7539
+ }
7540
+
7541
+ // src/testing/mock-conversation.ts
7542
+ var MockConversationManager = class {
7543
+ history;
7544
+ baseMessages;
7545
+ replacementHistory;
7546
+ replaceHistoryCallCount = 0;
7547
+ addedMessages = [];
7548
+ constructor(history = [], baseMessages = []) {
7549
+ this.history = [...history];
7550
+ this.baseMessages = [...baseMessages];
7551
+ }
7552
+ addUserMessage(content) {
7553
+ const msg = { role: "user", content };
7554
+ this.history.push(msg);
7555
+ this.addedMessages.push(msg);
7556
+ }
7557
+ addAssistantMessage(content) {
7558
+ const msg = { role: "assistant", content };
7559
+ this.history.push(msg);
7560
+ this.addedMessages.push(msg);
7561
+ }
7562
+ addGadgetCall(gadgetName, parameters, result) {
7563
+ const assistantMsg = {
7564
+ role: "assistant",
7565
+ content: `!!!GADGET_START:${gadgetName}
7566
+ ${JSON.stringify(parameters)}
7567
+ !!!GADGET_END`
7568
+ };
7569
+ const resultMsg = {
7570
+ role: "user",
7571
+ content: `Result: ${result}`
7572
+ };
7573
+ this.history.push(assistantMsg);
7574
+ this.history.push(resultMsg);
7575
+ this.addedMessages.push(assistantMsg);
7576
+ this.addedMessages.push(resultMsg);
7577
+ }
7578
+ getMessages() {
7579
+ return [...this.baseMessages, ...this.history];
7580
+ }
7581
+ getHistoryMessages() {
7582
+ return [...this.history];
7583
+ }
7584
+ getBaseMessages() {
7585
+ return [...this.baseMessages];
7586
+ }
7587
+ replaceHistory(newHistory) {
7588
+ this.replacementHistory = [...newHistory];
7589
+ this.history = [...newHistory];
7590
+ this.replaceHistoryCallCount++;
7591
+ }
7592
+ // ============================================
7593
+ // Test Helper Methods
7594
+ // ============================================
7595
+ /**
7596
+ * Check if replaceHistory was called.
7597
+ */
7598
+ wasReplaceHistoryCalled() {
7599
+ return this.replaceHistoryCallCount > 0;
7600
+ }
7601
+ /**
7602
+ * Get the number of times replaceHistory was called.
7603
+ */
7604
+ getReplaceHistoryCallCount() {
7605
+ return this.replaceHistoryCallCount;
7606
+ }
7607
+ /**
7608
+ * Get the most recent history passed to replaceHistory.
7609
+ * Returns undefined if replaceHistory was never called.
7610
+ */
7611
+ getReplacementHistory() {
7612
+ return this.replacementHistory;
7613
+ }
7614
+ /**
7615
+ * Get all messages that were added via add* methods.
7616
+ */
7617
+ getAddedMessages() {
7618
+ return [...this.addedMessages];
7619
+ }
7620
+ /**
7621
+ * Reset all tracking state while preserving the conversation.
7622
+ */
7623
+ resetTracking() {
7624
+ this.replacementHistory = void 0;
7625
+ this.replaceHistoryCallCount = 0;
7626
+ this.addedMessages = [];
7627
+ }
7628
+ /**
7629
+ * Completely reset the mock to initial state.
7630
+ * Note: baseMessages cannot be changed after construction.
7631
+ */
7632
+ reset(history = []) {
7633
+ this.history = [...history];
7634
+ this.resetTracking();
7635
+ }
7636
+ /**
7637
+ * Set the history directly (for test setup).
7638
+ */
7639
+ setHistory(messages) {
7640
+ this.history = [...messages];
7641
+ }
7642
+ /**
7643
+ * Get the current history length.
7644
+ */
7645
+ getHistoryLength() {
7646
+ return this.history.length;
7647
+ }
7648
+ /**
7649
+ * Get total message count (base + history).
7650
+ */
7651
+ getTotalMessageCount() {
7652
+ return this.baseMessages.length + this.history.length;
7653
+ }
7654
+ };
7655
+ function createMockConversationManager(turnCount, baseMessages = []) {
7656
+ const history = [];
7657
+ for (let i = 0; i < turnCount; i++) {
7658
+ history.push({
7659
+ role: "user",
7660
+ content: `User message ${i + 1}: This is turn ${i + 1} of the conversation.`
7661
+ });
7662
+ history.push({
7663
+ role: "assistant",
7664
+ content: `Assistant response ${i + 1}: I acknowledge turn ${i + 1}.`
7665
+ });
7666
+ }
7667
+ return new MockConversationManager(history, baseMessages);
7668
+ }
7669
+
7670
+ // src/testing/cli-helpers.ts
7671
+ var import_node_stream = require("stream");
7672
+ function createTestEnvironment(options = {}) {
7673
+ const stdin = createMockReadable(options.stdin);
7674
+ const stdout = new import_node_stream.PassThrough();
7675
+ const stderr = new import_node_stream.PassThrough();
7676
+ let exitCode;
7677
+ return {
7678
+ stdin,
7679
+ stdout,
7680
+ stderr,
7681
+ isTTY: options.isTTY ?? false,
7682
+ argv: options.argv ?? ["node", "llmist"],
7683
+ env: { ...filterDefinedEnv(process.env), ...options.env },
7684
+ get exitCode() {
7685
+ return exitCode;
7686
+ },
7687
+ setExitCode: (code) => {
7688
+ exitCode = code;
7689
+ }
7690
+ };
7691
+ }
7692
+ function createMockReadable(input) {
7693
+ if (!input) {
7694
+ const stream3 = new import_node_stream.Readable({ read() {
7695
+ } });
7696
+ stream3.push(null);
7697
+ return stream3;
7698
+ }
7699
+ const content = Array.isArray(input) ? `${input.join("\n")}
7700
+ ` : input;
7701
+ const stream2 = new import_node_stream.Readable({ read() {
7702
+ } });
7703
+ stream2.push(content);
7704
+ stream2.push(null);
7705
+ return stream2;
7706
+ }
7707
+ function createMockWritable() {
7708
+ const chunks = [];
7709
+ const stream2 = new import_node_stream.Writable({
7710
+ write(chunk, _encoding, callback) {
7711
+ chunks.push(Buffer.from(chunk));
7712
+ callback();
7713
+ }
7714
+ });
7715
+ stream2.getData = () => Buffer.concat(chunks).toString("utf8");
7716
+ return stream2;
7717
+ }
7718
+ async function collectOutput(stream2, timeout = 5e3) {
7719
+ return new Promise((resolve, reject) => {
7720
+ const chunks = [];
7721
+ const timeoutId = setTimeout(() => {
7722
+ resolve(Buffer.concat(chunks).toString("utf8"));
7723
+ }, timeout);
7724
+ stream2.on("data", (chunk) => {
7725
+ chunks.push(Buffer.from(chunk));
7726
+ });
7727
+ stream2.on("end", () => {
7728
+ clearTimeout(timeoutId);
7729
+ resolve(Buffer.concat(chunks).toString("utf8"));
7730
+ });
7731
+ stream2.on("error", (err) => {
7732
+ clearTimeout(timeoutId);
7733
+ reject(err);
7734
+ });
7735
+ });
7736
+ }
7737
+ function getBufferedOutput(stream2) {
7738
+ const chunks = [];
7739
+ for (; ; ) {
7740
+ const chunk = stream2.read();
7741
+ if (chunk === null) break;
7742
+ chunks.push(chunk);
7743
+ }
7744
+ return Buffer.concat(chunks).toString("utf8");
7745
+ }
7746
+ function createMockPrompt(responses) {
7747
+ let index = 0;
7748
+ return async (_question) => {
7749
+ if (index >= responses.length) {
7750
+ throw new Error(`Mock prompt exhausted: no response for question ${index + 1}`);
7751
+ }
7752
+ return responses[index++];
7753
+ };
7754
+ }
7755
+ var MockPromptRecorder = class {
7756
+ responses;
7757
+ index = 0;
7758
+ questions = [];
7759
+ constructor(responses) {
7760
+ this.responses = responses;
7761
+ }
7762
+ /**
7763
+ * The prompt function to use in tests.
7764
+ */
7765
+ prompt = async (question) => {
7766
+ this.questions.push(question);
7767
+ if (this.index >= this.responses.length) {
7768
+ throw new Error(`Mock prompt exhausted after ${this.index} questions`);
7769
+ }
7770
+ return this.responses[this.index++];
7771
+ };
7772
+ /**
7773
+ * Get all questions that were asked.
7774
+ */
7775
+ getQuestions() {
7776
+ return [...this.questions];
7777
+ }
7778
+ /**
7779
+ * Get the number of questions asked.
7780
+ */
7781
+ getQuestionCount() {
7782
+ return this.questions.length;
7783
+ }
7784
+ /**
7785
+ * Reset the recorder state.
7786
+ */
7787
+ reset(newResponses) {
7788
+ this.index = 0;
7789
+ this.questions = [];
7790
+ if (newResponses) {
7791
+ this.responses = newResponses;
7792
+ }
7793
+ }
7794
+ };
7795
+ async function waitFor(condition, timeout = 5e3, interval = 50) {
7796
+ const startTime = Date.now();
7797
+ while (!condition()) {
7798
+ if (Date.now() - startTime > timeout) {
7799
+ throw new Error(`waitFor timed out after ${timeout}ms`);
7800
+ }
7801
+ await sleep3(interval);
7802
+ }
7803
+ }
7804
+ function sleep3(ms) {
7805
+ return new Promise((resolve) => setTimeout(resolve, ms));
7806
+ }
7807
+ function filterDefinedEnv(env) {
7808
+ const result = {};
7809
+ for (const [key, value] of Object.entries(env)) {
7810
+ if (value !== void 0) {
7811
+ result[key] = value;
7812
+ }
7813
+ }
7814
+ return result;
7815
+ }
6464
7816
  // Annotate the CommonJS export names for ESM import in node:
6465
7817
  0 && (module.exports = {
6466
7818
  MockBuilder,
7819
+ MockConversationManager,
6467
7820
  MockGadgetBuilder,
6468
7821
  MockManager,
7822
+ MockPromptRecorder,
6469
7823
  MockProviderAdapter,
7824
+ collectOutput,
7825
+ collectStream,
7826
+ collectStreamText,
7827
+ createAssistantMessage,
7828
+ createConversation,
7829
+ createConversationWithGadgets,
7830
+ createEmptyStream,
7831
+ createErrorStream,
7832
+ createLargeConversation,
7833
+ createMinimalConversation,
6470
7834
  createMockAdapter,
6471
7835
  createMockClient,
7836
+ createMockConversationManager,
6472
7837
  createMockGadget,
7838
+ createMockPrompt,
7839
+ createMockReadable,
6473
7840
  createMockStream,
7841
+ createMockWritable,
7842
+ createSystemMessage,
7843
+ createTestEnvironment,
7844
+ createTestStream,
6474
7845
  createTextMockStream,
7846
+ createTextStream,
7847
+ createUserMessage,
7848
+ estimateTokens,
7849
+ getBufferedOutput,
6475
7850
  getMockManager,
7851
+ getStreamFinalChunk,
6476
7852
  mockGadget,
6477
7853
  mockLLM,
6478
7854
  testGadget,
6479
- testGadgetBatch
7855
+ testGadgetBatch,
7856
+ waitFor
6480
7857
  });
6481
7858
  //# sourceMappingURL=index.cjs.map