llmist 2.5.0 → 3.0.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.
@@ -134,6 +134,12 @@ function isAudioPart(part) {
134
134
  function text(content) {
135
135
  return { type: "text", text: content };
136
136
  }
137
+ function imageFromBase64(data, mediaType) {
138
+ return {
139
+ type: "image",
140
+ source: { type: "base64", mediaType, data }
141
+ };
142
+ }
137
143
  function imageFromUrl(url) {
138
144
  return {
139
145
  type: "image",
@@ -194,6 +200,12 @@ function toBase64(data) {
194
200
  }
195
201
  return Buffer.from(data).toString("base64");
196
202
  }
203
+ function audioFromBase64(data, mediaType) {
204
+ return {
205
+ type: "audio",
206
+ source: { type: "base64", mediaType, data }
207
+ };
208
+ }
197
209
  function audioFromBuffer(buffer, mediaType) {
198
210
  const detectedType = mediaType ?? detectAudioMimeType(buffer);
199
211
  if (!detectedType) {
@@ -240,7 +252,9 @@ var init_input_content = __esm({
240
252
  // WAV (RIFF)
241
253
  { bytes: [82, 73, 70, 70], mimeType: "audio/wav" },
242
254
  // WebM
243
- { bytes: [26, 69, 223, 163], mimeType: "audio/webm" }
255
+ { bytes: [26, 69, 223, 163], mimeType: "audio/webm" },
256
+ // FLAC (fLaC)
257
+ { bytes: [102, 76, 97, 67], mimeType: "audio/flac" }
244
258
  ];
245
259
  }
246
260
  });
@@ -287,13 +301,13 @@ var init_prompt_config = __esm({
287
301
  });
288
302
 
289
303
  // src/core/messages.ts
290
- function normalizeContent(content) {
304
+ function normalizeMessageContent(content) {
291
305
  if (typeof content === "string") {
292
306
  return [{ type: "text", text: content }];
293
307
  }
294
308
  return content;
295
309
  }
296
- function extractText(content) {
310
+ function extractMessageText(content) {
297
311
  if (typeof content === "string") {
298
312
  return content;
299
313
  }
@@ -653,7 +667,17 @@ Produces: { "items": ["first", "second"] }`);
653
667
  this.messages.push({ role: "user", content: parts });
654
668
  return this;
655
669
  }
656
- addGadgetCall(gadget, parameters, result) {
670
+ /**
671
+ * Record a gadget execution result in the message history.
672
+ * Creates an assistant message with the gadget invocation and a user message with the result.
673
+ *
674
+ * @param gadget - Name of the gadget that was executed
675
+ * @param parameters - Parameters that were passed to the gadget
676
+ * @param result - Text result from the gadget execution
677
+ * @param media - Optional media outputs from the gadget
678
+ * @param mediaIds - Optional IDs for the media outputs
679
+ */
680
+ addGadgetCallResult(gadget, parameters, result, media, mediaIds) {
657
681
  const paramStr = this.formatBlockParameters(parameters, "");
658
682
  this.messages.push({
659
683
  role: "assistant",
@@ -661,10 +685,25 @@ Produces: { "items": ["first", "second"] }`);
661
685
  ${paramStr}
662
686
  ${this.endPrefix}`
663
687
  });
664
- this.messages.push({
665
- role: "user",
666
- content: `Result: ${result}`
667
- });
688
+ if (media && media.length > 0 && mediaIds && mediaIds.length > 0) {
689
+ const idRefs = media.map((m, i) => `[Media: ${mediaIds[i]} (${m.kind})]`).join("\n");
690
+ const textWithIds = `Result: ${result}
691
+ ${idRefs}`;
692
+ const parts = [text(textWithIds)];
693
+ for (const item of media) {
694
+ if (item.kind === "image") {
695
+ parts.push(imageFromBase64(item.data, item.mimeType));
696
+ } else if (item.kind === "audio") {
697
+ parts.push(audioFromBase64(item.data, item.mimeType));
698
+ }
699
+ }
700
+ this.messages.push({ role: "user", content: parts });
701
+ } else {
702
+ this.messages.push({
703
+ role: "user",
704
+ content: `Result: ${result}`
705
+ });
706
+ }
668
707
  return this;
669
708
  }
670
709
  /**
@@ -998,22 +1037,226 @@ var init_registry = __esm({
998
1037
  }
999
1038
  });
1000
1039
 
1040
+ // src/gadgets/media-store.ts
1041
+ function getLlmistTmpDir() {
1042
+ return (0, import_node_path2.join)((0, import_node_os.homedir)(), ".llmist", "tmp");
1043
+ }
1044
+ var import_node_crypto, import_promises, import_node_os, import_node_path2, MIME_TO_EXTENSION, MediaStore;
1045
+ var init_media_store = __esm({
1046
+ "src/gadgets/media-store.ts"() {
1047
+ "use strict";
1048
+ import_node_crypto = require("crypto");
1049
+ import_promises = require("fs/promises");
1050
+ import_node_os = require("os");
1051
+ import_node_path2 = require("path");
1052
+ MIME_TO_EXTENSION = {
1053
+ // Images
1054
+ "image/png": ".png",
1055
+ "image/jpeg": ".jpg",
1056
+ "image/gif": ".gif",
1057
+ "image/webp": ".webp",
1058
+ "image/svg+xml": ".svg",
1059
+ "image/bmp": ".bmp",
1060
+ "image/tiff": ".tiff",
1061
+ // Audio
1062
+ "audio/mp3": ".mp3",
1063
+ "audio/mpeg": ".mp3",
1064
+ "audio/wav": ".wav",
1065
+ "audio/webm": ".webm",
1066
+ "audio/ogg": ".ogg",
1067
+ "audio/flac": ".flac",
1068
+ "audio/aac": ".aac",
1069
+ // Video
1070
+ "video/mp4": ".mp4",
1071
+ "video/webm": ".webm",
1072
+ "video/ogg": ".ogv",
1073
+ "video/quicktime": ".mov",
1074
+ "video/x-msvideo": ".avi",
1075
+ // Documents
1076
+ "application/pdf": ".pdf",
1077
+ "application/json": ".json",
1078
+ "text/plain": ".txt",
1079
+ "text/html": ".html",
1080
+ "text/css": ".css",
1081
+ "text/javascript": ".js"
1082
+ };
1083
+ MediaStore = class {
1084
+ items = /* @__PURE__ */ new Map();
1085
+ outputDir;
1086
+ counter = 0;
1087
+ initialized = false;
1088
+ /**
1089
+ * Create a new MediaStore.
1090
+ *
1091
+ * @param sessionId - Optional session ID for the output directory.
1092
+ * If not provided, a random ID is generated.
1093
+ */
1094
+ constructor(sessionId) {
1095
+ const id = sessionId ?? (0, import_node_crypto.randomBytes)(8).toString("hex");
1096
+ this.outputDir = (0, import_node_path2.join)(getLlmistTmpDir(), `media-${id}`);
1097
+ }
1098
+ /**
1099
+ * Get the output directory path.
1100
+ */
1101
+ getOutputDir() {
1102
+ return this.outputDir;
1103
+ }
1104
+ /**
1105
+ * Ensure the output directory exists.
1106
+ * @throws Error if directory creation fails
1107
+ */
1108
+ async ensureDir() {
1109
+ if (this.initialized) return;
1110
+ try {
1111
+ await (0, import_promises.mkdir)(this.outputDir, { recursive: true });
1112
+ this.initialized = true;
1113
+ } catch (error) {
1114
+ throw new Error(
1115
+ `MediaStore: Failed to create directory ${this.outputDir}: ${error instanceof Error ? error.message : String(error)}`
1116
+ );
1117
+ }
1118
+ }
1119
+ /**
1120
+ * Generate a unique media ID.
1121
+ * Format: "media_" + 6 random alphanumeric characters
1122
+ */
1123
+ generateId() {
1124
+ const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
1125
+ let id = "media_";
1126
+ const bytes = (0, import_node_crypto.randomBytes)(6);
1127
+ for (let i = 0; i < 6; i++) {
1128
+ id += chars[bytes[i] % chars.length];
1129
+ }
1130
+ return id;
1131
+ }
1132
+ /**
1133
+ * Get file extension from MIME type.
1134
+ */
1135
+ getExtension(mimeType) {
1136
+ return MIME_TO_EXTENSION[mimeType] ?? ".bin";
1137
+ }
1138
+ /**
1139
+ * Store media and return stored metadata with ID.
1140
+ *
1141
+ * @param media - The media output from a gadget
1142
+ * @param gadgetName - Name of the gadget that created this media
1143
+ * @returns Stored media information including generated ID
1144
+ * @throws Error if file write fails
1145
+ */
1146
+ async store(media, gadgetName) {
1147
+ await this.ensureDir();
1148
+ const id = this.generateId();
1149
+ const ext = this.getExtension(media.mimeType);
1150
+ const filename = media.fileName ?? `${gadgetName}_${String(++this.counter).padStart(3, "0")}${ext}`;
1151
+ const filePath = (0, import_node_path2.join)(this.outputDir, filename);
1152
+ const buffer = Buffer.from(media.data, "base64");
1153
+ try {
1154
+ await (0, import_promises.writeFile)(filePath, buffer);
1155
+ } catch (error) {
1156
+ throw new Error(
1157
+ `MediaStore: Failed to write media file ${filePath}: ${error instanceof Error ? error.message : String(error)}`
1158
+ );
1159
+ }
1160
+ const stored = {
1161
+ id,
1162
+ kind: media.kind,
1163
+ path: filePath,
1164
+ mimeType: media.mimeType,
1165
+ sizeBytes: buffer.length,
1166
+ description: media.description,
1167
+ metadata: media.metadata,
1168
+ gadgetName,
1169
+ createdAt: /* @__PURE__ */ new Date()
1170
+ };
1171
+ this.items.set(id, stored);
1172
+ return stored;
1173
+ }
1174
+ /**
1175
+ * Get stored media by ID.
1176
+ *
1177
+ * @param id - The media ID (e.g., "media_a1b2c3")
1178
+ * @returns The stored media or undefined if not found
1179
+ */
1180
+ get(id) {
1181
+ return this.items.get(id);
1182
+ }
1183
+ /**
1184
+ * Get the actual file path for a media ID.
1185
+ * Convenience method for gadgets that need the raw path.
1186
+ *
1187
+ * @param id - The media ID
1188
+ * @returns The file path or undefined if not found
1189
+ */
1190
+ getPath(id) {
1191
+ return this.items.get(id)?.path;
1192
+ }
1193
+ /**
1194
+ * List all stored media, optionally filtered by kind.
1195
+ *
1196
+ * @param kind - Optional media kind to filter by
1197
+ * @returns Array of stored media items
1198
+ */
1199
+ list(kind) {
1200
+ const all = Array.from(this.items.values());
1201
+ if (kind) {
1202
+ return all.filter((item) => item.kind === kind);
1203
+ }
1204
+ return all;
1205
+ }
1206
+ /**
1207
+ * Get the count of stored media items.
1208
+ */
1209
+ get size() {
1210
+ return this.items.size;
1211
+ }
1212
+ /**
1213
+ * Check if a media ID exists.
1214
+ */
1215
+ has(id) {
1216
+ return this.items.has(id);
1217
+ }
1218
+ /**
1219
+ * Clear in-memory store without deleting files.
1220
+ * Resets the counter but leaves files on disk.
1221
+ */
1222
+ clear() {
1223
+ this.items.clear();
1224
+ this.counter = 0;
1225
+ }
1226
+ /**
1227
+ * Delete all stored files and clear memory.
1228
+ * Removes the entire session directory.
1229
+ */
1230
+ async cleanup() {
1231
+ if (this.initialized) {
1232
+ try {
1233
+ await (0, import_promises.rm)(this.outputDir, { recursive: true, force: true });
1234
+ } catch {
1235
+ }
1236
+ this.initialized = false;
1237
+ }
1238
+ this.clear();
1239
+ }
1240
+ };
1241
+ }
1242
+ });
1243
+
1001
1244
  // src/gadgets/exceptions.ts
1002
- var BreakLoopException, HumanInputException, TimeoutException, AbortError;
1245
+ var TaskCompletionSignal, HumanInputRequiredException, TimeoutException, AbortException;
1003
1246
  var init_exceptions = __esm({
1004
1247
  "src/gadgets/exceptions.ts"() {
1005
1248
  "use strict";
1006
- BreakLoopException = class extends Error {
1249
+ TaskCompletionSignal = class extends Error {
1007
1250
  constructor(message) {
1008
1251
  super(message ?? "Agent loop terminated by gadget");
1009
- this.name = "BreakLoopException";
1252
+ this.name = "TaskCompletionSignal";
1010
1253
  }
1011
1254
  };
1012
- HumanInputException = class extends Error {
1255
+ HumanInputRequiredException = class extends Error {
1013
1256
  question;
1014
1257
  constructor(question) {
1015
1258
  super(`Human input required: ${question}`);
1016
- this.name = "HumanInputException";
1259
+ this.name = "HumanInputRequiredException";
1017
1260
  this.question = question;
1018
1261
  }
1019
1262
  };
@@ -1027,10 +1270,10 @@ var init_exceptions = __esm({
1027
1270
  this.timeoutMs = timeoutMs;
1028
1271
  }
1029
1272
  };
1030
- AbortError = class extends Error {
1273
+ AbortException = class extends Error {
1031
1274
  constructor(message) {
1032
1275
  super(message || "Gadget execution was aborted");
1033
- this.name = "AbortError";
1276
+ this.name = "AbortException";
1034
1277
  }
1035
1278
  };
1036
1279
  }
@@ -1120,7 +1363,7 @@ var init_schema_to_json = __esm({
1120
1363
  });
1121
1364
 
1122
1365
  // src/gadgets/gadget.ts
1123
- function formatParamsAsBlock(params, prefix = "", argPrefix = GADGET_ARG_PREFIX) {
1366
+ function formatParamsForBlockExample(params, prefix = "", argPrefix = GADGET_ARG_PREFIX) {
1124
1367
  const lines = [];
1125
1368
  for (const [key, value] of Object.entries(params)) {
1126
1369
  const fullPath = prefix ? `${prefix}/${key}` : key;
@@ -1128,14 +1371,14 @@ function formatParamsAsBlock(params, prefix = "", argPrefix = GADGET_ARG_PREFIX)
1128
1371
  value.forEach((item, index) => {
1129
1372
  const itemPath = `${fullPath}/${index}`;
1130
1373
  if (typeof item === "object" && item !== null) {
1131
- lines.push(formatParamsAsBlock(item, itemPath, argPrefix));
1374
+ lines.push(formatParamsForBlockExample(item, itemPath, argPrefix));
1132
1375
  } else {
1133
1376
  lines.push(`${argPrefix}${itemPath}`);
1134
1377
  lines.push(String(item));
1135
1378
  }
1136
1379
  });
1137
1380
  } else if (typeof value === "object" && value !== null) {
1138
- lines.push(formatParamsAsBlock(value, fullPath, argPrefix));
1381
+ lines.push(formatParamsForBlockExample(value, fullPath, argPrefix));
1139
1382
  } else {
1140
1383
  lines.push(`${argPrefix}${fullPath}`);
1141
1384
  lines.push(String(value));
@@ -1224,7 +1467,7 @@ function formatSchemaAsPlainText(schema, indent = "", atRoot = true) {
1224
1467
  }
1225
1468
  return lines.join("\n");
1226
1469
  }
1227
- var BaseGadget;
1470
+ var AbstractGadget;
1228
1471
  var init_gadget = __esm({
1229
1472
  "src/gadgets/gadget.ts"() {
1230
1473
  "use strict";
@@ -1232,7 +1475,7 @@ var init_gadget = __esm({
1232
1475
  init_exceptions();
1233
1476
  init_schema_to_json();
1234
1477
  init_schema_validator();
1235
- BaseGadget = class {
1478
+ AbstractGadget = class {
1236
1479
  /**
1237
1480
  * The name of the gadget. Used for identification when LLM calls it.
1238
1481
  * If not provided, defaults to the class name.
@@ -1260,14 +1503,14 @@ var init_gadget = __esm({
1260
1503
  */
1261
1504
  examples;
1262
1505
  /**
1263
- * Throws an AbortError if the execution has been aborted.
1506
+ * Throws an AbortException if the execution has been aborted.
1264
1507
  *
1265
1508
  * Call this at key checkpoints in long-running gadgets to allow early exit
1266
1509
  * when the gadget has been cancelled (e.g., due to timeout). This enables
1267
1510
  * resource cleanup and prevents unnecessary work after cancellation.
1268
1511
  *
1269
1512
  * @param ctx - The execution context containing the abort signal
1270
- * @throws AbortError if ctx.signal.aborted is true
1513
+ * @throws AbortException if ctx.signal.aborted is true
1271
1514
  *
1272
1515
  * @example
1273
1516
  * ```typescript
@@ -1292,7 +1535,7 @@ var init_gadget = __esm({
1292
1535
  */
1293
1536
  throwIfAborted(ctx) {
1294
1537
  if (ctx?.signal?.aborted) {
1295
- throw new AbortError();
1538
+ throw new AbortException();
1296
1539
  }
1297
1540
  }
1298
1541
  /**
@@ -1433,7 +1676,9 @@ var init_gadget = __esm({
1433
1676
  parts.push(`# ${example.comment}`);
1434
1677
  }
1435
1678
  parts.push(`${effectiveStartPrefix}${gadgetName}`);
1436
- parts.push(formatParamsAsBlock(example.params, "", effectiveArgPrefix));
1679
+ parts.push(
1680
+ formatParamsForBlockExample(example.params, "", effectiveArgPrefix)
1681
+ );
1437
1682
  parts.push(effectiveEndPrefix);
1438
1683
  if (example.output !== void 0) {
1439
1684
  parts.push("");
@@ -1450,7 +1695,7 @@ var init_gadget = __esm({
1450
1695
 
1451
1696
  // src/gadgets/create-gadget.ts
1452
1697
  function createGadget(config) {
1453
- class DynamicGadget extends BaseGadget {
1698
+ class DynamicGadget extends AbstractGadget {
1454
1699
  name = config.name;
1455
1700
  description = config.description;
1456
1701
  parameterSchema = config.schema;
@@ -1635,6 +1880,18 @@ var init_output_viewer = __esm({
1635
1880
  }
1636
1881
  });
1637
1882
 
1883
+ // src/agent/agent-internal-key.ts
1884
+ function isValidAgentKey(key) {
1885
+ return key === AGENT_INTERNAL_KEY;
1886
+ }
1887
+ var AGENT_INTERNAL_KEY;
1888
+ var init_agent_internal_key = __esm({
1889
+ "src/agent/agent-internal-key.ts"() {
1890
+ "use strict";
1891
+ AGENT_INTERNAL_KEY = Symbol("AGENT_INTERNAL_KEY");
1892
+ }
1893
+ });
1894
+
1638
1895
  // src/agent/compaction/config.ts
1639
1896
  function resolveCompactionConfig(config = {}) {
1640
1897
  const trigger = config.triggerThresholdPercent ?? DEFAULT_COMPACTION_CONFIG.triggerThresholdPercent;
@@ -1882,9 +2139,9 @@ var init_hybrid = __esm({
1882
2139
  var init_strategies = __esm({
1883
2140
  "src/agent/compaction/strategies/index.ts"() {
1884
2141
  "use strict";
2142
+ init_hybrid();
1885
2143
  init_sliding_window();
1886
2144
  init_summarization();
1887
- init_hybrid();
1888
2145
  }
1889
2146
  });
1890
2147
 
@@ -2046,98 +2303,6 @@ var init_manager = __esm({
2046
2303
  }
2047
2304
  });
2048
2305
 
2049
- // src/agent/gadget-output-store.ts
2050
- var import_node_crypto, GadgetOutputStore;
2051
- var init_gadget_output_store = __esm({
2052
- "src/agent/gadget-output-store.ts"() {
2053
- "use strict";
2054
- import_node_crypto = require("crypto");
2055
- GadgetOutputStore = class {
2056
- outputs = /* @__PURE__ */ new Map();
2057
- /**
2058
- * Store a gadget output and return its ID.
2059
- *
2060
- * @param gadgetName - Name of the gadget that produced the output
2061
- * @param content - Full output content to store
2062
- * @returns Generated ID for retrieving the output later
2063
- */
2064
- store(gadgetName, content) {
2065
- const id = this.generateId(gadgetName);
2066
- const encoder = new TextEncoder();
2067
- const stored = {
2068
- id,
2069
- gadgetName,
2070
- content,
2071
- byteSize: encoder.encode(content).length,
2072
- lineCount: content.split("\n").length,
2073
- timestamp: /* @__PURE__ */ new Date()
2074
- };
2075
- this.outputs.set(id, stored);
2076
- return id;
2077
- }
2078
- /**
2079
- * Retrieve a stored output by ID.
2080
- *
2081
- * @param id - The output ID (e.g., "Search_d34db33f")
2082
- * @returns The stored output or undefined if not found
2083
- */
2084
- get(id) {
2085
- return this.outputs.get(id);
2086
- }
2087
- /**
2088
- * Check if an output exists.
2089
- *
2090
- * @param id - The output ID to check
2091
- * @returns True if the output exists
2092
- */
2093
- has(id) {
2094
- return this.outputs.has(id);
2095
- }
2096
- /**
2097
- * Get all stored output IDs.
2098
- *
2099
- * @returns Array of output IDs
2100
- */
2101
- getIds() {
2102
- return Array.from(this.outputs.keys());
2103
- }
2104
- /**
2105
- * Get the number of stored outputs.
2106
- */
2107
- get size() {
2108
- return this.outputs.size;
2109
- }
2110
- /**
2111
- * Clear all stored outputs.
2112
- * Called when the agent run completes.
2113
- */
2114
- clear() {
2115
- this.outputs.clear();
2116
- }
2117
- /**
2118
- * Generate a unique ID for a stored output.
2119
- * Format: {GadgetName}_{8 hex chars}
2120
- */
2121
- generateId(gadgetName) {
2122
- const hex = (0, import_node_crypto.randomBytes)(4).toString("hex");
2123
- return `${gadgetName}_${hex}`;
2124
- }
2125
- };
2126
- }
2127
- });
2128
-
2129
- // src/agent/agent-internal-key.ts
2130
- function isValidAgentKey(key) {
2131
- return key === AGENT_INTERNAL_KEY;
2132
- }
2133
- var AGENT_INTERNAL_KEY;
2134
- var init_agent_internal_key = __esm({
2135
- "src/agent/agent-internal-key.ts"() {
2136
- "use strict";
2137
- AGENT_INTERNAL_KEY = Symbol("AGENT_INTERNAL_KEY");
2138
- }
2139
- });
2140
-
2141
2306
  // src/agent/conversation-manager.ts
2142
2307
  var ConversationManager;
2143
2308
  var init_conversation_manager = __esm({
@@ -2168,8 +2333,8 @@ var init_conversation_manager = __esm({
2168
2333
  addAssistantMessage(content) {
2169
2334
  this.historyBuilder.addAssistant(content);
2170
2335
  }
2171
- addGadgetCall(gadgetName, parameters, result) {
2172
- this.historyBuilder.addGadgetCall(gadgetName, parameters, result);
2336
+ addGadgetCallResult(gadgetName, parameters, result, media, mediaIds) {
2337
+ this.historyBuilder.addGadgetCallResult(gadgetName, parameters, result, media, mediaIds);
2173
2338
  }
2174
2339
  getMessages() {
2175
2340
  return [...this.baseMessages, ...this.initialMessages, ...this.historyBuilder.build()];
@@ -2189,7 +2354,7 @@ var init_conversation_manager = __esm({
2189
2354
  if (msg.role === "user") {
2190
2355
  this.historyBuilder.addUser(msg.content);
2191
2356
  } else if (msg.role === "assistant") {
2192
- this.historyBuilder.addAssistant(extractText(msg.content));
2357
+ this.historyBuilder.addAssistant(extractMessageText(msg.content));
2193
2358
  }
2194
2359
  }
2195
2360
  }
@@ -2253,59 +2418,139 @@ var init_event_handlers = __esm({
2253
2418
  }
2254
2419
  });
2255
2420
 
2256
- // src/agent/hook-validators.ts
2257
- function validateBeforeLLMCallAction(action) {
2258
- if (!action || typeof action !== "object" || !("action" in action)) {
2259
- throw new HookValidationError(
2260
- "beforeLLMCall",
2261
- "Must return an action object with an 'action' field"
2262
- );
2263
- }
2264
- const actionType = action.action;
2265
- if (actionType !== "proceed" && actionType !== "skip") {
2266
- throw new HookValidationError(
2267
- "beforeLLMCall",
2268
- `Invalid action type: ${actionType}. Must be 'proceed' or 'skip'`
2269
- );
2270
- }
2271
- if (actionType === "skip" && !action.syntheticResponse) {
2272
- throw new HookValidationError(
2273
- "beforeLLMCall",
2274
- "When action is 'skip', syntheticResponse is required"
2275
- );
2276
- }
2277
- }
2278
- function validateAfterLLMCallAction(action) {
2279
- if (!action || typeof action !== "object" || !("action" in action)) {
2280
- throw new HookValidationError(
2281
- "afterLLMCall",
2282
- "Must return an action object with an 'action' field"
2283
- );
2284
- }
2285
- const actionType = action.action;
2286
- const validActions = ["continue", "append_messages", "modify_and_continue", "append_and_modify"];
2287
- if (!validActions.includes(actionType)) {
2288
- throw new HookValidationError(
2289
- "afterLLMCall",
2290
- `Invalid action type: ${actionType}. Must be one of: ${validActions.join(", ")}`
2291
- );
2292
- }
2293
- if (actionType === "append_messages" || actionType === "append_and_modify") {
2294
- if (!("messages" in action) || !action.messages || !Array.isArray(action.messages)) {
2295
- throw new HookValidationError(
2296
- "afterLLMCall",
2297
- `When action is '${actionType}', messages array is required`
2298
- );
2299
- }
2300
- if (action.messages.length === 0) {
2301
- throw new HookValidationError(
2302
- "afterLLMCall",
2303
- `When action is '${actionType}', messages array must not be empty`
2304
- );
2305
- }
2306
- for (let i = 0; i < action.messages.length; i++) {
2307
- const msg = action.messages[i];
2308
- if (!msg || typeof msg !== "object") {
2421
+ // src/agent/gadget-output-store.ts
2422
+ var import_node_crypto2, GadgetOutputStore;
2423
+ var init_gadget_output_store = __esm({
2424
+ "src/agent/gadget-output-store.ts"() {
2425
+ "use strict";
2426
+ import_node_crypto2 = require("crypto");
2427
+ GadgetOutputStore = class {
2428
+ outputs = /* @__PURE__ */ new Map();
2429
+ /**
2430
+ * Store a gadget output and return its ID.
2431
+ *
2432
+ * @param gadgetName - Name of the gadget that produced the output
2433
+ * @param content - Full output content to store
2434
+ * @returns Generated ID for retrieving the output later
2435
+ */
2436
+ store(gadgetName, content) {
2437
+ const id = this.generateId(gadgetName);
2438
+ const encoder = new TextEncoder();
2439
+ const stored = {
2440
+ id,
2441
+ gadgetName,
2442
+ content,
2443
+ byteSize: encoder.encode(content).length,
2444
+ lineCount: content.split("\n").length,
2445
+ timestamp: /* @__PURE__ */ new Date()
2446
+ };
2447
+ this.outputs.set(id, stored);
2448
+ return id;
2449
+ }
2450
+ /**
2451
+ * Retrieve a stored output by ID.
2452
+ *
2453
+ * @param id - The output ID (e.g., "Search_d34db33f")
2454
+ * @returns The stored output or undefined if not found
2455
+ */
2456
+ get(id) {
2457
+ return this.outputs.get(id);
2458
+ }
2459
+ /**
2460
+ * Check if an output exists.
2461
+ *
2462
+ * @param id - The output ID to check
2463
+ * @returns True if the output exists
2464
+ */
2465
+ has(id) {
2466
+ return this.outputs.has(id);
2467
+ }
2468
+ /**
2469
+ * Get all stored output IDs.
2470
+ *
2471
+ * @returns Array of output IDs
2472
+ */
2473
+ getIds() {
2474
+ return Array.from(this.outputs.keys());
2475
+ }
2476
+ /**
2477
+ * Get the number of stored outputs.
2478
+ */
2479
+ get size() {
2480
+ return this.outputs.size;
2481
+ }
2482
+ /**
2483
+ * Clear all stored outputs.
2484
+ * Called when the agent run completes.
2485
+ */
2486
+ clear() {
2487
+ this.outputs.clear();
2488
+ }
2489
+ /**
2490
+ * Generate a unique ID for a stored output.
2491
+ * Format: {GadgetName}_{8 hex chars}
2492
+ */
2493
+ generateId(gadgetName) {
2494
+ const hex = (0, import_node_crypto2.randomBytes)(4).toString("hex");
2495
+ return `${gadgetName}_${hex}`;
2496
+ }
2497
+ };
2498
+ }
2499
+ });
2500
+
2501
+ // src/agent/hook-validators.ts
2502
+ function validateBeforeLLMCallAction(action) {
2503
+ if (!action || typeof action !== "object" || !("action" in action)) {
2504
+ throw new HookValidationError(
2505
+ "beforeLLMCall",
2506
+ "Must return an action object with an 'action' field"
2507
+ );
2508
+ }
2509
+ const actionType = action.action;
2510
+ if (actionType !== "proceed" && actionType !== "skip") {
2511
+ throw new HookValidationError(
2512
+ "beforeLLMCall",
2513
+ `Invalid action type: ${actionType}. Must be 'proceed' or 'skip'`
2514
+ );
2515
+ }
2516
+ if (actionType === "skip" && !action.syntheticResponse) {
2517
+ throw new HookValidationError(
2518
+ "beforeLLMCall",
2519
+ "When action is 'skip', syntheticResponse is required"
2520
+ );
2521
+ }
2522
+ }
2523
+ function validateAfterLLMCallAction(action) {
2524
+ if (!action || typeof action !== "object" || !("action" in action)) {
2525
+ throw new HookValidationError(
2526
+ "afterLLMCall",
2527
+ "Must return an action object with an 'action' field"
2528
+ );
2529
+ }
2530
+ const actionType = action.action;
2531
+ const validActions = ["continue", "append_messages", "modify_and_continue", "append_and_modify"];
2532
+ if (!validActions.includes(actionType)) {
2533
+ throw new HookValidationError(
2534
+ "afterLLMCall",
2535
+ `Invalid action type: ${actionType}. Must be one of: ${validActions.join(", ")}`
2536
+ );
2537
+ }
2538
+ if (actionType === "append_messages" || actionType === "append_and_modify") {
2539
+ if (!("messages" in action) || !action.messages || !Array.isArray(action.messages)) {
2540
+ throw new HookValidationError(
2541
+ "afterLLMCall",
2542
+ `When action is '${actionType}', messages array is required`
2543
+ );
2544
+ }
2545
+ if (action.messages.length === 0) {
2546
+ throw new HookValidationError(
2547
+ "afterLLMCall",
2548
+ `When action is '${actionType}', messages array must not be empty`
2549
+ );
2550
+ }
2551
+ for (let i = 0; i < action.messages.length; i++) {
2552
+ const msg = action.messages[i];
2553
+ if (!msg || typeof msg !== "object") {
2309
2554
  throw new HookValidationError("afterLLMCall", `Message at index ${i} must be an object`);
2310
2555
  }
2311
2556
  if (!msg.role || !msg.content) {
@@ -2563,8 +2808,7 @@ var init_schema_introspector = __esm({
2563
2808
  const values = def?.values;
2564
2809
  const value = values?.[0] ?? def?.value;
2565
2810
  if (typeof value === "string") return "string";
2566
- if (typeof value === "number" || typeof value === "bigint")
2567
- return "number";
2811
+ if (typeof value === "number" || typeof value === "bigint") return "number";
2568
2812
  if (typeof value === "boolean") return "boolean";
2569
2813
  return "unknown";
2570
2814
  }
@@ -2836,7 +3080,13 @@ var init_cost_reporting_client = __esm({
2836
3080
  cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
2837
3081
  }
2838
3082
  }
2839
- this.reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
3083
+ this.reportCostFromUsage(
3084
+ model,
3085
+ inputTokens,
3086
+ outputTokens,
3087
+ cachedInputTokens,
3088
+ cacheCreationInputTokens
3089
+ );
2840
3090
  return result;
2841
3091
  }
2842
3092
  /**
@@ -2876,7 +3126,13 @@ var init_cost_reporting_client = __esm({
2876
3126
  }
2877
3127
  }
2878
3128
  } finally {
2879
- this.reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
3129
+ this.reportCostFromUsage(
3130
+ model,
3131
+ inputTokens,
3132
+ outputTokens,
3133
+ cachedInputTokens,
3134
+ cacheCreationInputTokens
3135
+ );
2880
3136
  }
2881
3137
  }
2882
3138
  /**
@@ -2914,7 +3170,13 @@ var init_cost_reporting_client = __esm({
2914
3170
  }
2915
3171
  } finally {
2916
3172
  if (inputTokens > 0 || outputTokens > 0) {
2917
- reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
3173
+ reportCostFromUsage(
3174
+ model,
3175
+ inputTokens,
3176
+ outputTokens,
3177
+ cachedInputTokens,
3178
+ cacheCreationInputTokens
3179
+ );
2918
3180
  }
2919
3181
  }
2920
3182
  }
@@ -2942,12 +3204,12 @@ var init_cost_reporting_client = __esm({
2942
3204
  });
2943
3205
 
2944
3206
  // src/gadgets/error-formatter.ts
2945
- var GadgetErrorFormatter;
3207
+ var GadgetExecutionErrorFormatter;
2946
3208
  var init_error_formatter = __esm({
2947
3209
  "src/gadgets/error-formatter.ts"() {
2948
3210
  "use strict";
2949
3211
  init_constants();
2950
- GadgetErrorFormatter = class {
3212
+ GadgetExecutionErrorFormatter = class {
2951
3213
  argPrefix;
2952
3214
  startPrefix;
2953
3215
  endPrefix;
@@ -3033,16 +3295,16 @@ function stripMarkdownFences(content) {
3033
3295
  cleaned = cleaned.replace(closingFence, "");
3034
3296
  return cleaned.trim();
3035
3297
  }
3036
- var globalInvocationCounter, StreamParser;
3298
+ var globalInvocationCounter, GadgetCallParser;
3037
3299
  var init_parser = __esm({
3038
3300
  "src/gadgets/parser.ts"() {
3039
3301
  "use strict";
3040
3302
  init_constants();
3041
3303
  init_block_params();
3042
3304
  globalInvocationCounter = 0;
3043
- StreamParser = class {
3305
+ GadgetCallParser = class {
3044
3306
  buffer = "";
3045
- lastReportedTextLength = 0;
3307
+ lastEmittedTextOffset = 0;
3046
3308
  startPrefix;
3047
3309
  endPrefix;
3048
3310
  argPrefix;
@@ -3051,16 +3313,20 @@ var init_parser = __esm({
3051
3313
  this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
3052
3314
  this.argPrefix = options.argPrefix ?? GADGET_ARG_PREFIX;
3053
3315
  }
3054
- takeTextUntil(index) {
3055
- if (index <= this.lastReportedTextLength) {
3316
+ /**
3317
+ * Extract and consume text up to the given index.
3318
+ * Returns undefined if no meaningful text to emit.
3319
+ */
3320
+ extractTextSegment(index) {
3321
+ if (index <= this.lastEmittedTextOffset) {
3056
3322
  return void 0;
3057
3323
  }
3058
- const segment = this.buffer.slice(this.lastReportedTextLength, index);
3059
- this.lastReportedTextLength = index;
3324
+ const segment = this.buffer.slice(this.lastEmittedTextOffset, index);
3325
+ this.lastEmittedTextOffset = index;
3060
3326
  return segment.trim().length > 0 ? segment : void 0;
3061
3327
  }
3062
3328
  /**
3063
- * Parse gadget name with optional invocation ID and dependencies.
3329
+ * Parse gadget invocation metadata from the header line.
3064
3330
  *
3065
3331
  * Supported formats:
3066
3332
  * - `GadgetName` - Auto-generate ID, no dependencies
@@ -3069,24 +3335,24 @@ var init_parser = __esm({
3069
3335
  *
3070
3336
  * Dependencies must be comma-separated invocation IDs.
3071
3337
  */
3072
- parseGadgetName(gadgetName) {
3073
- const parts = gadgetName.split(":");
3338
+ parseInvocationMetadata(headerLine) {
3339
+ const parts = headerLine.split(":");
3074
3340
  if (parts.length === 1) {
3075
3341
  return {
3076
- actualName: parts[0],
3342
+ gadgetName: parts[0],
3077
3343
  invocationId: `gadget_${++globalInvocationCounter}`,
3078
3344
  dependencies: []
3079
3345
  };
3080
3346
  } else if (parts.length === 2) {
3081
3347
  return {
3082
- actualName: parts[0],
3348
+ gadgetName: parts[0],
3083
3349
  invocationId: parts[1].trim(),
3084
3350
  dependencies: []
3085
3351
  };
3086
3352
  } else {
3087
3353
  const deps = parts[2].split(",").map((d) => d.trim()).filter((d) => d.length > 0);
3088
3354
  return {
3089
- actualName: parts[0],
3355
+ gadgetName: parts[0],
3090
3356
  invocationId: parts[1].trim(),
3091
3357
  dependencies: deps
3092
3358
  };
@@ -3118,15 +3384,15 @@ var init_parser = __esm({
3118
3384
  while (true) {
3119
3385
  const partStartIndex = this.buffer.indexOf(this.startPrefix, startIndex);
3120
3386
  if (partStartIndex === -1) break;
3121
- const textBefore = this.takeTextUntil(partStartIndex);
3387
+ const textBefore = this.extractTextSegment(partStartIndex);
3122
3388
  if (textBefore !== void 0) {
3123
3389
  yield { type: "text", content: textBefore };
3124
3390
  }
3125
3391
  const metadataStartIndex = partStartIndex + this.startPrefix.length;
3126
3392
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
3127
3393
  if (metadataEndIndex === -1) break;
3128
- const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3129
- const { actualName: actualGadgetName, invocationId, dependencies } = this.parseGadgetName(gadgetName);
3394
+ const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3395
+ const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
3130
3396
  const contentStartIndex = metadataEndIndex + 1;
3131
3397
  let partEndIndex;
3132
3398
  let endMarkerLength = 0;
@@ -3146,7 +3412,7 @@ var init_parser = __esm({
3146
3412
  yield {
3147
3413
  type: "gadget_call",
3148
3414
  call: {
3149
- gadgetName: actualGadgetName,
3415
+ gadgetName,
3150
3416
  invocationId,
3151
3417
  parametersRaw,
3152
3418
  parameters,
@@ -3155,33 +3421,33 @@ var init_parser = __esm({
3155
3421
  }
3156
3422
  };
3157
3423
  startIndex = partEndIndex + endMarkerLength;
3158
- this.lastReportedTextLength = startIndex;
3424
+ this.lastEmittedTextOffset = startIndex;
3159
3425
  }
3160
3426
  if (startIndex > 0) {
3161
3427
  this.buffer = this.buffer.substring(startIndex);
3162
- this.lastReportedTextLength = 0;
3428
+ this.lastEmittedTextOffset = 0;
3163
3429
  }
3164
3430
  }
3165
3431
  // Finalize parsing and return remaining text or incomplete gadgets
3166
3432
  *finalize() {
3167
- const startIndex = this.buffer.indexOf(this.startPrefix, this.lastReportedTextLength);
3433
+ const startIndex = this.buffer.indexOf(this.startPrefix, this.lastEmittedTextOffset);
3168
3434
  if (startIndex !== -1) {
3169
- const textBefore = this.takeTextUntil(startIndex);
3435
+ const textBefore = this.extractTextSegment(startIndex);
3170
3436
  if (textBefore !== void 0) {
3171
3437
  yield { type: "text", content: textBefore };
3172
3438
  }
3173
3439
  const metadataStartIndex = startIndex + this.startPrefix.length;
3174
3440
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
3175
3441
  if (metadataEndIndex !== -1) {
3176
- const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3177
- const { actualName: actualGadgetName, invocationId, dependencies } = this.parseGadgetName(gadgetName);
3442
+ const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3443
+ const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
3178
3444
  const contentStartIndex = metadataEndIndex + 1;
3179
3445
  const parametersRaw = this.buffer.substring(contentStartIndex).trim();
3180
3446
  const { parameters, parseError } = this.parseParameters(parametersRaw);
3181
3447
  yield {
3182
3448
  type: "gadget_call",
3183
3449
  call: {
3184
- gadgetName: actualGadgetName,
3450
+ gadgetName,
3185
3451
  invocationId,
3186
3452
  parametersRaw,
3187
3453
  parameters,
@@ -3192,7 +3458,7 @@ var init_parser = __esm({
3192
3458
  return;
3193
3459
  }
3194
3460
  }
3195
- const remainingText = this.takeTextUntil(this.buffer.length);
3461
+ const remainingText = this.extractTextSegment(this.buffer.length);
3196
3462
  if (remainingText !== void 0) {
3197
3463
  yield { type: "text", content: remainingText };
3198
3464
  }
@@ -3200,7 +3466,7 @@ var init_parser = __esm({
3200
3466
  // Reset parser state (note: global invocation counter is NOT reset to ensure unique IDs)
3201
3467
  reset() {
3202
3468
  this.buffer = "";
3203
- this.lastReportedTextLength = 0;
3469
+ this.lastEmittedTextOffset = 0;
3204
3470
  }
3205
3471
  };
3206
3472
  }
@@ -3219,13 +3485,16 @@ var init_executor = __esm({
3219
3485
  init_exceptions();
3220
3486
  init_parser();
3221
3487
  GadgetExecutor = class {
3222
- constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client) {
3488
+ constructor(registry, requestHumanInput, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig) {
3223
3489
  this.registry = registry;
3224
- this.onHumanInputRequired = onHumanInputRequired;
3490
+ this.requestHumanInput = requestHumanInput;
3225
3491
  this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
3226
3492
  this.client = client;
3493
+ this.mediaStore = mediaStore;
3494
+ this.agentConfig = agentConfig;
3495
+ this.subagentConfig = subagentConfig;
3227
3496
  this.logger = logger ?? createLogger({ name: "llmist:executor" });
3228
- this.errorFormatter = new GadgetErrorFormatter(errorFormatterOptions);
3497
+ this.errorFormatter = new GadgetExecutionErrorFormatter(errorFormatterOptions);
3229
3498
  this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
3230
3499
  }
3231
3500
  logger;
@@ -3245,13 +3514,17 @@ var init_executor = __esm({
3245
3514
  });
3246
3515
  }
3247
3516
  /**
3248
- * Normalizes gadget execute result to consistent format.
3249
- * Handles both string returns (backwards compat) and object returns with cost.
3517
+ * Unify gadget execute result to consistent internal format.
3518
+ * Handles string returns (backwards compat), object returns with cost,
3519
+ * and object returns with media.
3250
3520
  */
3251
- normalizeExecuteResult(raw) {
3521
+ unifyExecuteResult(raw) {
3252
3522
  if (typeof raw === "string") {
3253
3523
  return { result: raw, cost: 0 };
3254
3524
  }
3525
+ if ("media" in raw && raw.media) {
3526
+ return { result: raw.result, media: raw.media, cost: raw.cost ?? 0 };
3527
+ }
3255
3528
  return { result: raw.result, cost: raw.cost ?? 0 };
3256
3529
  }
3257
3530
  // Execute a gadget call asynchronously
@@ -3363,7 +3636,9 @@ var init_executor = __esm({
3363
3636
  const ctx = {
3364
3637
  reportCost,
3365
3638
  llmist: this.client ? new CostReportingLLMistWrapper(this.client, reportCost) : void 0,
3366
- signal: abortController.signal
3639
+ signal: abortController.signal,
3640
+ agentConfig: this.agentConfig,
3641
+ subagentConfig: this.subagentConfig
3367
3642
  };
3368
3643
  let rawResult;
3369
3644
  if (timeoutMs && timeoutMs > 0) {
@@ -3378,8 +3653,21 @@ var init_executor = __esm({
3378
3653
  } else {
3379
3654
  rawResult = await Promise.resolve(gadget.execute(validatedParameters, ctx));
3380
3655
  }
3381
- const { result, cost: returnCost } = this.normalizeExecuteResult(rawResult);
3656
+ const { result, media, cost: returnCost } = this.unifyExecuteResult(rawResult);
3382
3657
  const totalCost = callbackCost + returnCost;
3658
+ let mediaIds;
3659
+ let storedMedia;
3660
+ if (media && media.length > 0 && this.mediaStore) {
3661
+ storedMedia = await Promise.all(
3662
+ media.map((item) => this.mediaStore.store(item, call.gadgetName))
3663
+ );
3664
+ mediaIds = storedMedia.map((m) => m.id);
3665
+ this.logger.debug("Stored media outputs", {
3666
+ gadgetName: call.gadgetName,
3667
+ mediaIds,
3668
+ count: media.length
3669
+ });
3670
+ }
3383
3671
  const executionTimeMs = Date.now() - startTime;
3384
3672
  this.logger.info("Gadget executed successfully", {
3385
3673
  gadgetName: call.gadgetName,
@@ -3387,7 +3675,8 @@ var init_executor = __esm({
3387
3675
  executionTimeMs,
3388
3676
  cost: totalCost > 0 ? totalCost : void 0,
3389
3677
  callbackCost: callbackCost > 0 ? callbackCost : void 0,
3390
- returnCost: returnCost > 0 ? returnCost : void 0
3678
+ returnCost: returnCost > 0 ? returnCost : void 0,
3679
+ mediaCount: media?.length
3391
3680
  });
3392
3681
  this.logger.debug("Gadget result", {
3393
3682
  gadgetName: call.gadgetName,
@@ -3395,7 +3684,8 @@ var init_executor = __esm({
3395
3684
  parameters: validatedParameters,
3396
3685
  result,
3397
3686
  cost: totalCost,
3398
- executionTimeMs
3687
+ executionTimeMs,
3688
+ mediaIds
3399
3689
  });
3400
3690
  return {
3401
3691
  gadgetName: call.gadgetName,
@@ -3403,10 +3693,13 @@ var init_executor = __esm({
3403
3693
  parameters: validatedParameters,
3404
3694
  result,
3405
3695
  executionTimeMs,
3406
- cost: totalCost
3696
+ cost: totalCost,
3697
+ media,
3698
+ mediaIds,
3699
+ storedMedia
3407
3700
  };
3408
3701
  } catch (error) {
3409
- if (error instanceof BreakLoopException) {
3702
+ if (error instanceof TaskCompletionSignal) {
3410
3703
  this.logger.info("Gadget requested loop termination", {
3411
3704
  gadgetName: call.gadgetName,
3412
3705
  message: error.message
@@ -3434,7 +3727,7 @@ var init_executor = __esm({
3434
3727
  executionTimeMs: Date.now() - startTime
3435
3728
  };
3436
3729
  }
3437
- if (error instanceof AbortError) {
3730
+ if (error instanceof AbortException) {
3438
3731
  this.logger.info("Gadget execution was aborted", {
3439
3732
  gadgetName: call.gadgetName,
3440
3733
  executionTimeMs: Date.now() - startTime
@@ -3447,14 +3740,14 @@ var init_executor = __esm({
3447
3740
  executionTimeMs: Date.now() - startTime
3448
3741
  };
3449
3742
  }
3450
- if (error instanceof HumanInputException) {
3743
+ if (error instanceof HumanInputRequiredException) {
3451
3744
  this.logger.info("Gadget requested human input", {
3452
3745
  gadgetName: call.gadgetName,
3453
3746
  question: error.question
3454
3747
  });
3455
- if (this.onHumanInputRequired) {
3748
+ if (this.requestHumanInput) {
3456
3749
  try {
3457
- const answer = await this.onHumanInputRequired(error.question);
3750
+ const answer = await this.requestHumanInput(error.question);
3458
3751
  this.logger.debug("Human input received", {
3459
3752
  gadgetName: call.gadgetName,
3460
3753
  answerLength: answer.length
@@ -3552,13 +3845,13 @@ var init_stream_processor = __esm({
3552
3845
  parser;
3553
3846
  executor;
3554
3847
  stopOnGadgetError;
3555
- shouldContinueAfterError;
3556
- accumulatedText = "";
3557
- shouldStopExecution = false;
3848
+ canRecoverFromGadgetError;
3849
+ responseText = "";
3850
+ executionHalted = false;
3558
3851
  observerFailureCount = 0;
3559
3852
  // Dependency tracking for gadget execution DAG
3560
3853
  /** Gadgets waiting for their dependencies to complete */
3561
- pendingGadgets = /* @__PURE__ */ new Map();
3854
+ gadgetsAwaitingDependencies = /* @__PURE__ */ new Map();
3562
3855
  /** Completed gadget results, keyed by invocation ID */
3563
3856
  completedResults = /* @__PURE__ */ new Map();
3564
3857
  /** Invocation IDs of gadgets that have failed (error or skipped due to dependency) */
@@ -3569,19 +3862,22 @@ var init_stream_processor = __esm({
3569
3862
  this.hooks = options.hooks ?? {};
3570
3863
  this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
3571
3864
  this.stopOnGadgetError = options.stopOnGadgetError ?? true;
3572
- this.shouldContinueAfterError = options.shouldContinueAfterError;
3573
- this.parser = new StreamParser({
3865
+ this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
3866
+ this.parser = new GadgetCallParser({
3574
3867
  startPrefix: options.gadgetStartPrefix,
3575
3868
  endPrefix: options.gadgetEndPrefix,
3576
3869
  argPrefix: options.gadgetArgPrefix
3577
3870
  });
3578
3871
  this.executor = new GadgetExecutor(
3579
3872
  options.registry,
3580
- options.onHumanInputRequired,
3873
+ options.requestHumanInput,
3581
3874
  this.logger.getSubLogger({ name: "executor" }),
3582
3875
  options.defaultGadgetTimeoutMs,
3583
3876
  { argPrefix: options.gadgetArgPrefix },
3584
- options.client
3877
+ options.client,
3878
+ options.mediaStore,
3879
+ options.agentConfig,
3880
+ options.subagentConfig
3585
3881
  );
3586
3882
  }
3587
3883
  /**
@@ -3602,7 +3898,7 @@ var init_stream_processor = __esm({
3602
3898
  if (this.hooks.interceptors?.interceptRawChunk) {
3603
3899
  const context = {
3604
3900
  iteration: this.iteration,
3605
- accumulatedText: this.accumulatedText,
3901
+ accumulatedText: this.responseText,
3606
3902
  logger: this.logger
3607
3903
  };
3608
3904
  const intercepted = this.hooks.interceptors.interceptRawChunk(processedChunk, context);
@@ -3613,7 +3909,7 @@ var init_stream_processor = __esm({
3613
3909
  }
3614
3910
  }
3615
3911
  if (processedChunk) {
3616
- this.accumulatedText += processedChunk;
3912
+ this.responseText += processedChunk;
3617
3913
  }
3618
3914
  }
3619
3915
  if (this.hooks.observers?.onStreamChunk && (processedChunk || chunk.usage)) {
@@ -3622,7 +3918,7 @@ var init_stream_processor = __esm({
3622
3918
  const context = {
3623
3919
  iteration: this.iteration,
3624
3920
  rawChunk: processedChunk,
3625
- accumulatedText: this.accumulatedText,
3921
+ accumulatedText: this.responseText,
3626
3922
  usage,
3627
3923
  logger: this.logger
3628
3924
  };
@@ -3645,12 +3941,12 @@ var init_stream_processor = __esm({
3645
3941
  }
3646
3942
  }
3647
3943
  }
3648
- if (this.shouldStopExecution) {
3944
+ if (this.executionHalted) {
3649
3945
  this.logger.info("Breaking from LLM stream due to gadget error");
3650
3946
  break;
3651
3947
  }
3652
3948
  }
3653
- if (!this.shouldStopExecution) {
3949
+ if (!this.executionHalted) {
3654
3950
  for (const event of this.parser.finalize()) {
3655
3951
  const processedEvents = await this.processEvent(event);
3656
3952
  outputs.push(...processedEvents);
@@ -3674,11 +3970,11 @@ var init_stream_processor = __esm({
3674
3970
  }
3675
3971
  }
3676
3972
  }
3677
- let finalMessage = this.accumulatedText;
3973
+ let finalMessage = this.responseText;
3678
3974
  if (this.hooks.interceptors?.interceptAssistantMessage) {
3679
3975
  const context = {
3680
3976
  iteration: this.iteration,
3681
- rawResponse: this.accumulatedText,
3977
+ rawResponse: this.responseText,
3682
3978
  logger: this.logger
3683
3979
  };
3684
3980
  finalMessage = this.hooks.interceptors.interceptAssistantMessage(finalMessage, context);
@@ -3689,7 +3985,7 @@ var init_stream_processor = __esm({
3689
3985
  didExecuteGadgets,
3690
3986
  finishReason,
3691
3987
  usage,
3692
- rawResponse: this.accumulatedText,
3988
+ rawResponse: this.responseText,
3693
3989
  finalMessage
3694
3990
  };
3695
3991
  }
@@ -3712,7 +4008,7 @@ var init_stream_processor = __esm({
3712
4008
  if (this.hooks.interceptors?.interceptTextChunk) {
3713
4009
  const context = {
3714
4010
  iteration: this.iteration,
3715
- accumulatedText: this.accumulatedText,
4011
+ accumulatedText: this.responseText,
3716
4012
  logger: this.logger
3717
4013
  };
3718
4014
  const intercepted = this.hooks.interceptors.interceptTextChunk(content, context);
@@ -3731,7 +4027,7 @@ var init_stream_processor = __esm({
3731
4027
  * After each execution, pending gadgets are checked to see if they can now run.
3732
4028
  */
3733
4029
  async processGadgetCall(call) {
3734
- if (this.shouldStopExecution) {
4030
+ if (this.executionHalted) {
3735
4031
  this.logger.debug("Skipping gadget execution due to previous error", {
3736
4032
  gadgetName: call.gadgetName
3737
4033
  });
@@ -3770,7 +4066,7 @@ var init_stream_processor = __esm({
3770
4066
  invocationId: call.invocationId,
3771
4067
  waitingOn: unsatisfied
3772
4068
  });
3773
- this.pendingGadgets.set(call.invocationId, call);
4069
+ this.gadgetsAwaitingDependencies.set(call.invocationId, call);
3774
4070
  return events;
3775
4071
  }
3776
4072
  }
@@ -3792,14 +4088,14 @@ var init_stream_processor = __esm({
3792
4088
  error: call.parseError,
3793
4089
  rawParameters: call.parametersRaw
3794
4090
  });
3795
- const shouldContinue = await this.checkContinueAfterError(
4091
+ const shouldContinue = await this.checkCanRecoverFromError(
3796
4092
  call.parseError,
3797
4093
  call.gadgetName,
3798
4094
  "parse",
3799
4095
  call.parameters
3800
4096
  );
3801
4097
  if (!shouldContinue) {
3802
- this.shouldStopExecution = true;
4098
+ this.executionHalted = true;
3803
4099
  }
3804
4100
  }
3805
4101
  let parameters = call.parameters ?? {};
@@ -3923,14 +4219,14 @@ var init_stream_processor = __esm({
3923
4219
  events.push({ type: "gadget_result", result });
3924
4220
  if (result.error) {
3925
4221
  const errorType = this.determineErrorType(call, result);
3926
- const shouldContinue = await this.checkContinueAfterError(
4222
+ const shouldContinue = await this.checkCanRecoverFromError(
3927
4223
  result.error,
3928
4224
  result.gadgetName,
3929
4225
  errorType,
3930
4226
  result.parameters
3931
4227
  );
3932
4228
  if (!shouldContinue) {
3933
- this.shouldStopExecution = true;
4229
+ this.executionHalted = true;
3934
4230
  }
3935
4231
  }
3936
4232
  return events;
@@ -4017,11 +4313,11 @@ var init_stream_processor = __esm({
4017
4313
  async processPendingGadgets() {
4018
4314
  const events = [];
4019
4315
  let progress = true;
4020
- while (progress && this.pendingGadgets.size > 0) {
4316
+ while (progress && this.gadgetsAwaitingDependencies.size > 0) {
4021
4317
  progress = false;
4022
4318
  const readyToExecute = [];
4023
4319
  const readyToSkip = [];
4024
- for (const [invocationId, call] of this.pendingGadgets) {
4320
+ for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
4025
4321
  const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
4026
4322
  if (failedDep) {
4027
4323
  readyToSkip.push({ call, failedDep });
@@ -4033,7 +4329,7 @@ var init_stream_processor = __esm({
4033
4329
  }
4034
4330
  }
4035
4331
  for (const { call, failedDep } of readyToSkip) {
4036
- this.pendingGadgets.delete(call.invocationId);
4332
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4037
4333
  const skipEvents = await this.handleFailedDependency(call, failedDep);
4038
4334
  events.push(...skipEvents);
4039
4335
  progress = true;
@@ -4044,7 +4340,7 @@ var init_stream_processor = __esm({
4044
4340
  invocationIds: readyToExecute.map((c) => c.invocationId)
4045
4341
  });
4046
4342
  for (const call of readyToExecute) {
4047
- this.pendingGadgets.delete(call.invocationId);
4343
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4048
4344
  }
4049
4345
  const executePromises = readyToExecute.map((call) => this.executeGadgetWithHooks(call));
4050
4346
  const results = await Promise.all(executePromises);
@@ -4054,9 +4350,9 @@ var init_stream_processor = __esm({
4054
4350
  progress = true;
4055
4351
  }
4056
4352
  }
4057
- if (this.pendingGadgets.size > 0) {
4058
- const pendingIds = new Set(this.pendingGadgets.keys());
4059
- for (const [invocationId, call] of this.pendingGadgets) {
4353
+ if (this.gadgetsAwaitingDependencies.size > 0) {
4354
+ const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
4355
+ for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
4060
4356
  const missingDeps = call.dependencies.filter((dep) => !this.completedResults.has(dep));
4061
4357
  const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
4062
4358
  const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
@@ -4087,7 +4383,7 @@ var init_stream_processor = __esm({
4087
4383
  };
4088
4384
  events.push(skipEvent);
4089
4385
  }
4090
- this.pendingGadgets.clear();
4386
+ this.gadgetsAwaitingDependencies.clear();
4091
4387
  }
4092
4388
  return events;
4093
4389
  }
@@ -4117,19 +4413,19 @@ var init_stream_processor = __esm({
4117
4413
  );
4118
4414
  }
4119
4415
  /**
4120
- * Check if execution should continue after an error.
4416
+ * Check if execution can recover from an error.
4121
4417
  *
4122
4418
  * Returns true if we should continue processing subsequent gadgets, false if we should stop.
4123
4419
  *
4124
4420
  * Logic:
4125
- * - If custom shouldContinueAfterError is provided, use it
4421
+ * - If custom canRecoverFromGadgetError is provided, use it
4126
4422
  * - Otherwise, use stopOnGadgetError config:
4127
4423
  * - stopOnGadgetError=true → return false (stop execution)
4128
4424
  * - stopOnGadgetError=false → return true (continue execution)
4129
4425
  */
4130
- async checkContinueAfterError(error, gadgetName, errorType, parameters) {
4131
- if (this.shouldContinueAfterError) {
4132
- return await this.shouldContinueAfterError({
4426
+ async checkCanRecoverFromError(error, gadgetName, errorType, parameters) {
4427
+ if (this.canRecoverFromGadgetError) {
4428
+ return await this.canRecoverFromGadgetError({
4133
4429
  error,
4134
4430
  gadgetName,
4135
4431
  errorType,
@@ -4170,13 +4466,14 @@ var init_agent = __esm({
4170
4466
  init_constants();
4171
4467
  init_messages();
4172
4468
  init_model_shortcuts();
4469
+ init_media_store();
4173
4470
  init_output_viewer();
4174
4471
  init_logger();
4175
- init_manager();
4176
- init_gadget_output_store();
4177
4472
  init_agent_internal_key();
4473
+ init_manager();
4178
4474
  init_conversation_manager();
4179
4475
  init_event_handlers();
4476
+ init_gadget_output_store();
4180
4477
  init_hook_validators();
4181
4478
  init_stream_processor();
4182
4479
  Agent = class {
@@ -4191,22 +4488,27 @@ var init_agent = __esm({
4191
4488
  gadgetStartPrefix;
4192
4489
  gadgetEndPrefix;
4193
4490
  gadgetArgPrefix;
4194
- onHumanInputRequired;
4491
+ requestHumanInput;
4195
4492
  textOnlyHandler;
4196
4493
  textWithGadgetsHandler;
4197
4494
  stopOnGadgetError;
4198
- shouldContinueAfterError;
4495
+ canRecoverFromGadgetError;
4199
4496
  defaultGadgetTimeoutMs;
4200
4497
  defaultMaxTokens;
4201
- userPromptProvided;
4498
+ hasUserPrompt;
4202
4499
  // Gadget output limiting
4203
4500
  outputStore;
4204
4501
  outputLimitEnabled;
4205
4502
  outputLimitCharLimit;
4206
4503
  // Context compaction
4207
4504
  compactionManager;
4505
+ // Media storage (for gadgets returning images, audio, etc.)
4506
+ mediaStore;
4208
4507
  // Cancellation
4209
4508
  signal;
4509
+ // Subagent configuration
4510
+ agentContextConfig;
4511
+ subagentConfig;
4210
4512
  /**
4211
4513
  * Creates a new Agent instance.
4212
4514
  * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
@@ -4226,15 +4528,16 @@ var init_agent = __esm({
4226
4528
  this.gadgetStartPrefix = options.gadgetStartPrefix;
4227
4529
  this.gadgetEndPrefix = options.gadgetEndPrefix;
4228
4530
  this.gadgetArgPrefix = options.gadgetArgPrefix;
4229
- this.onHumanInputRequired = options.onHumanInputRequired;
4531
+ this.requestHumanInput = options.requestHumanInput;
4230
4532
  this.textOnlyHandler = options.textOnlyHandler ?? "terminate";
4231
4533
  this.textWithGadgetsHandler = options.textWithGadgetsHandler;
4232
4534
  this.stopOnGadgetError = options.stopOnGadgetError ?? true;
4233
- this.shouldContinueAfterError = options.shouldContinueAfterError;
4535
+ this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
4234
4536
  this.defaultGadgetTimeoutMs = options.defaultGadgetTimeoutMs;
4235
4537
  this.defaultMaxTokens = this.resolveMaxTokensFromCatalog(options.model);
4236
4538
  this.outputLimitEnabled = options.gadgetOutputLimit ?? DEFAULT_GADGET_OUTPUT_LIMIT;
4237
4539
  this.outputStore = new GadgetOutputStore();
4540
+ this.mediaStore = new MediaStore();
4238
4541
  const limitPercent = options.gadgetOutputLimitPercent ?? DEFAULT_GADGET_OUTPUT_LIMIT_PERCENT;
4239
4542
  const limits = this.client.modelRegistry.getModelLimits(this.model);
4240
4543
  const contextWindow = limits?.contextWindow ?? FALLBACK_CONTEXT_WINDOW;
@@ -4245,7 +4548,7 @@ var init_agent = __esm({
4245
4548
  createGadgetOutputViewer(this.outputStore, this.outputLimitCharLimit)
4246
4549
  );
4247
4550
  }
4248
- this.hooks = this.mergeOutputLimiterHook(options.hooks);
4551
+ this.hooks = this.chainOutputLimiterWithUserHooks(options.hooks);
4249
4552
  const baseBuilder = new LLMMessageBuilder(options.promptConfig);
4250
4553
  if (options.systemPrompt) {
4251
4554
  baseBuilder.addSystem(options.systemPrompt);
@@ -4265,7 +4568,7 @@ var init_agent = __esm({
4265
4568
  endPrefix: options.gadgetEndPrefix,
4266
4569
  argPrefix: options.gadgetArgPrefix
4267
4570
  });
4268
- this.userPromptProvided = !!options.userPrompt;
4571
+ this.hasUserPrompt = !!options.userPrompt;
4269
4572
  if (options.userPrompt) {
4270
4573
  this.conversation.addUserMessage(options.userPrompt);
4271
4574
  }
@@ -4278,6 +4581,11 @@ var init_agent = __esm({
4278
4581
  );
4279
4582
  }
4280
4583
  this.signal = options.signal;
4584
+ this.agentContextConfig = {
4585
+ model: this.model,
4586
+ temperature: this.temperature
4587
+ };
4588
+ this.subagentConfig = options.subagentConfig;
4281
4589
  }
4282
4590
  /**
4283
4591
  * Get the gadget registry for this agent.
@@ -4300,6 +4608,36 @@ var init_agent = __esm({
4300
4608
  getRegistry() {
4301
4609
  return this.registry;
4302
4610
  }
4611
+ /**
4612
+ * Get the media store for this agent session.
4613
+ *
4614
+ * The media store holds all media outputs (images, audio, etc.) produced by gadgets
4615
+ * during this agent's execution. Use this to:
4616
+ * - Access stored media files by ID
4617
+ * - List all stored media
4618
+ * - Clean up temporary files after execution
4619
+ *
4620
+ * @returns The MediaStore instance for this agent
4621
+ *
4622
+ * @example
4623
+ * ```typescript
4624
+ * const agent = new AgentBuilder()
4625
+ * .withModel("sonnet")
4626
+ * .build();
4627
+ *
4628
+ * // After execution, access stored media
4629
+ * const store = agent.getMediaStore();
4630
+ * for (const media of store.list()) {
4631
+ * console.log(`${media.id}: ${media.path}`);
4632
+ * }
4633
+ *
4634
+ * // Clean up when done
4635
+ * await store.cleanup();
4636
+ * ```
4637
+ */
4638
+ getMediaStore() {
4639
+ return this.mediaStore;
4640
+ }
4303
4641
  /**
4304
4642
  * Manually trigger context compaction.
4305
4643
  *
@@ -4354,7 +4692,7 @@ var init_agent = __esm({
4354
4692
  * @throws {Error} If no user prompt was provided (when using build() without ask())
4355
4693
  */
4356
4694
  async *run() {
4357
- if (!this.userPromptProvided) {
4695
+ if (!this.hasUserPrompt) {
4358
4696
  throw new Error(
4359
4697
  "No user prompt provided. Use .ask(prompt) instead of .build(), or call agent.run() after providing a prompt."
4360
4698
  );
@@ -4471,11 +4809,14 @@ var init_agent = __esm({
4471
4809
  gadgetArgPrefix: this.gadgetArgPrefix,
4472
4810
  hooks: this.hooks,
4473
4811
  logger: this.logger.getSubLogger({ name: "stream-processor" }),
4474
- onHumanInputRequired: this.onHumanInputRequired,
4812
+ requestHumanInput: this.requestHumanInput,
4475
4813
  stopOnGadgetError: this.stopOnGadgetError,
4476
- shouldContinueAfterError: this.shouldContinueAfterError,
4814
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
4477
4815
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
4478
- client: this.client
4816
+ client: this.client,
4817
+ mediaStore: this.mediaStore,
4818
+ agentConfig: this.agentContextConfig,
4819
+ subagentConfig: this.subagentConfig
4479
4820
  });
4480
4821
  const result = await processor.process(stream2);
4481
4822
  for (const output of result.outputs) {
@@ -4528,19 +4869,21 @@ var init_agent = __esm({
4528
4869
  if (msg.role === "user") {
4529
4870
  this.conversation.addUserMessage(msg.content);
4530
4871
  } else if (msg.role === "assistant") {
4531
- this.conversation.addAssistantMessage(extractText(msg.content));
4872
+ this.conversation.addAssistantMessage(extractMessageText(msg.content));
4532
4873
  } else if (msg.role === "system") {
4533
- this.conversation.addUserMessage(`[System] ${extractText(msg.content)}`);
4874
+ this.conversation.addUserMessage(`[System] ${extractMessageText(msg.content)}`);
4534
4875
  }
4535
4876
  }
4536
4877
  }
4537
4878
  }
4538
4879
  if (result.didExecuteGadgets) {
4539
4880
  if (this.textWithGadgetsHandler) {
4540
- const textContent = result.outputs.filter((output) => output.type === "text").map((output) => output.content).join("");
4881
+ const textContent = result.outputs.filter(
4882
+ (output) => output.type === "text"
4883
+ ).map((output) => output.content).join("");
4541
4884
  if (textContent.trim()) {
4542
4885
  const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
4543
- this.conversation.addGadgetCall(
4886
+ this.conversation.addGadgetCallResult(
4544
4887
  gadgetName,
4545
4888
  parameterMapping(textContent),
4546
4889
  resultMapping ? resultMapping(textContent) : textContent
@@ -4550,16 +4893,18 @@ var init_agent = __esm({
4550
4893
  for (const output of result.outputs) {
4551
4894
  if (output.type === "gadget_result") {
4552
4895
  const gadgetResult = output.result;
4553
- this.conversation.addGadgetCall(
4896
+ this.conversation.addGadgetCallResult(
4554
4897
  gadgetResult.gadgetName,
4555
4898
  gadgetResult.parameters,
4556
- gadgetResult.error ?? gadgetResult.result ?? ""
4899
+ gadgetResult.error ?? gadgetResult.result ?? "",
4900
+ gadgetResult.media,
4901
+ gadgetResult.mediaIds
4557
4902
  );
4558
4903
  }
4559
4904
  }
4560
4905
  } else {
4561
4906
  if (finalMessage.trim()) {
4562
- this.conversation.addGadgetCall(
4907
+ this.conversation.addGadgetCallResult(
4563
4908
  "TellUser",
4564
4909
  { message: finalMessage, done: false, type: "info" },
4565
4910
  `\u2139\uFE0F ${finalMessage}`
@@ -4685,10 +5030,10 @@ var init_agent = __esm({
4685
5030
  return this.client.modelRegistry.getModelLimits(unprefixedModelId)?.maxOutputTokens;
4686
5031
  }
4687
5032
  /**
4688
- * Merge the output limiter interceptor into user-provided hooks.
5033
+ * Chain the output limiter interceptor with user-provided hooks.
4689
5034
  * The limiter runs first, then chains to any user interceptor.
4690
5035
  */
4691
- mergeOutputLimiterHook(userHooks) {
5036
+ chainOutputLimiterWithUserHooks(userHooks) {
4692
5037
  if (!this.outputLimitEnabled) {
4693
5038
  return userHooks ?? {};
4694
5039
  }
@@ -4767,20 +5112,21 @@ var init_builder = __esm({
4767
5112
  promptConfig;
4768
5113
  gadgets = [];
4769
5114
  initialMessages = [];
4770
- onHumanInputRequired;
5115
+ requestHumanInput;
4771
5116
  gadgetStartPrefix;
4772
5117
  gadgetEndPrefix;
4773
5118
  gadgetArgPrefix;
4774
5119
  textOnlyHandler;
4775
5120
  textWithGadgetsHandler;
4776
5121
  stopOnGadgetError;
4777
- shouldContinueAfterError;
5122
+ canRecoverFromGadgetError;
4778
5123
  defaultGadgetTimeoutMs;
4779
5124
  gadgetOutputLimit;
4780
5125
  gadgetOutputLimitPercent;
4781
5126
  compactionConfig;
4782
5127
  signal;
4783
5128
  trailingMessage;
5129
+ subagentConfig;
4784
5130
  constructor(client) {
4785
5131
  this.client = client;
4786
5132
  }
@@ -4871,13 +5217,13 @@ var init_builder = __esm({
4871
5217
  *
4872
5218
  * @example
4873
5219
  * ```typescript
4874
- * .withPromptConfig({
5220
+ * .withPromptTemplateConfig({
4875
5221
  * mainInstruction: "Use the gadget markers below:",
4876
5222
  * rules: ["Always use markers", "Never use function calling"]
4877
5223
  * })
4878
5224
  * ```
4879
5225
  */
4880
- withPromptConfig(config) {
5226
+ withPromptTemplateConfig(config) {
4881
5227
  this.promptConfig = config;
4882
5228
  return this;
4883
5229
  }
@@ -4957,7 +5303,7 @@ var init_builder = __esm({
4957
5303
  * ```
4958
5304
  */
4959
5305
  onHumanInput(handler) {
4960
- this.onHumanInputRequired = handler;
5306
+ this.requestHumanInput = handler;
4961
5307
  return this;
4962
5308
  }
4963
5309
  /**
@@ -5092,9 +5438,9 @@ var init_builder = __esm({
5092
5438
  * Provides fine-grained control over whether to continue after different types of errors.
5093
5439
  * Overrides `stopOnGadgetError` when provided.
5094
5440
  *
5095
- * **Note:** This builder method configures the underlying `shouldContinueAfterError` option
5441
+ * **Note:** This builder method configures the underlying `canRecoverFromGadgetError` option
5096
5442
  * in `AgentOptions`. The method is named `withErrorHandler` for better developer experience,
5097
- * but maps to the `shouldContinueAfterError` property internally.
5443
+ * but maps to the `canRecoverFromGadgetError` property internally.
5098
5444
  *
5099
5445
  * @param handler - Function that decides whether to continue after an error.
5100
5446
  * Return `true` to continue execution, `false` to stop.
@@ -5115,7 +5461,7 @@ var init_builder = __esm({
5115
5461
  * ```
5116
5462
  */
5117
5463
  withErrorHandler(handler) {
5118
- this.shouldContinueAfterError = handler;
5464
+ this.canRecoverFromGadgetError = handler;
5119
5465
  return this;
5120
5466
  }
5121
5467
  /**
@@ -5256,6 +5602,27 @@ var init_builder = __esm({
5256
5602
  this.signal = signal;
5257
5603
  return this;
5258
5604
  }
5605
+ /**
5606
+ * Set subagent configuration overrides.
5607
+ *
5608
+ * Subagent gadgets (like BrowseWeb) can read these settings from ExecutionContext
5609
+ * to inherit model and other options from the CLI configuration.
5610
+ *
5611
+ * @param config - Subagent configuration map keyed by gadget name
5612
+ * @returns This builder for chaining
5613
+ *
5614
+ * @example
5615
+ * ```typescript
5616
+ * .withSubagentConfig({
5617
+ * BrowseWeb: { model: "inherit", maxIterations: 20, headless: true },
5618
+ * CodeAnalyzer: { model: "sonnet", maxIterations: 10 }
5619
+ * })
5620
+ * ```
5621
+ */
5622
+ withSubagentConfig(config) {
5623
+ this.subagentConfig = config;
5624
+ return this;
5625
+ }
5259
5626
  /**
5260
5627
  * Add an ephemeral trailing message that appears at the end of each LLM request.
5261
5628
  *
@@ -5420,19 +5787,20 @@ ${endPrefix}`
5420
5787
  hooks: this.composeHooks(),
5421
5788
  promptConfig: this.promptConfig,
5422
5789
  initialMessages: this.initialMessages,
5423
- onHumanInputRequired: this.onHumanInputRequired,
5790
+ requestHumanInput: this.requestHumanInput,
5424
5791
  gadgetStartPrefix: this.gadgetStartPrefix,
5425
5792
  gadgetEndPrefix: this.gadgetEndPrefix,
5426
5793
  gadgetArgPrefix: this.gadgetArgPrefix,
5427
5794
  textOnlyHandler: this.textOnlyHandler,
5428
5795
  textWithGadgetsHandler: this.textWithGadgetsHandler,
5429
5796
  stopOnGadgetError: this.stopOnGadgetError,
5430
- shouldContinueAfterError: this.shouldContinueAfterError,
5797
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
5431
5798
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
5432
5799
  gadgetOutputLimit: this.gadgetOutputLimit,
5433
5800
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
5434
5801
  compactionConfig: this.compactionConfig,
5435
- signal: this.signal
5802
+ signal: this.signal,
5803
+ subagentConfig: this.subagentConfig
5436
5804
  };
5437
5805
  }
5438
5806
  ask(userPrompt) {
@@ -5601,19 +5969,20 @@ ${endPrefix}`
5601
5969
  hooks: this.composeHooks(),
5602
5970
  promptConfig: this.promptConfig,
5603
5971
  initialMessages: this.initialMessages,
5604
- onHumanInputRequired: this.onHumanInputRequired,
5972
+ requestHumanInput: this.requestHumanInput,
5605
5973
  gadgetStartPrefix: this.gadgetStartPrefix,
5606
5974
  gadgetEndPrefix: this.gadgetEndPrefix,
5607
5975
  gadgetArgPrefix: this.gadgetArgPrefix,
5608
5976
  textOnlyHandler: this.textOnlyHandler,
5609
5977
  textWithGadgetsHandler: this.textWithGadgetsHandler,
5610
5978
  stopOnGadgetError: this.stopOnGadgetError,
5611
- shouldContinueAfterError: this.shouldContinueAfterError,
5979
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
5612
5980
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
5613
5981
  gadgetOutputLimit: this.gadgetOutputLimit,
5614
5982
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
5615
5983
  compactionConfig: this.compactionConfig,
5616
- signal: this.signal
5984
+ signal: this.signal,
5985
+ subagentConfig: this.subagentConfig
5617
5986
  };
5618
5987
  return new Agent(AGENT_INTERNAL_KEY, options);
5619
5988
  }
@@ -5919,9 +6288,9 @@ var init_base_provider = __esm({
5919
6288
  */
5920
6289
  async *stream(options, descriptor, spec) {
5921
6290
  const preparedMessages = this.prepareMessages(options.messages);
5922
- const payload = this.buildRequestPayload(options, descriptor, spec, preparedMessages);
6291
+ const payload = this.buildApiRequest(options, descriptor, spec, preparedMessages);
5923
6292
  const rawStream = await this.executeStreamRequest(payload, options.signal);
5924
- yield* this.wrapStream(rawStream);
6293
+ yield* this.normalizeProviderStream(rawStream);
5925
6294
  }
5926
6295
  /**
5927
6296
  * Prepare messages for the request.
@@ -6021,11 +6390,11 @@ var init_anthropic = __esm({
6021
6390
  "Anthropic does not support speech generation. Use OpenAI (TTS) or Google Gemini (TTS) instead."
6022
6391
  );
6023
6392
  }
6024
- buildRequestPayload(options, descriptor, spec, messages) {
6393
+ buildApiRequest(options, descriptor, spec, messages) {
6025
6394
  const systemMessages = messages.filter((message) => message.role === "system");
6026
6395
  const system = systemMessages.length > 0 ? systemMessages.map((m, index) => ({
6027
6396
  type: "text",
6028
- text: extractText(m.content),
6397
+ text: extractMessageText(m.content),
6029
6398
  // Add cache_control to the LAST system message block
6030
6399
  ...index === systemMessages.length - 1 ? { cache_control: { type: "ephemeral" } } : {}
6031
6400
  })) : void 0;
@@ -6062,7 +6431,7 @@ var init_anthropic = __esm({
6062
6431
  * Handles text, images (base64 only), and applies cache_control.
6063
6432
  */
6064
6433
  convertToAnthropicContent(content, addCacheControl) {
6065
- const parts = normalizeContent(content);
6434
+ const parts = normalizeMessageContent(content);
6066
6435
  return parts.map((part, index) => {
6067
6436
  const isLastPart = index === parts.length - 1;
6068
6437
  const cacheControl = addCacheControl && isLastPart ? { cache_control: { type: "ephemeral" } } : {};
@@ -6108,7 +6477,7 @@ var init_anthropic = __esm({
6108
6477
  const stream2 = await client.messages.create(payload, signal ? { signal } : void 0);
6109
6478
  return stream2;
6110
6479
  }
6111
- async *wrapStream(iterable) {
6480
+ async *normalizeProviderStream(iterable) {
6112
6481
  const stream2 = iterable;
6113
6482
  let inputTokens = 0;
6114
6483
  let cachedInputTokens = 0;
@@ -6185,7 +6554,7 @@ var init_anthropic = __esm({
6185
6554
  async countTokens(messages, descriptor, _spec) {
6186
6555
  const client = this.client;
6187
6556
  const systemMessages = messages.filter((message) => message.role === "system");
6188
- const system = systemMessages.length > 0 ? systemMessages.map((m) => extractText(m.content)).join("\n\n") : void 0;
6557
+ const system = systemMessages.length > 0 ? systemMessages.map((m) => extractMessageText(m.content)).join("\n\n") : void 0;
6189
6558
  const conversation = messages.filter(
6190
6559
  (message) => message.role !== "system"
6191
6560
  ).map((message) => ({
@@ -6207,7 +6576,7 @@ var init_anthropic = __esm({
6207
6576
  let totalChars = 0;
6208
6577
  let imageCount = 0;
6209
6578
  for (const msg of messages) {
6210
- const parts = normalizeContent(msg.content);
6579
+ const parts = normalizeMessageContent(msg.content);
6211
6580
  for (const part of parts) {
6212
6581
  if (part.type === "text") {
6213
6582
  totalChars += part.text.length;
@@ -6898,7 +7267,7 @@ var init_gemini = __esm({
6898
7267
  format: spec?.defaultFormat ?? "wav"
6899
7268
  };
6900
7269
  }
6901
- buildRequestPayload(options, descriptor, _spec, messages) {
7270
+ buildApiRequest(options, descriptor, _spec, messages) {
6902
7271
  const contents = this.convertMessagesToContents(messages);
6903
7272
  const generationConfig = this.buildGenerationConfig(options);
6904
7273
  const config = {
@@ -6950,7 +7319,7 @@ var init_gemini = __esm({
6950
7319
  if (message.role === "system") {
6951
7320
  expandedMessages.push({
6952
7321
  role: "user",
6953
- content: extractText(message.content)
7322
+ content: extractMessageText(message.content)
6954
7323
  });
6955
7324
  expandedMessages.push({
6956
7325
  role: "assistant",
@@ -7000,7 +7369,7 @@ var init_gemini = __esm({
7000
7369
  * Handles text, images, and audio (Gemini supports all three).
7001
7370
  */
7002
7371
  convertToGeminiParts(content) {
7003
- const parts = normalizeContent(content);
7372
+ const parts = normalizeMessageContent(content);
7004
7373
  return parts.map((part) => {
7005
7374
  if (part.type === "text") {
7006
7375
  return { text: part.text };
@@ -7045,10 +7414,10 @@ var init_gemini = __esm({
7045
7414
  }
7046
7415
  return Object.keys(config).length > 0 ? config : null;
7047
7416
  }
7048
- async *wrapStream(iterable) {
7417
+ async *normalizeProviderStream(iterable) {
7049
7418
  const stream2 = iterable;
7050
7419
  for await (const chunk of stream2) {
7051
- const text3 = this.extractText(chunk);
7420
+ const text3 = this.extractMessageText(chunk);
7052
7421
  if (text3) {
7053
7422
  yield { text: text3, rawEvent: chunk };
7054
7423
  }
@@ -7059,7 +7428,7 @@ var init_gemini = __esm({
7059
7428
  }
7060
7429
  }
7061
7430
  }
7062
- extractText(chunk) {
7431
+ extractMessageText(chunk) {
7063
7432
  if (!chunk?.candidates) {
7064
7433
  return "";
7065
7434
  }
@@ -7124,7 +7493,7 @@ var init_gemini = __esm({
7124
7493
  let totalChars = 0;
7125
7494
  let mediaCount = 0;
7126
7495
  for (const msg of messages) {
7127
- const parts = normalizeContent(msg.content);
7496
+ const parts = normalizeMessageContent(msg.content);
7128
7497
  for (const part of parts) {
7129
7498
  if (part.type === "text") {
7130
7499
  totalChars += part.text.length;
@@ -7645,14 +8014,7 @@ var OPENAI_TTS_VOICES, OPENAI_TTS_EXTENDED_VOICES, OPENAI_TTS_FORMATS, openaiSpe
7645
8014
  var init_openai_speech_models = __esm({
7646
8015
  "src/providers/openai-speech-models.ts"() {
7647
8016
  "use strict";
7648
- OPENAI_TTS_VOICES = [
7649
- "alloy",
7650
- "echo",
7651
- "fable",
7652
- "onyx",
7653
- "nova",
7654
- "shimmer"
7655
- ];
8017
+ OPENAI_TTS_VOICES = ["alloy", "echo", "fable", "onyx", "nova", "shimmer"];
7656
8018
  OPENAI_TTS_EXTENDED_VOICES = [
7657
8019
  ...OPENAI_TTS_VOICES,
7658
8020
  "ash",
@@ -7877,7 +8239,7 @@ var init_openai = __esm({
7877
8239
  format
7878
8240
  };
7879
8241
  }
7880
- buildRequestPayload(options, descriptor, spec, messages) {
8242
+ buildApiRequest(options, descriptor, spec, messages) {
7881
8243
  const { maxTokens, temperature, topP, stopSequences, extra } = options;
7882
8244
  const supportsTemperature = spec?.metadata?.supportsTemperature !== false;
7883
8245
  const shouldIncludeTemperature = typeof temperature === "number" && supportsTemperature;
@@ -7912,7 +8274,7 @@ var init_openai = __esm({
7912
8274
  ...message.name ? { name: message.name } : {}
7913
8275
  };
7914
8276
  }
7915
- const textContent = typeof message.content === "string" ? message.content : extractText(message.content);
8277
+ const textContent = typeof message.content === "string" ? message.content : extractMessageText(message.content);
7916
8278
  if (role === "system") {
7917
8279
  return {
7918
8280
  role: "system",
@@ -7972,7 +8334,7 @@ var init_openai = __esm({
7972
8334
  const stream2 = await client.chat.completions.create(payload, signal ? { signal } : void 0);
7973
8335
  return stream2;
7974
8336
  }
7975
- async *wrapStream(iterable) {
8337
+ async *normalizeProviderStream(iterable) {
7976
8338
  const stream2 = iterable;
7977
8339
  for await (const chunk of stream2) {
7978
8340
  const text3 = chunk.choices.map((choice) => choice.delta?.content ?? "").join("");
@@ -8030,9 +8392,9 @@ var init_openai = __esm({
8030
8392
  tokenCount += OPENAI_MESSAGE_OVERHEAD_TOKENS;
8031
8393
  const roleText = ROLE_MAP[message.role];
8032
8394
  tokenCount += encoding.encode(roleText).length;
8033
- const textContent = extractText(message.content);
8395
+ const textContent = extractMessageText(message.content);
8034
8396
  tokenCount += encoding.encode(textContent).length;
8035
- const parts = normalizeContent(message.content);
8397
+ const parts = normalizeMessageContent(message.content);
8036
8398
  for (const part of parts) {
8037
8399
  if (part.type === "image") {
8038
8400
  imageCount++;
@@ -8057,7 +8419,7 @@ var init_openai = __esm({
8057
8419
  let totalChars = 0;
8058
8420
  let imageCount = 0;
8059
8421
  for (const msg of messages) {
8060
- const parts = normalizeContent(msg.content);
8422
+ const parts = normalizeMessageContent(msg.content);
8061
8423
  for (const part of parts) {
8062
8424
  if (part.type === "text") {
8063
8425
  totalChars += part.text.length;
@@ -8350,9 +8712,7 @@ var init_image = __esm({
8350
8712
  return this.findImageAdapter(modelId) !== void 0;
8351
8713
  }
8352
8714
  findImageAdapter(modelId) {
8353
- return this.adapters.find(
8354
- (adapter) => adapter.supportsImageGeneration?.(modelId) ?? false
8355
- );
8715
+ return this.adapters.find((adapter) => adapter.supportsImageGeneration?.(modelId) ?? false);
8356
8716
  }
8357
8717
  };
8358
8718
  }
@@ -8404,9 +8764,7 @@ var init_speech = __esm({
8404
8764
  return this.findSpeechAdapter(modelId) !== void 0;
8405
8765
  }
8406
8766
  findSpeechAdapter(modelId) {
8407
- return this.adapters.find(
8408
- (adapter) => adapter.supportsSpeechGeneration?.(modelId) ?? false
8409
- );
8767
+ return this.adapters.find((adapter) => adapter.supportsSpeechGeneration?.(modelId) ?? false);
8410
8768
  }
8411
8769
  };
8412
8770
  }
@@ -8517,11 +8875,7 @@ var init_vision = __esm({
8517
8875
  if (!parsed) {
8518
8876
  throw new Error("Invalid data URL format");
8519
8877
  }
8520
- builder.addUserWithImage(
8521
- options.prompt,
8522
- parsed.data,
8523
- parsed.mimeType
8524
- );
8878
+ builder.addUserWithImage(options.prompt, parsed.data, parsed.mimeType);
8525
8879
  } else {
8526
8880
  const buffer = Buffer.from(options.image, "base64");
8527
8881
  builder.addUserWithImage(options.prompt, buffer, options.mimeType);
@@ -8952,46 +9306,271 @@ __export(testing_exports, {
8952
9306
  });
8953
9307
  module.exports = __toCommonJS(testing_exports);
8954
9308
 
8955
- // src/gadgets/validation.ts
8956
- function validateAndApplyDefaults(schema, params) {
8957
- const result = schema.safeParse(params);
8958
- if (result.success) {
8959
- return {
8960
- success: true,
8961
- data: result.data
8962
- };
8963
- }
8964
- const issues = result.error.issues.map((issue) => ({
8965
- path: issue.path.join(".") || "root",
8966
- message: issue.message
8967
- }));
8968
- const formattedError = `Invalid parameters: ${issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`;
9309
+ // src/testing/cli-helpers.ts
9310
+ var import_node_stream = require("stream");
9311
+ function createTestEnvironment(options = {}) {
9312
+ const stdin = createMockReadable(options.stdin);
9313
+ const stdout = new import_node_stream.PassThrough();
9314
+ const stderr = new import_node_stream.PassThrough();
9315
+ let exitCode;
8969
9316
  return {
8970
- success: false,
8971
- error: formattedError,
8972
- issues
9317
+ stdin,
9318
+ stdout,
9319
+ stderr,
9320
+ isTTY: options.isTTY ?? false,
9321
+ argv: options.argv ?? ["node", "llmist"],
9322
+ env: { ...filterDefinedEnv(process.env), ...options.env },
9323
+ get exitCode() {
9324
+ return exitCode;
9325
+ },
9326
+ setExitCode: (code) => {
9327
+ exitCode = code;
9328
+ }
8973
9329
  };
8974
9330
  }
8975
- function validateGadgetParams(gadget, params) {
8976
- if (!gadget.parameterSchema) {
8977
- return {
8978
- success: true,
8979
- data: params
8980
- };
9331
+ function createMockReadable(input) {
9332
+ if (!input) {
9333
+ const stream3 = new import_node_stream.Readable({ read() {
9334
+ } });
9335
+ stream3.push(null);
9336
+ return stream3;
8981
9337
  }
8982
- return validateAndApplyDefaults(gadget.parameterSchema, params);
9338
+ const content = Array.isArray(input) ? `${input.join("\n")}
9339
+ ` : input;
9340
+ const stream2 = new import_node_stream.Readable({ read() {
9341
+ } });
9342
+ stream2.push(content);
9343
+ stream2.push(null);
9344
+ return stream2;
8983
9345
  }
8984
-
8985
- // src/testing/gadget-testing.ts
8986
- async function testGadget(gadget, params, options) {
8987
- let validatedParams = params;
8988
- if (!options?.skipValidation) {
8989
- const validationResult = validateGadgetParams(gadget, params);
8990
- if (!validationResult.success) {
8991
- return {
8992
- error: validationResult.error,
8993
- validatedParams: params
8994
- };
9346
+ function createMockWritable() {
9347
+ const chunks = [];
9348
+ const stream2 = new import_node_stream.Writable({
9349
+ write(chunk, _encoding, callback) {
9350
+ chunks.push(Buffer.from(chunk));
9351
+ callback();
9352
+ }
9353
+ });
9354
+ stream2.getData = () => Buffer.concat(chunks).toString("utf8");
9355
+ return stream2;
9356
+ }
9357
+ async function collectOutput(stream2, timeout = 5e3) {
9358
+ return new Promise((resolve, reject) => {
9359
+ const chunks = [];
9360
+ const timeoutId = setTimeout(() => {
9361
+ resolve(Buffer.concat(chunks).toString("utf8"));
9362
+ }, timeout);
9363
+ stream2.on("data", (chunk) => {
9364
+ chunks.push(Buffer.from(chunk));
9365
+ });
9366
+ stream2.on("end", () => {
9367
+ clearTimeout(timeoutId);
9368
+ resolve(Buffer.concat(chunks).toString("utf8"));
9369
+ });
9370
+ stream2.on("error", (err) => {
9371
+ clearTimeout(timeoutId);
9372
+ reject(err);
9373
+ });
9374
+ });
9375
+ }
9376
+ function getBufferedOutput(stream2) {
9377
+ const chunks = [];
9378
+ for (; ; ) {
9379
+ const chunk = stream2.read();
9380
+ if (chunk === null) break;
9381
+ chunks.push(chunk);
9382
+ }
9383
+ return Buffer.concat(chunks).toString("utf8");
9384
+ }
9385
+ function createMockPrompt(responses) {
9386
+ let index = 0;
9387
+ return async (_question) => {
9388
+ if (index >= responses.length) {
9389
+ throw new Error(`Mock prompt exhausted: no response for question ${index + 1}`);
9390
+ }
9391
+ return responses[index++];
9392
+ };
9393
+ }
9394
+ var MockPromptRecorder = class {
9395
+ responses;
9396
+ index = 0;
9397
+ questions = [];
9398
+ constructor(responses) {
9399
+ this.responses = responses;
9400
+ }
9401
+ /**
9402
+ * The prompt function to use in tests.
9403
+ */
9404
+ prompt = async (question) => {
9405
+ this.questions.push(question);
9406
+ if (this.index >= this.responses.length) {
9407
+ throw new Error(`Mock prompt exhausted after ${this.index} questions`);
9408
+ }
9409
+ return this.responses[this.index++];
9410
+ };
9411
+ /**
9412
+ * Get all questions that were asked.
9413
+ */
9414
+ getQuestions() {
9415
+ return [...this.questions];
9416
+ }
9417
+ /**
9418
+ * Get the number of questions asked.
9419
+ */
9420
+ getQuestionCount() {
9421
+ return this.questions.length;
9422
+ }
9423
+ /**
9424
+ * Reset the recorder state.
9425
+ */
9426
+ reset(newResponses) {
9427
+ this.index = 0;
9428
+ this.questions = [];
9429
+ if (newResponses) {
9430
+ this.responses = newResponses;
9431
+ }
9432
+ }
9433
+ };
9434
+ async function waitFor(condition, timeout = 5e3, interval = 50) {
9435
+ const startTime = Date.now();
9436
+ while (!condition()) {
9437
+ if (Date.now() - startTime > timeout) {
9438
+ throw new Error(`waitFor timed out after ${timeout}ms`);
9439
+ }
9440
+ await sleep(interval);
9441
+ }
9442
+ }
9443
+ function sleep(ms) {
9444
+ return new Promise((resolve) => setTimeout(resolve, ms));
9445
+ }
9446
+ function filterDefinedEnv(env) {
9447
+ const result = {};
9448
+ for (const [key, value] of Object.entries(env)) {
9449
+ if (value !== void 0) {
9450
+ result[key] = value;
9451
+ }
9452
+ }
9453
+ return result;
9454
+ }
9455
+
9456
+ // src/testing/conversation-fixtures.ts
9457
+ function createConversation(turnCount, options) {
9458
+ const messages = [];
9459
+ const userPrefix = options?.userPrefix ?? "User message";
9460
+ const assistantPrefix = options?.assistantPrefix ?? "Assistant response";
9461
+ const contentLength = options?.contentLength ?? 100;
9462
+ for (let i = 0; i < turnCount; i++) {
9463
+ const padding = " ".repeat(Math.max(0, contentLength - 30));
9464
+ messages.push({
9465
+ role: "user",
9466
+ content: `${userPrefix} ${i + 1}: This is turn ${i + 1} of the conversation.${padding}`
9467
+ });
9468
+ messages.push({
9469
+ role: "assistant",
9470
+ content: `${assistantPrefix} ${i + 1}: I acknowledge turn ${i + 1}.${padding}`
9471
+ });
9472
+ }
9473
+ return messages;
9474
+ }
9475
+ function createConversationWithGadgets(turnCount, gadgetCallsPerTurn = 1, options) {
9476
+ const messages = [];
9477
+ const gadgetNames = options?.gadgetNames ?? ["search", "calculate", "read"];
9478
+ const contentLength = options?.contentLength ?? 50;
9479
+ let gadgetIndex = 0;
9480
+ for (let turn = 0; turn < turnCount; turn++) {
9481
+ messages.push({
9482
+ role: "user",
9483
+ content: `User request ${turn + 1}${"x".repeat(contentLength)}`
9484
+ });
9485
+ for (let g = 0; g < gadgetCallsPerTurn; g++) {
9486
+ const gadgetName = gadgetNames[gadgetIndex % gadgetNames.length];
9487
+ gadgetIndex++;
9488
+ messages.push({
9489
+ role: "assistant",
9490
+ content: `!!!GADGET_START:${gadgetName}
9491
+ !!!ARG:query
9492
+ test query ${turn}-${g}
9493
+ !!!GADGET_END`
9494
+ });
9495
+ messages.push({
9496
+ role: "user",
9497
+ content: `Result: Gadget ${gadgetName} returned result for query ${turn}-${g}`
9498
+ });
9499
+ }
9500
+ messages.push({
9501
+ role: "assistant",
9502
+ content: `Final response for turn ${turn + 1}${"y".repeat(contentLength)}`
9503
+ });
9504
+ }
9505
+ return messages;
9506
+ }
9507
+ function estimateTokens(messages) {
9508
+ return Math.ceil(messages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0) / 4);
9509
+ }
9510
+ function createUserMessage(content) {
9511
+ return { role: "user", content };
9512
+ }
9513
+ function createAssistantMessage(content) {
9514
+ return { role: "assistant", content };
9515
+ }
9516
+ function createSystemMessage(content) {
9517
+ return { role: "system", content };
9518
+ }
9519
+ function createMinimalConversation() {
9520
+ return [
9521
+ { role: "user", content: "Hello" },
9522
+ { role: "assistant", content: "Hi there!" }
9523
+ ];
9524
+ }
9525
+ function createLargeConversation(targetTokens, options) {
9526
+ const tokensPerTurn = options?.tokensPerTurn ?? 200;
9527
+ const turnsNeeded = Math.ceil(targetTokens / tokensPerTurn);
9528
+ const charsPerMessage = Math.floor(tokensPerTurn * 4 / 2);
9529
+ return createConversation(turnsNeeded, {
9530
+ contentLength: charsPerMessage
9531
+ });
9532
+ }
9533
+
9534
+ // src/gadgets/validation.ts
9535
+ function validateAndApplyDefaults(schema, params) {
9536
+ const result = schema.safeParse(params);
9537
+ if (result.success) {
9538
+ return {
9539
+ success: true,
9540
+ data: result.data
9541
+ };
9542
+ }
9543
+ const issues = result.error.issues.map((issue) => ({
9544
+ path: issue.path.join(".") || "root",
9545
+ message: issue.message
9546
+ }));
9547
+ const formattedError = `Invalid parameters: ${issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`;
9548
+ return {
9549
+ success: false,
9550
+ error: formattedError,
9551
+ issues
9552
+ };
9553
+ }
9554
+ function validateGadgetParams(gadget, params) {
9555
+ if (!gadget.parameterSchema) {
9556
+ return {
9557
+ success: true,
9558
+ data: params
9559
+ };
9560
+ }
9561
+ return validateAndApplyDefaults(gadget.parameterSchema, params);
9562
+ }
9563
+
9564
+ // src/testing/gadget-testing.ts
9565
+ async function testGadget(gadget, params, options) {
9566
+ let validatedParams = params;
9567
+ if (!options?.skipValidation) {
9568
+ const validationResult = validateGadgetParams(gadget, params);
9569
+ if (!validationResult.success) {
9570
+ return {
9571
+ error: validationResult.error,
9572
+ validatedParams: params
9573
+ };
8995
9574
  }
8996
9575
  validatedParams = validationResult.data;
8997
9576
  }
@@ -9189,7 +9768,7 @@ function getMockManager(options) {
9189
9768
 
9190
9769
  // src/testing/mock-stream.ts
9191
9770
  init_constants();
9192
- function sleep(ms) {
9771
+ function sleep2(ms) {
9193
9772
  return new Promise((resolve) => setTimeout(resolve, ms));
9194
9773
  }
9195
9774
  function generateInvocationId() {
@@ -9270,7 +9849,7 @@ ${blockParams}${GADGET_END_PREFIX}`;
9270
9849
  }
9271
9850
  async function* createMockStream(response) {
9272
9851
  if (response.delayMs) {
9273
- await sleep(response.delayMs);
9852
+ await sleep2(response.delayMs);
9274
9853
  }
9275
9854
  const streamDelay = response.streamDelayMs ?? 0;
9276
9855
  let fullText = response.text ?? "";
@@ -9295,7 +9874,7 @@ async function* createMockStream(response) {
9295
9874
  }
9296
9875
  yield chunk;
9297
9876
  if (streamDelay > 0 && !isLast) {
9298
- await sleep(streamDelay);
9877
+ await sleep2(streamDelay);
9299
9878
  }
9300
9879
  }
9301
9880
  } else {
@@ -9572,7 +10151,9 @@ var MockBuilder = class {
9572
10151
  */
9573
10152
  whenMessageContains(text3) {
9574
10153
  this.matchers.push(
9575
- (ctx) => ctx.messages.some((msg) => extractText(msg.content).toLowerCase().includes(text3.toLowerCase()))
10154
+ (ctx) => ctx.messages.some(
10155
+ (msg) => extractMessageText(msg.content).toLowerCase().includes(text3.toLowerCase())
10156
+ )
9576
10157
  );
9577
10158
  return this;
9578
10159
  }
@@ -9586,7 +10167,7 @@ var MockBuilder = class {
9586
10167
  this.matchers.push((ctx) => {
9587
10168
  const lastMsg = ctx.messages[ctx.messages.length - 1];
9588
10169
  if (!lastMsg) return false;
9589
- return extractText(lastMsg.content).toLowerCase().includes(text3.toLowerCase());
10170
+ return extractMessageText(lastMsg.content).toLowerCase().includes(text3.toLowerCase());
9590
10171
  });
9591
10172
  return this;
9592
10173
  }
@@ -9597,7 +10178,7 @@ var MockBuilder = class {
9597
10178
  * mockLLM().whenMessageMatches(/calculate \d+/)
9598
10179
  */
9599
10180
  whenMessageMatches(regex) {
9600
- this.matchers.push((ctx) => ctx.messages.some((msg) => regex.test(extractText(msg.content))));
10181
+ this.matchers.push((ctx) => ctx.messages.some((msg) => regex.test(extractMessageText(msg.content))));
9601
10182
  return this;
9602
10183
  }
9603
10184
  /**
@@ -9609,7 +10190,7 @@ var MockBuilder = class {
9609
10190
  whenRoleContains(role, text3) {
9610
10191
  this.matchers.push(
9611
10192
  (ctx) => ctx.messages.some(
9612
- (msg) => msg.role === role && extractText(msg.content).toLowerCase().includes(text3.toLowerCase())
10193
+ (msg) => msg.role === role && extractMessageText(msg.content).toLowerCase().includes(text3.toLowerCase())
9613
10194
  )
9614
10195
  );
9615
10196
  return this;
@@ -10010,32 +10591,161 @@ function createMockClient(options) {
10010
10591
  });
10011
10592
  }
10012
10593
 
10013
- // src/testing/mock-gadget.ts
10014
- init_gadget();
10015
- var MockGadgetImpl = class extends BaseGadget {
10016
- name;
10017
- description;
10018
- parameterSchema;
10019
- timeoutMs;
10020
- calls = [];
10021
- resultValue;
10022
- resultFn;
10023
- errorToThrow;
10024
- delayMs;
10025
- shouldTrackCalls;
10026
- constructor(config) {
10027
- super();
10028
- this.name = config.name;
10029
- this.description = config.description ?? `Mock gadget: ${config.name}`;
10030
- this.parameterSchema = config.schema;
10031
- this.resultValue = config.result;
10032
- this.resultFn = config.resultFn;
10033
- this.delayMs = config.delayMs ?? 0;
10034
- this.shouldTrackCalls = config.trackCalls ?? true;
10035
- this.timeoutMs = config.timeoutMs;
10036
- if (config.error) {
10037
- this.errorToThrow = typeof config.error === "string" ? new Error(config.error) : config.error;
10038
- }
10594
+ // src/testing/mock-conversation.ts
10595
+ var MockConversationManager = class {
10596
+ history;
10597
+ baseMessages;
10598
+ replacementHistory;
10599
+ replaceHistoryCallCount = 0;
10600
+ addedMessages = [];
10601
+ constructor(history = [], baseMessages = []) {
10602
+ this.history = [...history];
10603
+ this.baseMessages = [...baseMessages];
10604
+ }
10605
+ addUserMessage(content) {
10606
+ const msg = { role: "user", content };
10607
+ this.history.push(msg);
10608
+ this.addedMessages.push(msg);
10609
+ }
10610
+ addAssistantMessage(content) {
10611
+ const msg = { role: "assistant", content };
10612
+ this.history.push(msg);
10613
+ this.addedMessages.push(msg);
10614
+ }
10615
+ addGadgetCallResult(gadgetName, parameters, result) {
10616
+ const assistantMsg = {
10617
+ role: "assistant",
10618
+ content: `!!!GADGET_START:${gadgetName}
10619
+ ${JSON.stringify(parameters)}
10620
+ !!!GADGET_END`
10621
+ };
10622
+ const resultMsg = {
10623
+ role: "user",
10624
+ content: `Result: ${result}`
10625
+ };
10626
+ this.history.push(assistantMsg);
10627
+ this.history.push(resultMsg);
10628
+ this.addedMessages.push(assistantMsg);
10629
+ this.addedMessages.push(resultMsg);
10630
+ }
10631
+ getMessages() {
10632
+ return [...this.baseMessages, ...this.history];
10633
+ }
10634
+ getHistoryMessages() {
10635
+ return [...this.history];
10636
+ }
10637
+ getBaseMessages() {
10638
+ return [...this.baseMessages];
10639
+ }
10640
+ replaceHistory(newHistory) {
10641
+ this.replacementHistory = [...newHistory];
10642
+ this.history = [...newHistory];
10643
+ this.replaceHistoryCallCount++;
10644
+ }
10645
+ // ============================================
10646
+ // Test Helper Methods
10647
+ // ============================================
10648
+ /**
10649
+ * Check if replaceHistory was called.
10650
+ */
10651
+ wasReplaceHistoryCalled() {
10652
+ return this.replaceHistoryCallCount > 0;
10653
+ }
10654
+ /**
10655
+ * Get the number of times replaceHistory was called.
10656
+ */
10657
+ getReplaceHistoryCallCount() {
10658
+ return this.replaceHistoryCallCount;
10659
+ }
10660
+ /**
10661
+ * Get the most recent history passed to replaceHistory.
10662
+ * Returns undefined if replaceHistory was never called.
10663
+ */
10664
+ getReplacementHistory() {
10665
+ return this.replacementHistory;
10666
+ }
10667
+ /**
10668
+ * Get all messages that were added via add* methods.
10669
+ */
10670
+ getAddedMessages() {
10671
+ return [...this.addedMessages];
10672
+ }
10673
+ /**
10674
+ * Reset all tracking state while preserving the conversation.
10675
+ */
10676
+ resetTracking() {
10677
+ this.replacementHistory = void 0;
10678
+ this.replaceHistoryCallCount = 0;
10679
+ this.addedMessages = [];
10680
+ }
10681
+ /**
10682
+ * Completely reset the mock to initial state.
10683
+ * Note: baseMessages cannot be changed after construction.
10684
+ */
10685
+ reset(history = []) {
10686
+ this.history = [...history];
10687
+ this.resetTracking();
10688
+ }
10689
+ /**
10690
+ * Set the history directly (for test setup).
10691
+ */
10692
+ setHistory(messages) {
10693
+ this.history = [...messages];
10694
+ }
10695
+ /**
10696
+ * Get the current history length.
10697
+ */
10698
+ getHistoryLength() {
10699
+ return this.history.length;
10700
+ }
10701
+ /**
10702
+ * Get total message count (base + history).
10703
+ */
10704
+ getTotalMessageCount() {
10705
+ return this.baseMessages.length + this.history.length;
10706
+ }
10707
+ };
10708
+ function createMockConversationManager(turnCount, baseMessages = []) {
10709
+ const history = [];
10710
+ for (let i = 0; i < turnCount; i++) {
10711
+ history.push({
10712
+ role: "user",
10713
+ content: `User message ${i + 1}: This is turn ${i + 1} of the conversation.`
10714
+ });
10715
+ history.push({
10716
+ role: "assistant",
10717
+ content: `Assistant response ${i + 1}: I acknowledge turn ${i + 1}.`
10718
+ });
10719
+ }
10720
+ return new MockConversationManager(history, baseMessages);
10721
+ }
10722
+
10723
+ // src/testing/mock-gadget.ts
10724
+ init_gadget();
10725
+ var MockGadgetImpl = class extends AbstractGadget {
10726
+ name;
10727
+ description;
10728
+ parameterSchema;
10729
+ timeoutMs;
10730
+ calls = [];
10731
+ resultValue;
10732
+ resultFn;
10733
+ errorToThrow;
10734
+ delayMs;
10735
+ shouldTrackCalls;
10736
+ constructor(config) {
10737
+ super();
10738
+ this.name = config.name;
10739
+ this.description = config.description ?? `Mock gadget: ${config.name}`;
10740
+ this.parameterSchema = config.schema;
10741
+ this.resultValue = config.result;
10742
+ this.resultFn = config.resultFn;
10743
+ this.delayMs = config.delayMs ?? 0;
10744
+ this.shouldTrackCalls = config.trackCalls ?? true;
10745
+ this.timeoutMs = config.timeoutMs;
10746
+ if (config.error) {
10747
+ this.errorToThrow = typeof config.error === "string" ? new Error(config.error) : config.error;
10748
+ }
10039
10749
  }
10040
10750
  async execute(params) {
10041
10751
  if (this.shouldTrackCalls) {
@@ -10169,7 +10879,7 @@ function createTestStream(chunks) {
10169
10879
  function createTextStream(text3, options) {
10170
10880
  return async function* () {
10171
10881
  if (options?.delayMs) {
10172
- await sleep2(options.delayMs);
10882
+ await sleep3(options.delayMs);
10173
10883
  }
10174
10884
  const chunkSize = options?.chunkSize ?? text3.length;
10175
10885
  const chunks = [];
@@ -10191,7 +10901,7 @@ function createTextStream(text3, options) {
10191
10901
  }
10192
10902
  yield chunk;
10193
10903
  if (options?.chunkDelayMs && !isLast) {
10194
- await sleep2(options.chunkDelayMs);
10904
+ await sleep3(options.chunkDelayMs);
10195
10905
  }
10196
10906
  }
10197
10907
  }();
@@ -10229,365 +10939,9 @@ function createErrorStream(chunksBeforeError, error) {
10229
10939
  throw error;
10230
10940
  }();
10231
10941
  }
10232
- function sleep2(ms) {
10233
- return new Promise((resolve) => setTimeout(resolve, ms));
10234
- }
10235
-
10236
- // src/testing/conversation-fixtures.ts
10237
- function createConversation(turnCount, options) {
10238
- const messages = [];
10239
- const userPrefix = options?.userPrefix ?? "User message";
10240
- const assistantPrefix = options?.assistantPrefix ?? "Assistant response";
10241
- const contentLength = options?.contentLength ?? 100;
10242
- for (let i = 0; i < turnCount; i++) {
10243
- const padding = " ".repeat(Math.max(0, contentLength - 30));
10244
- messages.push({
10245
- role: "user",
10246
- content: `${userPrefix} ${i + 1}: This is turn ${i + 1} of the conversation.${padding}`
10247
- });
10248
- messages.push({
10249
- role: "assistant",
10250
- content: `${assistantPrefix} ${i + 1}: I acknowledge turn ${i + 1}.${padding}`
10251
- });
10252
- }
10253
- return messages;
10254
- }
10255
- function createConversationWithGadgets(turnCount, gadgetCallsPerTurn = 1, options) {
10256
- const messages = [];
10257
- const gadgetNames = options?.gadgetNames ?? ["search", "calculate", "read"];
10258
- const contentLength = options?.contentLength ?? 50;
10259
- let gadgetIndex = 0;
10260
- for (let turn = 0; turn < turnCount; turn++) {
10261
- messages.push({
10262
- role: "user",
10263
- content: `User request ${turn + 1}${"x".repeat(contentLength)}`
10264
- });
10265
- for (let g = 0; g < gadgetCallsPerTurn; g++) {
10266
- const gadgetName = gadgetNames[gadgetIndex % gadgetNames.length];
10267
- gadgetIndex++;
10268
- messages.push({
10269
- role: "assistant",
10270
- content: `!!!GADGET_START:${gadgetName}
10271
- !!!ARG:query
10272
- test query ${turn}-${g}
10273
- !!!GADGET_END`
10274
- });
10275
- messages.push({
10276
- role: "user",
10277
- content: `Result: Gadget ${gadgetName} returned result for query ${turn}-${g}`
10278
- });
10279
- }
10280
- messages.push({
10281
- role: "assistant",
10282
- content: `Final response for turn ${turn + 1}${"y".repeat(contentLength)}`
10283
- });
10284
- }
10285
- return messages;
10286
- }
10287
- function estimateTokens(messages) {
10288
- return Math.ceil(
10289
- messages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0) / 4
10290
- );
10291
- }
10292
- function createUserMessage(content) {
10293
- return { role: "user", content };
10294
- }
10295
- function createAssistantMessage(content) {
10296
- return { role: "assistant", content };
10297
- }
10298
- function createSystemMessage(content) {
10299
- return { role: "system", content };
10300
- }
10301
- function createMinimalConversation() {
10302
- return [
10303
- { role: "user", content: "Hello" },
10304
- { role: "assistant", content: "Hi there!" }
10305
- ];
10306
- }
10307
- function createLargeConversation(targetTokens, options) {
10308
- const tokensPerTurn = options?.tokensPerTurn ?? 200;
10309
- const turnsNeeded = Math.ceil(targetTokens / tokensPerTurn);
10310
- const charsPerMessage = Math.floor(tokensPerTurn * 4 / 2);
10311
- return createConversation(turnsNeeded, {
10312
- contentLength: charsPerMessage
10313
- });
10314
- }
10315
-
10316
- // src/testing/mock-conversation.ts
10317
- var MockConversationManager = class {
10318
- history;
10319
- baseMessages;
10320
- replacementHistory;
10321
- replaceHistoryCallCount = 0;
10322
- addedMessages = [];
10323
- constructor(history = [], baseMessages = []) {
10324
- this.history = [...history];
10325
- this.baseMessages = [...baseMessages];
10326
- }
10327
- addUserMessage(content) {
10328
- const msg = { role: "user", content };
10329
- this.history.push(msg);
10330
- this.addedMessages.push(msg);
10331
- }
10332
- addAssistantMessage(content) {
10333
- const msg = { role: "assistant", content };
10334
- this.history.push(msg);
10335
- this.addedMessages.push(msg);
10336
- }
10337
- addGadgetCall(gadgetName, parameters, result) {
10338
- const assistantMsg = {
10339
- role: "assistant",
10340
- content: `!!!GADGET_START:${gadgetName}
10341
- ${JSON.stringify(parameters)}
10342
- !!!GADGET_END`
10343
- };
10344
- const resultMsg = {
10345
- role: "user",
10346
- content: `Result: ${result}`
10347
- };
10348
- this.history.push(assistantMsg);
10349
- this.history.push(resultMsg);
10350
- this.addedMessages.push(assistantMsg);
10351
- this.addedMessages.push(resultMsg);
10352
- }
10353
- getMessages() {
10354
- return [...this.baseMessages, ...this.history];
10355
- }
10356
- getHistoryMessages() {
10357
- return [...this.history];
10358
- }
10359
- getBaseMessages() {
10360
- return [...this.baseMessages];
10361
- }
10362
- replaceHistory(newHistory) {
10363
- this.replacementHistory = [...newHistory];
10364
- this.history = [...newHistory];
10365
- this.replaceHistoryCallCount++;
10366
- }
10367
- // ============================================
10368
- // Test Helper Methods
10369
- // ============================================
10370
- /**
10371
- * Check if replaceHistory was called.
10372
- */
10373
- wasReplaceHistoryCalled() {
10374
- return this.replaceHistoryCallCount > 0;
10375
- }
10376
- /**
10377
- * Get the number of times replaceHistory was called.
10378
- */
10379
- getReplaceHistoryCallCount() {
10380
- return this.replaceHistoryCallCount;
10381
- }
10382
- /**
10383
- * Get the most recent history passed to replaceHistory.
10384
- * Returns undefined if replaceHistory was never called.
10385
- */
10386
- getReplacementHistory() {
10387
- return this.replacementHistory;
10388
- }
10389
- /**
10390
- * Get all messages that were added via add* methods.
10391
- */
10392
- getAddedMessages() {
10393
- return [...this.addedMessages];
10394
- }
10395
- /**
10396
- * Reset all tracking state while preserving the conversation.
10397
- */
10398
- resetTracking() {
10399
- this.replacementHistory = void 0;
10400
- this.replaceHistoryCallCount = 0;
10401
- this.addedMessages = [];
10402
- }
10403
- /**
10404
- * Completely reset the mock to initial state.
10405
- * Note: baseMessages cannot be changed after construction.
10406
- */
10407
- reset(history = []) {
10408
- this.history = [...history];
10409
- this.resetTracking();
10410
- }
10411
- /**
10412
- * Set the history directly (for test setup).
10413
- */
10414
- setHistory(messages) {
10415
- this.history = [...messages];
10416
- }
10417
- /**
10418
- * Get the current history length.
10419
- */
10420
- getHistoryLength() {
10421
- return this.history.length;
10422
- }
10423
- /**
10424
- * Get total message count (base + history).
10425
- */
10426
- getTotalMessageCount() {
10427
- return this.baseMessages.length + this.history.length;
10428
- }
10429
- };
10430
- function createMockConversationManager(turnCount, baseMessages = []) {
10431
- const history = [];
10432
- for (let i = 0; i < turnCount; i++) {
10433
- history.push({
10434
- role: "user",
10435
- content: `User message ${i + 1}: This is turn ${i + 1} of the conversation.`
10436
- });
10437
- history.push({
10438
- role: "assistant",
10439
- content: `Assistant response ${i + 1}: I acknowledge turn ${i + 1}.`
10440
- });
10441
- }
10442
- return new MockConversationManager(history, baseMessages);
10443
- }
10444
-
10445
- // src/testing/cli-helpers.ts
10446
- var import_node_stream = require("stream");
10447
- function createTestEnvironment(options = {}) {
10448
- const stdin = createMockReadable(options.stdin);
10449
- const stdout = new import_node_stream.PassThrough();
10450
- const stderr = new import_node_stream.PassThrough();
10451
- let exitCode;
10452
- return {
10453
- stdin,
10454
- stdout,
10455
- stderr,
10456
- isTTY: options.isTTY ?? false,
10457
- argv: options.argv ?? ["node", "llmist"],
10458
- env: { ...filterDefinedEnv(process.env), ...options.env },
10459
- get exitCode() {
10460
- return exitCode;
10461
- },
10462
- setExitCode: (code) => {
10463
- exitCode = code;
10464
- }
10465
- };
10466
- }
10467
- function createMockReadable(input) {
10468
- if (!input) {
10469
- const stream3 = new import_node_stream.Readable({ read() {
10470
- } });
10471
- stream3.push(null);
10472
- return stream3;
10473
- }
10474
- const content = Array.isArray(input) ? `${input.join("\n")}
10475
- ` : input;
10476
- const stream2 = new import_node_stream.Readable({ read() {
10477
- } });
10478
- stream2.push(content);
10479
- stream2.push(null);
10480
- return stream2;
10481
- }
10482
- function createMockWritable() {
10483
- const chunks = [];
10484
- const stream2 = new import_node_stream.Writable({
10485
- write(chunk, _encoding, callback) {
10486
- chunks.push(Buffer.from(chunk));
10487
- callback();
10488
- }
10489
- });
10490
- stream2.getData = () => Buffer.concat(chunks).toString("utf8");
10491
- return stream2;
10492
- }
10493
- async function collectOutput(stream2, timeout = 5e3) {
10494
- return new Promise((resolve, reject) => {
10495
- const chunks = [];
10496
- const timeoutId = setTimeout(() => {
10497
- resolve(Buffer.concat(chunks).toString("utf8"));
10498
- }, timeout);
10499
- stream2.on("data", (chunk) => {
10500
- chunks.push(Buffer.from(chunk));
10501
- });
10502
- stream2.on("end", () => {
10503
- clearTimeout(timeoutId);
10504
- resolve(Buffer.concat(chunks).toString("utf8"));
10505
- });
10506
- stream2.on("error", (err) => {
10507
- clearTimeout(timeoutId);
10508
- reject(err);
10509
- });
10510
- });
10511
- }
10512
- function getBufferedOutput(stream2) {
10513
- const chunks = [];
10514
- for (; ; ) {
10515
- const chunk = stream2.read();
10516
- if (chunk === null) break;
10517
- chunks.push(chunk);
10518
- }
10519
- return Buffer.concat(chunks).toString("utf8");
10520
- }
10521
- function createMockPrompt(responses) {
10522
- let index = 0;
10523
- return async (_question) => {
10524
- if (index >= responses.length) {
10525
- throw new Error(`Mock prompt exhausted: no response for question ${index + 1}`);
10526
- }
10527
- return responses[index++];
10528
- };
10529
- }
10530
- var MockPromptRecorder = class {
10531
- responses;
10532
- index = 0;
10533
- questions = [];
10534
- constructor(responses) {
10535
- this.responses = responses;
10536
- }
10537
- /**
10538
- * The prompt function to use in tests.
10539
- */
10540
- prompt = async (question) => {
10541
- this.questions.push(question);
10542
- if (this.index >= this.responses.length) {
10543
- throw new Error(`Mock prompt exhausted after ${this.index} questions`);
10544
- }
10545
- return this.responses[this.index++];
10546
- };
10547
- /**
10548
- * Get all questions that were asked.
10549
- */
10550
- getQuestions() {
10551
- return [...this.questions];
10552
- }
10553
- /**
10554
- * Get the number of questions asked.
10555
- */
10556
- getQuestionCount() {
10557
- return this.questions.length;
10558
- }
10559
- /**
10560
- * Reset the recorder state.
10561
- */
10562
- reset(newResponses) {
10563
- this.index = 0;
10564
- this.questions = [];
10565
- if (newResponses) {
10566
- this.responses = newResponses;
10567
- }
10568
- }
10569
- };
10570
- async function waitFor(condition, timeout = 5e3, interval = 50) {
10571
- const startTime = Date.now();
10572
- while (!condition()) {
10573
- if (Date.now() - startTime > timeout) {
10574
- throw new Error(`waitFor timed out after ${timeout}ms`);
10575
- }
10576
- await sleep3(interval);
10577
- }
10578
- }
10579
10942
  function sleep3(ms) {
10580
10943
  return new Promise((resolve) => setTimeout(resolve, ms));
10581
10944
  }
10582
- function filterDefinedEnv(env) {
10583
- const result = {};
10584
- for (const [key, value] of Object.entries(env)) {
10585
- if (value !== void 0) {
10586
- result[key] = value;
10587
- }
10588
- }
10589
- return result;
10590
- }
10591
10945
  // Annotate the CommonJS export names for ESM import in node:
10592
10946
  0 && (module.exports = {
10593
10947
  MockBuilder,