llmist 2.5.0 → 2.6.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
  });
@@ -653,7 +667,7 @@ 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
+ addGadgetCall(gadget, parameters, result, media, mediaIds) {
657
671
  const paramStr = this.formatBlockParameters(parameters, "");
658
672
  this.messages.push({
659
673
  role: "assistant",
@@ -661,10 +675,25 @@ Produces: { "items": ["first", "second"] }`);
661
675
  ${paramStr}
662
676
  ${this.endPrefix}`
663
677
  });
664
- this.messages.push({
665
- role: "user",
666
- content: `Result: ${result}`
667
- });
678
+ if (media && media.length > 0 && mediaIds && mediaIds.length > 0) {
679
+ const idRefs = media.map((m, i) => `[Media: ${mediaIds[i]} (${m.kind})]`).join("\n");
680
+ const textWithIds = `Result: ${result}
681
+ ${idRefs}`;
682
+ const parts = [text(textWithIds)];
683
+ for (const item of media) {
684
+ if (item.kind === "image") {
685
+ parts.push(imageFromBase64(item.data, item.mimeType));
686
+ } else if (item.kind === "audio") {
687
+ parts.push(audioFromBase64(item.data, item.mimeType));
688
+ }
689
+ }
690
+ this.messages.push({ role: "user", content: parts });
691
+ } else {
692
+ this.messages.push({
693
+ role: "user",
694
+ content: `Result: ${result}`
695
+ });
696
+ }
668
697
  return this;
669
698
  }
670
699
  /**
@@ -998,6 +1027,210 @@ var init_registry = __esm({
998
1027
  }
999
1028
  });
1000
1029
 
1030
+ // src/gadgets/media-store.ts
1031
+ function getLlmistTmpDir() {
1032
+ return (0, import_node_path2.join)((0, import_node_os.homedir)(), ".llmist", "tmp");
1033
+ }
1034
+ var import_node_crypto, import_promises, import_node_os, import_node_path2, MIME_TO_EXTENSION, MediaStore;
1035
+ var init_media_store = __esm({
1036
+ "src/gadgets/media-store.ts"() {
1037
+ "use strict";
1038
+ import_node_crypto = require("crypto");
1039
+ import_promises = require("fs/promises");
1040
+ import_node_os = require("os");
1041
+ import_node_path2 = require("path");
1042
+ MIME_TO_EXTENSION = {
1043
+ // Images
1044
+ "image/png": ".png",
1045
+ "image/jpeg": ".jpg",
1046
+ "image/gif": ".gif",
1047
+ "image/webp": ".webp",
1048
+ "image/svg+xml": ".svg",
1049
+ "image/bmp": ".bmp",
1050
+ "image/tiff": ".tiff",
1051
+ // Audio
1052
+ "audio/mp3": ".mp3",
1053
+ "audio/mpeg": ".mp3",
1054
+ "audio/wav": ".wav",
1055
+ "audio/webm": ".webm",
1056
+ "audio/ogg": ".ogg",
1057
+ "audio/flac": ".flac",
1058
+ "audio/aac": ".aac",
1059
+ // Video
1060
+ "video/mp4": ".mp4",
1061
+ "video/webm": ".webm",
1062
+ "video/ogg": ".ogv",
1063
+ "video/quicktime": ".mov",
1064
+ "video/x-msvideo": ".avi",
1065
+ // Documents
1066
+ "application/pdf": ".pdf",
1067
+ "application/json": ".json",
1068
+ "text/plain": ".txt",
1069
+ "text/html": ".html",
1070
+ "text/css": ".css",
1071
+ "text/javascript": ".js"
1072
+ };
1073
+ MediaStore = class {
1074
+ items = /* @__PURE__ */ new Map();
1075
+ outputDir;
1076
+ counter = 0;
1077
+ initialized = false;
1078
+ /**
1079
+ * Create a new MediaStore.
1080
+ *
1081
+ * @param sessionId - Optional session ID for the output directory.
1082
+ * If not provided, a random ID is generated.
1083
+ */
1084
+ constructor(sessionId) {
1085
+ const id = sessionId ?? (0, import_node_crypto.randomBytes)(8).toString("hex");
1086
+ this.outputDir = (0, import_node_path2.join)(getLlmistTmpDir(), `media-${id}`);
1087
+ }
1088
+ /**
1089
+ * Get the output directory path.
1090
+ */
1091
+ getOutputDir() {
1092
+ return this.outputDir;
1093
+ }
1094
+ /**
1095
+ * Ensure the output directory exists.
1096
+ * @throws Error if directory creation fails
1097
+ */
1098
+ async ensureDir() {
1099
+ if (this.initialized) return;
1100
+ try {
1101
+ await (0, import_promises.mkdir)(this.outputDir, { recursive: true });
1102
+ this.initialized = true;
1103
+ } catch (error) {
1104
+ throw new Error(
1105
+ `MediaStore: Failed to create directory ${this.outputDir}: ${error instanceof Error ? error.message : String(error)}`
1106
+ );
1107
+ }
1108
+ }
1109
+ /**
1110
+ * Generate a unique media ID.
1111
+ * Format: "media_" + 6 random alphanumeric characters
1112
+ */
1113
+ generateId() {
1114
+ const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
1115
+ let id = "media_";
1116
+ const bytes = (0, import_node_crypto.randomBytes)(6);
1117
+ for (let i = 0; i < 6; i++) {
1118
+ id += chars[bytes[i] % chars.length];
1119
+ }
1120
+ return id;
1121
+ }
1122
+ /**
1123
+ * Get file extension from MIME type.
1124
+ */
1125
+ getExtension(mimeType) {
1126
+ return MIME_TO_EXTENSION[mimeType] ?? ".bin";
1127
+ }
1128
+ /**
1129
+ * Store media and return stored metadata with ID.
1130
+ *
1131
+ * @param media - The media output from a gadget
1132
+ * @param gadgetName - Name of the gadget that created this media
1133
+ * @returns Stored media information including generated ID
1134
+ * @throws Error if file write fails
1135
+ */
1136
+ async store(media, gadgetName) {
1137
+ await this.ensureDir();
1138
+ const id = this.generateId();
1139
+ const ext = this.getExtension(media.mimeType);
1140
+ const filename = media.fileName ?? `${gadgetName}_${String(++this.counter).padStart(3, "0")}${ext}`;
1141
+ const filePath = (0, import_node_path2.join)(this.outputDir, filename);
1142
+ const buffer = Buffer.from(media.data, "base64");
1143
+ try {
1144
+ await (0, import_promises.writeFile)(filePath, buffer);
1145
+ } catch (error) {
1146
+ throw new Error(
1147
+ `MediaStore: Failed to write media file ${filePath}: ${error instanceof Error ? error.message : String(error)}`
1148
+ );
1149
+ }
1150
+ const stored = {
1151
+ id,
1152
+ kind: media.kind,
1153
+ path: filePath,
1154
+ mimeType: media.mimeType,
1155
+ sizeBytes: buffer.length,
1156
+ description: media.description,
1157
+ metadata: media.metadata,
1158
+ gadgetName,
1159
+ createdAt: /* @__PURE__ */ new Date()
1160
+ };
1161
+ this.items.set(id, stored);
1162
+ return stored;
1163
+ }
1164
+ /**
1165
+ * Get stored media by ID.
1166
+ *
1167
+ * @param id - The media ID (e.g., "media_a1b2c3")
1168
+ * @returns The stored media or undefined if not found
1169
+ */
1170
+ get(id) {
1171
+ return this.items.get(id);
1172
+ }
1173
+ /**
1174
+ * Get the actual file path for a media ID.
1175
+ * Convenience method for gadgets that need the raw path.
1176
+ *
1177
+ * @param id - The media ID
1178
+ * @returns The file path or undefined if not found
1179
+ */
1180
+ getPath(id) {
1181
+ return this.items.get(id)?.path;
1182
+ }
1183
+ /**
1184
+ * List all stored media, optionally filtered by kind.
1185
+ *
1186
+ * @param kind - Optional media kind to filter by
1187
+ * @returns Array of stored media items
1188
+ */
1189
+ list(kind) {
1190
+ const all = Array.from(this.items.values());
1191
+ if (kind) {
1192
+ return all.filter((item) => item.kind === kind);
1193
+ }
1194
+ return all;
1195
+ }
1196
+ /**
1197
+ * Get the count of stored media items.
1198
+ */
1199
+ get size() {
1200
+ return this.items.size;
1201
+ }
1202
+ /**
1203
+ * Check if a media ID exists.
1204
+ */
1205
+ has(id) {
1206
+ return this.items.has(id);
1207
+ }
1208
+ /**
1209
+ * Clear in-memory store without deleting files.
1210
+ * Resets the counter but leaves files on disk.
1211
+ */
1212
+ clear() {
1213
+ this.items.clear();
1214
+ this.counter = 0;
1215
+ }
1216
+ /**
1217
+ * Delete all stored files and clear memory.
1218
+ * Removes the entire session directory.
1219
+ */
1220
+ async cleanup() {
1221
+ if (this.initialized) {
1222
+ try {
1223
+ await (0, import_promises.rm)(this.outputDir, { recursive: true, force: true });
1224
+ } catch {
1225
+ }
1226
+ this.initialized = false;
1227
+ }
1228
+ this.clear();
1229
+ }
1230
+ };
1231
+ }
1232
+ });
1233
+
1001
1234
  // src/gadgets/exceptions.ts
1002
1235
  var BreakLoopException, HumanInputException, TimeoutException, AbortError;
1003
1236
  var init_exceptions = __esm({
@@ -1433,7 +1666,9 @@ var init_gadget = __esm({
1433
1666
  parts.push(`# ${example.comment}`);
1434
1667
  }
1435
1668
  parts.push(`${effectiveStartPrefix}${gadgetName}`);
1436
- parts.push(formatParamsAsBlock(example.params, "", effectiveArgPrefix));
1669
+ parts.push(
1670
+ formatParamsAsBlock(example.params, "", effectiveArgPrefix)
1671
+ );
1437
1672
  parts.push(effectiveEndPrefix);
1438
1673
  if (example.output !== void 0) {
1439
1674
  parts.push("");
@@ -1635,6 +1870,18 @@ var init_output_viewer = __esm({
1635
1870
  }
1636
1871
  });
1637
1872
 
1873
+ // src/agent/agent-internal-key.ts
1874
+ function isValidAgentKey(key) {
1875
+ return key === AGENT_INTERNAL_KEY;
1876
+ }
1877
+ var AGENT_INTERNAL_KEY;
1878
+ var init_agent_internal_key = __esm({
1879
+ "src/agent/agent-internal-key.ts"() {
1880
+ "use strict";
1881
+ AGENT_INTERNAL_KEY = Symbol("AGENT_INTERNAL_KEY");
1882
+ }
1883
+ });
1884
+
1638
1885
  // src/agent/compaction/config.ts
1639
1886
  function resolveCompactionConfig(config = {}) {
1640
1887
  const trigger = config.triggerThresholdPercent ?? DEFAULT_COMPACTION_CONFIG.triggerThresholdPercent;
@@ -1882,9 +2129,9 @@ var init_hybrid = __esm({
1882
2129
  var init_strategies = __esm({
1883
2130
  "src/agent/compaction/strategies/index.ts"() {
1884
2131
  "use strict";
2132
+ init_hybrid();
1885
2133
  init_sliding_window();
1886
2134
  init_summarization();
1887
- init_hybrid();
1888
2135
  }
1889
2136
  });
1890
2137
 
@@ -2046,98 +2293,6 @@ var init_manager = __esm({
2046
2293
  }
2047
2294
  });
2048
2295
 
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
2296
  // src/agent/conversation-manager.ts
2142
2297
  var ConversationManager;
2143
2298
  var init_conversation_manager = __esm({
@@ -2168,8 +2323,8 @@ var init_conversation_manager = __esm({
2168
2323
  addAssistantMessage(content) {
2169
2324
  this.historyBuilder.addAssistant(content);
2170
2325
  }
2171
- addGadgetCall(gadgetName, parameters, result) {
2172
- this.historyBuilder.addGadgetCall(gadgetName, parameters, result);
2326
+ addGadgetCall(gadgetName, parameters, result, media, mediaIds) {
2327
+ this.historyBuilder.addGadgetCall(gadgetName, parameters, result, media, mediaIds);
2173
2328
  }
2174
2329
  getMessages() {
2175
2330
  return [...this.baseMessages, ...this.initialMessages, ...this.historyBuilder.build()];
@@ -2253,62 +2408,142 @@ var init_event_handlers = __esm({
2253
2408
  }
2254
2409
  });
2255
2410
 
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") {
2309
- throw new HookValidationError("afterLLMCall", `Message at index ${i} must be an object`);
2310
- }
2311
- if (!msg.role || !msg.content) {
2411
+ // src/agent/gadget-output-store.ts
2412
+ var import_node_crypto2, GadgetOutputStore;
2413
+ var init_gadget_output_store = __esm({
2414
+ "src/agent/gadget-output-store.ts"() {
2415
+ "use strict";
2416
+ import_node_crypto2 = require("crypto");
2417
+ GadgetOutputStore = class {
2418
+ outputs = /* @__PURE__ */ new Map();
2419
+ /**
2420
+ * Store a gadget output and return its ID.
2421
+ *
2422
+ * @param gadgetName - Name of the gadget that produced the output
2423
+ * @param content - Full output content to store
2424
+ * @returns Generated ID for retrieving the output later
2425
+ */
2426
+ store(gadgetName, content) {
2427
+ const id = this.generateId(gadgetName);
2428
+ const encoder = new TextEncoder();
2429
+ const stored = {
2430
+ id,
2431
+ gadgetName,
2432
+ content,
2433
+ byteSize: encoder.encode(content).length,
2434
+ lineCount: content.split("\n").length,
2435
+ timestamp: /* @__PURE__ */ new Date()
2436
+ };
2437
+ this.outputs.set(id, stored);
2438
+ return id;
2439
+ }
2440
+ /**
2441
+ * Retrieve a stored output by ID.
2442
+ *
2443
+ * @param id - The output ID (e.g., "Search_d34db33f")
2444
+ * @returns The stored output or undefined if not found
2445
+ */
2446
+ get(id) {
2447
+ return this.outputs.get(id);
2448
+ }
2449
+ /**
2450
+ * Check if an output exists.
2451
+ *
2452
+ * @param id - The output ID to check
2453
+ * @returns True if the output exists
2454
+ */
2455
+ has(id) {
2456
+ return this.outputs.has(id);
2457
+ }
2458
+ /**
2459
+ * Get all stored output IDs.
2460
+ *
2461
+ * @returns Array of output IDs
2462
+ */
2463
+ getIds() {
2464
+ return Array.from(this.outputs.keys());
2465
+ }
2466
+ /**
2467
+ * Get the number of stored outputs.
2468
+ */
2469
+ get size() {
2470
+ return this.outputs.size;
2471
+ }
2472
+ /**
2473
+ * Clear all stored outputs.
2474
+ * Called when the agent run completes.
2475
+ */
2476
+ clear() {
2477
+ this.outputs.clear();
2478
+ }
2479
+ /**
2480
+ * Generate a unique ID for a stored output.
2481
+ * Format: {GadgetName}_{8 hex chars}
2482
+ */
2483
+ generateId(gadgetName) {
2484
+ const hex = (0, import_node_crypto2.randomBytes)(4).toString("hex");
2485
+ return `${gadgetName}_${hex}`;
2486
+ }
2487
+ };
2488
+ }
2489
+ });
2490
+
2491
+ // src/agent/hook-validators.ts
2492
+ function validateBeforeLLMCallAction(action) {
2493
+ if (!action || typeof action !== "object" || !("action" in action)) {
2494
+ throw new HookValidationError(
2495
+ "beforeLLMCall",
2496
+ "Must return an action object with an 'action' field"
2497
+ );
2498
+ }
2499
+ const actionType = action.action;
2500
+ if (actionType !== "proceed" && actionType !== "skip") {
2501
+ throw new HookValidationError(
2502
+ "beforeLLMCall",
2503
+ `Invalid action type: ${actionType}. Must be 'proceed' or 'skip'`
2504
+ );
2505
+ }
2506
+ if (actionType === "skip" && !action.syntheticResponse) {
2507
+ throw new HookValidationError(
2508
+ "beforeLLMCall",
2509
+ "When action is 'skip', syntheticResponse is required"
2510
+ );
2511
+ }
2512
+ }
2513
+ function validateAfterLLMCallAction(action) {
2514
+ if (!action || typeof action !== "object" || !("action" in action)) {
2515
+ throw new HookValidationError(
2516
+ "afterLLMCall",
2517
+ "Must return an action object with an 'action' field"
2518
+ );
2519
+ }
2520
+ const actionType = action.action;
2521
+ const validActions = ["continue", "append_messages", "modify_and_continue", "append_and_modify"];
2522
+ if (!validActions.includes(actionType)) {
2523
+ throw new HookValidationError(
2524
+ "afterLLMCall",
2525
+ `Invalid action type: ${actionType}. Must be one of: ${validActions.join(", ")}`
2526
+ );
2527
+ }
2528
+ if (actionType === "append_messages" || actionType === "append_and_modify") {
2529
+ if (!("messages" in action) || !action.messages || !Array.isArray(action.messages)) {
2530
+ throw new HookValidationError(
2531
+ "afterLLMCall",
2532
+ `When action is '${actionType}', messages array is required`
2533
+ );
2534
+ }
2535
+ if (action.messages.length === 0) {
2536
+ throw new HookValidationError(
2537
+ "afterLLMCall",
2538
+ `When action is '${actionType}', messages array must not be empty`
2539
+ );
2540
+ }
2541
+ for (let i = 0; i < action.messages.length; i++) {
2542
+ const msg = action.messages[i];
2543
+ if (!msg || typeof msg !== "object") {
2544
+ throw new HookValidationError("afterLLMCall", `Message at index ${i} must be an object`);
2545
+ }
2546
+ if (!msg.role || !msg.content) {
2312
2547
  throw new HookValidationError(
2313
2548
  "afterLLMCall",
2314
2549
  `Message at index ${i} must have 'role' and 'content' fields`
@@ -2563,8 +2798,7 @@ var init_schema_introspector = __esm({
2563
2798
  const values = def?.values;
2564
2799
  const value = values?.[0] ?? def?.value;
2565
2800
  if (typeof value === "string") return "string";
2566
- if (typeof value === "number" || typeof value === "bigint")
2567
- return "number";
2801
+ if (typeof value === "number" || typeof value === "bigint") return "number";
2568
2802
  if (typeof value === "boolean") return "boolean";
2569
2803
  return "unknown";
2570
2804
  }
@@ -2836,7 +3070,13 @@ var init_cost_reporting_client = __esm({
2836
3070
  cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
2837
3071
  }
2838
3072
  }
2839
- this.reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
3073
+ this.reportCostFromUsage(
3074
+ model,
3075
+ inputTokens,
3076
+ outputTokens,
3077
+ cachedInputTokens,
3078
+ cacheCreationInputTokens
3079
+ );
2840
3080
  return result;
2841
3081
  }
2842
3082
  /**
@@ -2876,7 +3116,13 @@ var init_cost_reporting_client = __esm({
2876
3116
  }
2877
3117
  }
2878
3118
  } finally {
2879
- this.reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
3119
+ this.reportCostFromUsage(
3120
+ model,
3121
+ inputTokens,
3122
+ outputTokens,
3123
+ cachedInputTokens,
3124
+ cacheCreationInputTokens
3125
+ );
2880
3126
  }
2881
3127
  }
2882
3128
  /**
@@ -2914,7 +3160,13 @@ var init_cost_reporting_client = __esm({
2914
3160
  }
2915
3161
  } finally {
2916
3162
  if (inputTokens > 0 || outputTokens > 0) {
2917
- reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
3163
+ reportCostFromUsage(
3164
+ model,
3165
+ inputTokens,
3166
+ outputTokens,
3167
+ cachedInputTokens,
3168
+ cacheCreationInputTokens
3169
+ );
2918
3170
  }
2919
3171
  }
2920
3172
  }
@@ -3126,7 +3378,11 @@ var init_parser = __esm({
3126
3378
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
3127
3379
  if (metadataEndIndex === -1) break;
3128
3380
  const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3129
- const { actualName: actualGadgetName, invocationId, dependencies } = this.parseGadgetName(gadgetName);
3381
+ const {
3382
+ actualName: actualGadgetName,
3383
+ invocationId,
3384
+ dependencies
3385
+ } = this.parseGadgetName(gadgetName);
3130
3386
  const contentStartIndex = metadataEndIndex + 1;
3131
3387
  let partEndIndex;
3132
3388
  let endMarkerLength = 0;
@@ -3174,7 +3430,11 @@ var init_parser = __esm({
3174
3430
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
3175
3431
  if (metadataEndIndex !== -1) {
3176
3432
  const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3177
- const { actualName: actualGadgetName, invocationId, dependencies } = this.parseGadgetName(gadgetName);
3433
+ const {
3434
+ actualName: actualGadgetName,
3435
+ invocationId,
3436
+ dependencies
3437
+ } = this.parseGadgetName(gadgetName);
3178
3438
  const contentStartIndex = metadataEndIndex + 1;
3179
3439
  const parametersRaw = this.buffer.substring(contentStartIndex).trim();
3180
3440
  const { parameters, parseError } = this.parseParameters(parametersRaw);
@@ -3219,11 +3479,12 @@ var init_executor = __esm({
3219
3479
  init_exceptions();
3220
3480
  init_parser();
3221
3481
  GadgetExecutor = class {
3222
- constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client) {
3482
+ constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore) {
3223
3483
  this.registry = registry;
3224
3484
  this.onHumanInputRequired = onHumanInputRequired;
3225
3485
  this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
3226
3486
  this.client = client;
3487
+ this.mediaStore = mediaStore;
3227
3488
  this.logger = logger ?? createLogger({ name: "llmist:executor" });
3228
3489
  this.errorFormatter = new GadgetErrorFormatter(errorFormatterOptions);
3229
3490
  this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
@@ -3246,12 +3507,16 @@ var init_executor = __esm({
3246
3507
  }
3247
3508
  /**
3248
3509
  * Normalizes gadget execute result to consistent format.
3249
- * Handles both string returns (backwards compat) and object returns with cost.
3510
+ * Handles string returns (backwards compat), object returns with cost,
3511
+ * and object returns with media.
3250
3512
  */
3251
3513
  normalizeExecuteResult(raw) {
3252
3514
  if (typeof raw === "string") {
3253
3515
  return { result: raw, cost: 0 };
3254
3516
  }
3517
+ if ("media" in raw && raw.media) {
3518
+ return { result: raw.result, media: raw.media, cost: raw.cost ?? 0 };
3519
+ }
3255
3520
  return { result: raw.result, cost: raw.cost ?? 0 };
3256
3521
  }
3257
3522
  // Execute a gadget call asynchronously
@@ -3378,8 +3643,21 @@ var init_executor = __esm({
3378
3643
  } else {
3379
3644
  rawResult = await Promise.resolve(gadget.execute(validatedParameters, ctx));
3380
3645
  }
3381
- const { result, cost: returnCost } = this.normalizeExecuteResult(rawResult);
3646
+ const { result, media, cost: returnCost } = this.normalizeExecuteResult(rawResult);
3382
3647
  const totalCost = callbackCost + returnCost;
3648
+ let mediaIds;
3649
+ let storedMedia;
3650
+ if (media && media.length > 0 && this.mediaStore) {
3651
+ storedMedia = await Promise.all(
3652
+ media.map((item) => this.mediaStore.store(item, call.gadgetName))
3653
+ );
3654
+ mediaIds = storedMedia.map((m) => m.id);
3655
+ this.logger.debug("Stored media outputs", {
3656
+ gadgetName: call.gadgetName,
3657
+ mediaIds,
3658
+ count: media.length
3659
+ });
3660
+ }
3383
3661
  const executionTimeMs = Date.now() - startTime;
3384
3662
  this.logger.info("Gadget executed successfully", {
3385
3663
  gadgetName: call.gadgetName,
@@ -3387,7 +3665,8 @@ var init_executor = __esm({
3387
3665
  executionTimeMs,
3388
3666
  cost: totalCost > 0 ? totalCost : void 0,
3389
3667
  callbackCost: callbackCost > 0 ? callbackCost : void 0,
3390
- returnCost: returnCost > 0 ? returnCost : void 0
3668
+ returnCost: returnCost > 0 ? returnCost : void 0,
3669
+ mediaCount: media?.length
3391
3670
  });
3392
3671
  this.logger.debug("Gadget result", {
3393
3672
  gadgetName: call.gadgetName,
@@ -3395,7 +3674,8 @@ var init_executor = __esm({
3395
3674
  parameters: validatedParameters,
3396
3675
  result,
3397
3676
  cost: totalCost,
3398
- executionTimeMs
3677
+ executionTimeMs,
3678
+ mediaIds
3399
3679
  });
3400
3680
  return {
3401
3681
  gadgetName: call.gadgetName,
@@ -3403,7 +3683,10 @@ var init_executor = __esm({
3403
3683
  parameters: validatedParameters,
3404
3684
  result,
3405
3685
  executionTimeMs,
3406
- cost: totalCost
3686
+ cost: totalCost,
3687
+ media,
3688
+ mediaIds,
3689
+ storedMedia
3407
3690
  };
3408
3691
  } catch (error) {
3409
3692
  if (error instanceof BreakLoopException) {
@@ -3581,7 +3864,8 @@ var init_stream_processor = __esm({
3581
3864
  this.logger.getSubLogger({ name: "executor" }),
3582
3865
  options.defaultGadgetTimeoutMs,
3583
3866
  { argPrefix: options.gadgetArgPrefix },
3584
- options.client
3867
+ options.client,
3868
+ options.mediaStore
3585
3869
  );
3586
3870
  }
3587
3871
  /**
@@ -4170,13 +4454,14 @@ var init_agent = __esm({
4170
4454
  init_constants();
4171
4455
  init_messages();
4172
4456
  init_model_shortcuts();
4457
+ init_media_store();
4173
4458
  init_output_viewer();
4174
4459
  init_logger();
4175
- init_manager();
4176
- init_gadget_output_store();
4177
4460
  init_agent_internal_key();
4461
+ init_manager();
4178
4462
  init_conversation_manager();
4179
4463
  init_event_handlers();
4464
+ init_gadget_output_store();
4180
4465
  init_hook_validators();
4181
4466
  init_stream_processor();
4182
4467
  Agent = class {
@@ -4205,6 +4490,8 @@ var init_agent = __esm({
4205
4490
  outputLimitCharLimit;
4206
4491
  // Context compaction
4207
4492
  compactionManager;
4493
+ // Media storage (for gadgets returning images, audio, etc.)
4494
+ mediaStore;
4208
4495
  // Cancellation
4209
4496
  signal;
4210
4497
  /**
@@ -4235,6 +4522,7 @@ var init_agent = __esm({
4235
4522
  this.defaultMaxTokens = this.resolveMaxTokensFromCatalog(options.model);
4236
4523
  this.outputLimitEnabled = options.gadgetOutputLimit ?? DEFAULT_GADGET_OUTPUT_LIMIT;
4237
4524
  this.outputStore = new GadgetOutputStore();
4525
+ this.mediaStore = new MediaStore();
4238
4526
  const limitPercent = options.gadgetOutputLimitPercent ?? DEFAULT_GADGET_OUTPUT_LIMIT_PERCENT;
4239
4527
  const limits = this.client.modelRegistry.getModelLimits(this.model);
4240
4528
  const contextWindow = limits?.contextWindow ?? FALLBACK_CONTEXT_WINDOW;
@@ -4300,6 +4588,36 @@ var init_agent = __esm({
4300
4588
  getRegistry() {
4301
4589
  return this.registry;
4302
4590
  }
4591
+ /**
4592
+ * Get the media store for this agent session.
4593
+ *
4594
+ * The media store holds all media outputs (images, audio, etc.) produced by gadgets
4595
+ * during this agent's execution. Use this to:
4596
+ * - Access stored media files by ID
4597
+ * - List all stored media
4598
+ * - Clean up temporary files after execution
4599
+ *
4600
+ * @returns The MediaStore instance for this agent
4601
+ *
4602
+ * @example
4603
+ * ```typescript
4604
+ * const agent = new AgentBuilder()
4605
+ * .withModel("sonnet")
4606
+ * .build();
4607
+ *
4608
+ * // After execution, access stored media
4609
+ * const store = agent.getMediaStore();
4610
+ * for (const media of store.list()) {
4611
+ * console.log(`${media.id}: ${media.path}`);
4612
+ * }
4613
+ *
4614
+ * // Clean up when done
4615
+ * await store.cleanup();
4616
+ * ```
4617
+ */
4618
+ getMediaStore() {
4619
+ return this.mediaStore;
4620
+ }
4303
4621
  /**
4304
4622
  * Manually trigger context compaction.
4305
4623
  *
@@ -4475,7 +4793,8 @@ var init_agent = __esm({
4475
4793
  stopOnGadgetError: this.stopOnGadgetError,
4476
4794
  shouldContinueAfterError: this.shouldContinueAfterError,
4477
4795
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
4478
- client: this.client
4796
+ client: this.client,
4797
+ mediaStore: this.mediaStore
4479
4798
  });
4480
4799
  const result = await processor.process(stream2);
4481
4800
  for (const output of result.outputs) {
@@ -4537,7 +4856,9 @@ var init_agent = __esm({
4537
4856
  }
4538
4857
  if (result.didExecuteGadgets) {
4539
4858
  if (this.textWithGadgetsHandler) {
4540
- const textContent = result.outputs.filter((output) => output.type === "text").map((output) => output.content).join("");
4859
+ const textContent = result.outputs.filter(
4860
+ (output) => output.type === "text"
4861
+ ).map((output) => output.content).join("");
4541
4862
  if (textContent.trim()) {
4542
4863
  const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
4543
4864
  this.conversation.addGadgetCall(
@@ -4553,7 +4874,9 @@ var init_agent = __esm({
4553
4874
  this.conversation.addGadgetCall(
4554
4875
  gadgetResult.gadgetName,
4555
4876
  gadgetResult.parameters,
4556
- gadgetResult.error ?? gadgetResult.result ?? ""
4877
+ gadgetResult.error ?? gadgetResult.result ?? "",
4878
+ gadgetResult.media,
4879
+ gadgetResult.mediaIds
4557
4880
  );
4558
4881
  }
4559
4882
  }
@@ -7645,14 +7968,7 @@ var OPENAI_TTS_VOICES, OPENAI_TTS_EXTENDED_VOICES, OPENAI_TTS_FORMATS, openaiSpe
7645
7968
  var init_openai_speech_models = __esm({
7646
7969
  "src/providers/openai-speech-models.ts"() {
7647
7970
  "use strict";
7648
- OPENAI_TTS_VOICES = [
7649
- "alloy",
7650
- "echo",
7651
- "fable",
7652
- "onyx",
7653
- "nova",
7654
- "shimmer"
7655
- ];
7971
+ OPENAI_TTS_VOICES = ["alloy", "echo", "fable", "onyx", "nova", "shimmer"];
7656
7972
  OPENAI_TTS_EXTENDED_VOICES = [
7657
7973
  ...OPENAI_TTS_VOICES,
7658
7974
  "ash",
@@ -8350,9 +8666,7 @@ var init_image = __esm({
8350
8666
  return this.findImageAdapter(modelId) !== void 0;
8351
8667
  }
8352
8668
  findImageAdapter(modelId) {
8353
- return this.adapters.find(
8354
- (adapter) => adapter.supportsImageGeneration?.(modelId) ?? false
8355
- );
8669
+ return this.adapters.find((adapter) => adapter.supportsImageGeneration?.(modelId) ?? false);
8356
8670
  }
8357
8671
  };
8358
8672
  }
@@ -8404,9 +8718,7 @@ var init_speech = __esm({
8404
8718
  return this.findSpeechAdapter(modelId) !== void 0;
8405
8719
  }
8406
8720
  findSpeechAdapter(modelId) {
8407
- return this.adapters.find(
8408
- (adapter) => adapter.supportsSpeechGeneration?.(modelId) ?? false
8409
- );
8721
+ return this.adapters.find((adapter) => adapter.supportsSpeechGeneration?.(modelId) ?? false);
8410
8722
  }
8411
8723
  };
8412
8724
  }
@@ -8517,11 +8829,7 @@ var init_vision = __esm({
8517
8829
  if (!parsed) {
8518
8830
  throw new Error("Invalid data URL format");
8519
8831
  }
8520
- builder.addUserWithImage(
8521
- options.prompt,
8522
- parsed.data,
8523
- parsed.mimeType
8524
- );
8832
+ builder.addUserWithImage(options.prompt, parsed.data, parsed.mimeType);
8525
8833
  } else {
8526
8834
  const buffer = Buffer.from(options.image, "base64");
8527
8835
  builder.addUserWithImage(options.prompt, buffer, options.mimeType);
@@ -8952,52 +9260,277 @@ __export(testing_exports, {
8952
9260
  });
8953
9261
  module.exports = __toCommonJS(testing_exports);
8954
9262
 
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("; ")}`;
9263
+ // src/testing/cli-helpers.ts
9264
+ var import_node_stream = require("stream");
9265
+ function createTestEnvironment(options = {}) {
9266
+ const stdin = createMockReadable(options.stdin);
9267
+ const stdout = new import_node_stream.PassThrough();
9268
+ const stderr = new import_node_stream.PassThrough();
9269
+ let exitCode;
8969
9270
  return {
8970
- success: false,
8971
- error: formattedError,
8972
- issues
9271
+ stdin,
9272
+ stdout,
9273
+ stderr,
9274
+ isTTY: options.isTTY ?? false,
9275
+ argv: options.argv ?? ["node", "llmist"],
9276
+ env: { ...filterDefinedEnv(process.env), ...options.env },
9277
+ get exitCode() {
9278
+ return exitCode;
9279
+ },
9280
+ setExitCode: (code) => {
9281
+ exitCode = code;
9282
+ }
8973
9283
  };
8974
9284
  }
8975
- function validateGadgetParams(gadget, params) {
8976
- if (!gadget.parameterSchema) {
8977
- return {
8978
- success: true,
8979
- data: params
8980
- };
9285
+ function createMockReadable(input) {
9286
+ if (!input) {
9287
+ const stream3 = new import_node_stream.Readable({ read() {
9288
+ } });
9289
+ stream3.push(null);
9290
+ return stream3;
8981
9291
  }
8982
- return validateAndApplyDefaults(gadget.parameterSchema, params);
9292
+ const content = Array.isArray(input) ? `${input.join("\n")}
9293
+ ` : input;
9294
+ const stream2 = new import_node_stream.Readable({ read() {
9295
+ } });
9296
+ stream2.push(content);
9297
+ stream2.push(null);
9298
+ return stream2;
8983
9299
  }
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
- };
9300
+ function createMockWritable() {
9301
+ const chunks = [];
9302
+ const stream2 = new import_node_stream.Writable({
9303
+ write(chunk, _encoding, callback) {
9304
+ chunks.push(Buffer.from(chunk));
9305
+ callback();
8995
9306
  }
8996
- validatedParams = validationResult.data;
8997
- }
8998
- try {
8999
- const rawResult = await Promise.resolve(gadget.execute(validatedParams));
9000
- if (typeof rawResult === "string") {
9307
+ });
9308
+ stream2.getData = () => Buffer.concat(chunks).toString("utf8");
9309
+ return stream2;
9310
+ }
9311
+ async function collectOutput(stream2, timeout = 5e3) {
9312
+ return new Promise((resolve, reject) => {
9313
+ const chunks = [];
9314
+ const timeoutId = setTimeout(() => {
9315
+ resolve(Buffer.concat(chunks).toString("utf8"));
9316
+ }, timeout);
9317
+ stream2.on("data", (chunk) => {
9318
+ chunks.push(Buffer.from(chunk));
9319
+ });
9320
+ stream2.on("end", () => {
9321
+ clearTimeout(timeoutId);
9322
+ resolve(Buffer.concat(chunks).toString("utf8"));
9323
+ });
9324
+ stream2.on("error", (err) => {
9325
+ clearTimeout(timeoutId);
9326
+ reject(err);
9327
+ });
9328
+ });
9329
+ }
9330
+ function getBufferedOutput(stream2) {
9331
+ const chunks = [];
9332
+ for (; ; ) {
9333
+ const chunk = stream2.read();
9334
+ if (chunk === null) break;
9335
+ chunks.push(chunk);
9336
+ }
9337
+ return Buffer.concat(chunks).toString("utf8");
9338
+ }
9339
+ function createMockPrompt(responses) {
9340
+ let index = 0;
9341
+ return async (_question) => {
9342
+ if (index >= responses.length) {
9343
+ throw new Error(`Mock prompt exhausted: no response for question ${index + 1}`);
9344
+ }
9345
+ return responses[index++];
9346
+ };
9347
+ }
9348
+ var MockPromptRecorder = class {
9349
+ responses;
9350
+ index = 0;
9351
+ questions = [];
9352
+ constructor(responses) {
9353
+ this.responses = responses;
9354
+ }
9355
+ /**
9356
+ * The prompt function to use in tests.
9357
+ */
9358
+ prompt = async (question) => {
9359
+ this.questions.push(question);
9360
+ if (this.index >= this.responses.length) {
9361
+ throw new Error(`Mock prompt exhausted after ${this.index} questions`);
9362
+ }
9363
+ return this.responses[this.index++];
9364
+ };
9365
+ /**
9366
+ * Get all questions that were asked.
9367
+ */
9368
+ getQuestions() {
9369
+ return [...this.questions];
9370
+ }
9371
+ /**
9372
+ * Get the number of questions asked.
9373
+ */
9374
+ getQuestionCount() {
9375
+ return this.questions.length;
9376
+ }
9377
+ /**
9378
+ * Reset the recorder state.
9379
+ */
9380
+ reset(newResponses) {
9381
+ this.index = 0;
9382
+ this.questions = [];
9383
+ if (newResponses) {
9384
+ this.responses = newResponses;
9385
+ }
9386
+ }
9387
+ };
9388
+ async function waitFor(condition, timeout = 5e3, interval = 50) {
9389
+ const startTime = Date.now();
9390
+ while (!condition()) {
9391
+ if (Date.now() - startTime > timeout) {
9392
+ throw new Error(`waitFor timed out after ${timeout}ms`);
9393
+ }
9394
+ await sleep(interval);
9395
+ }
9396
+ }
9397
+ function sleep(ms) {
9398
+ return new Promise((resolve) => setTimeout(resolve, ms));
9399
+ }
9400
+ function filterDefinedEnv(env) {
9401
+ const result = {};
9402
+ for (const [key, value] of Object.entries(env)) {
9403
+ if (value !== void 0) {
9404
+ result[key] = value;
9405
+ }
9406
+ }
9407
+ return result;
9408
+ }
9409
+
9410
+ // src/testing/conversation-fixtures.ts
9411
+ function createConversation(turnCount, options) {
9412
+ const messages = [];
9413
+ const userPrefix = options?.userPrefix ?? "User message";
9414
+ const assistantPrefix = options?.assistantPrefix ?? "Assistant response";
9415
+ const contentLength = options?.contentLength ?? 100;
9416
+ for (let i = 0; i < turnCount; i++) {
9417
+ const padding = " ".repeat(Math.max(0, contentLength - 30));
9418
+ messages.push({
9419
+ role: "user",
9420
+ content: `${userPrefix} ${i + 1}: This is turn ${i + 1} of the conversation.${padding}`
9421
+ });
9422
+ messages.push({
9423
+ role: "assistant",
9424
+ content: `${assistantPrefix} ${i + 1}: I acknowledge turn ${i + 1}.${padding}`
9425
+ });
9426
+ }
9427
+ return messages;
9428
+ }
9429
+ function createConversationWithGadgets(turnCount, gadgetCallsPerTurn = 1, options) {
9430
+ const messages = [];
9431
+ const gadgetNames = options?.gadgetNames ?? ["search", "calculate", "read"];
9432
+ const contentLength = options?.contentLength ?? 50;
9433
+ let gadgetIndex = 0;
9434
+ for (let turn = 0; turn < turnCount; turn++) {
9435
+ messages.push({
9436
+ role: "user",
9437
+ content: `User request ${turn + 1}${"x".repeat(contentLength)}`
9438
+ });
9439
+ for (let g = 0; g < gadgetCallsPerTurn; g++) {
9440
+ const gadgetName = gadgetNames[gadgetIndex % gadgetNames.length];
9441
+ gadgetIndex++;
9442
+ messages.push({
9443
+ role: "assistant",
9444
+ content: `!!!GADGET_START:${gadgetName}
9445
+ !!!ARG:query
9446
+ test query ${turn}-${g}
9447
+ !!!GADGET_END`
9448
+ });
9449
+ messages.push({
9450
+ role: "user",
9451
+ content: `Result: Gadget ${gadgetName} returned result for query ${turn}-${g}`
9452
+ });
9453
+ }
9454
+ messages.push({
9455
+ role: "assistant",
9456
+ content: `Final response for turn ${turn + 1}${"y".repeat(contentLength)}`
9457
+ });
9458
+ }
9459
+ return messages;
9460
+ }
9461
+ function estimateTokens(messages) {
9462
+ return Math.ceil(messages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0) / 4);
9463
+ }
9464
+ function createUserMessage(content) {
9465
+ return { role: "user", content };
9466
+ }
9467
+ function createAssistantMessage(content) {
9468
+ return { role: "assistant", content };
9469
+ }
9470
+ function createSystemMessage(content) {
9471
+ return { role: "system", content };
9472
+ }
9473
+ function createMinimalConversation() {
9474
+ return [
9475
+ { role: "user", content: "Hello" },
9476
+ { role: "assistant", content: "Hi there!" }
9477
+ ];
9478
+ }
9479
+ function createLargeConversation(targetTokens, options) {
9480
+ const tokensPerTurn = options?.tokensPerTurn ?? 200;
9481
+ const turnsNeeded = Math.ceil(targetTokens / tokensPerTurn);
9482
+ const charsPerMessage = Math.floor(tokensPerTurn * 4 / 2);
9483
+ return createConversation(turnsNeeded, {
9484
+ contentLength: charsPerMessage
9485
+ });
9486
+ }
9487
+
9488
+ // src/gadgets/validation.ts
9489
+ function validateAndApplyDefaults(schema, params) {
9490
+ const result = schema.safeParse(params);
9491
+ if (result.success) {
9492
+ return {
9493
+ success: true,
9494
+ data: result.data
9495
+ };
9496
+ }
9497
+ const issues = result.error.issues.map((issue) => ({
9498
+ path: issue.path.join(".") || "root",
9499
+ message: issue.message
9500
+ }));
9501
+ const formattedError = `Invalid parameters: ${issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`;
9502
+ return {
9503
+ success: false,
9504
+ error: formattedError,
9505
+ issues
9506
+ };
9507
+ }
9508
+ function validateGadgetParams(gadget, params) {
9509
+ if (!gadget.parameterSchema) {
9510
+ return {
9511
+ success: true,
9512
+ data: params
9513
+ };
9514
+ }
9515
+ return validateAndApplyDefaults(gadget.parameterSchema, params);
9516
+ }
9517
+
9518
+ // src/testing/gadget-testing.ts
9519
+ async function testGadget(gadget, params, options) {
9520
+ let validatedParams = params;
9521
+ if (!options?.skipValidation) {
9522
+ const validationResult = validateGadgetParams(gadget, params);
9523
+ if (!validationResult.success) {
9524
+ return {
9525
+ error: validationResult.error,
9526
+ validatedParams: params
9527
+ };
9528
+ }
9529
+ validatedParams = validationResult.data;
9530
+ }
9531
+ try {
9532
+ const rawResult = await Promise.resolve(gadget.execute(validatedParams));
9533
+ if (typeof rawResult === "string") {
9001
9534
  return {
9002
9535
  result: rawResult,
9003
9536
  validatedParams,
@@ -9189,7 +9722,7 @@ function getMockManager(options) {
9189
9722
 
9190
9723
  // src/testing/mock-stream.ts
9191
9724
  init_constants();
9192
- function sleep(ms) {
9725
+ function sleep2(ms) {
9193
9726
  return new Promise((resolve) => setTimeout(resolve, ms));
9194
9727
  }
9195
9728
  function generateInvocationId() {
@@ -9270,7 +9803,7 @@ ${blockParams}${GADGET_END_PREFIX}`;
9270
9803
  }
9271
9804
  async function* createMockStream(response) {
9272
9805
  if (response.delayMs) {
9273
- await sleep(response.delayMs);
9806
+ await sleep2(response.delayMs);
9274
9807
  }
9275
9808
  const streamDelay = response.streamDelayMs ?? 0;
9276
9809
  let fullText = response.text ?? "";
@@ -9295,7 +9828,7 @@ async function* createMockStream(response) {
9295
9828
  }
9296
9829
  yield chunk;
9297
9830
  if (streamDelay > 0 && !isLast) {
9298
- await sleep(streamDelay);
9831
+ await sleep2(streamDelay);
9299
9832
  }
9300
9833
  }
9301
9834
  } else {
@@ -9572,7 +10105,9 @@ var MockBuilder = class {
9572
10105
  */
9573
10106
  whenMessageContains(text3) {
9574
10107
  this.matchers.push(
9575
- (ctx) => ctx.messages.some((msg) => extractText(msg.content).toLowerCase().includes(text3.toLowerCase()))
10108
+ (ctx) => ctx.messages.some(
10109
+ (msg) => extractText(msg.content).toLowerCase().includes(text3.toLowerCase())
10110
+ )
9576
10111
  );
9577
10112
  return this;
9578
10113
  }
@@ -10010,32 +10545,161 @@ function createMockClient(options) {
10010
10545
  });
10011
10546
  }
10012
10547
 
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
- }
10548
+ // src/testing/mock-conversation.ts
10549
+ var MockConversationManager = class {
10550
+ history;
10551
+ baseMessages;
10552
+ replacementHistory;
10553
+ replaceHistoryCallCount = 0;
10554
+ addedMessages = [];
10555
+ constructor(history = [], baseMessages = []) {
10556
+ this.history = [...history];
10557
+ this.baseMessages = [...baseMessages];
10558
+ }
10559
+ addUserMessage(content) {
10560
+ const msg = { role: "user", content };
10561
+ this.history.push(msg);
10562
+ this.addedMessages.push(msg);
10563
+ }
10564
+ addAssistantMessage(content) {
10565
+ const msg = { role: "assistant", content };
10566
+ this.history.push(msg);
10567
+ this.addedMessages.push(msg);
10568
+ }
10569
+ addGadgetCall(gadgetName, parameters, result) {
10570
+ const assistantMsg = {
10571
+ role: "assistant",
10572
+ content: `!!!GADGET_START:${gadgetName}
10573
+ ${JSON.stringify(parameters)}
10574
+ !!!GADGET_END`
10575
+ };
10576
+ const resultMsg = {
10577
+ role: "user",
10578
+ content: `Result: ${result}`
10579
+ };
10580
+ this.history.push(assistantMsg);
10581
+ this.history.push(resultMsg);
10582
+ this.addedMessages.push(assistantMsg);
10583
+ this.addedMessages.push(resultMsg);
10584
+ }
10585
+ getMessages() {
10586
+ return [...this.baseMessages, ...this.history];
10587
+ }
10588
+ getHistoryMessages() {
10589
+ return [...this.history];
10590
+ }
10591
+ getBaseMessages() {
10592
+ return [...this.baseMessages];
10593
+ }
10594
+ replaceHistory(newHistory) {
10595
+ this.replacementHistory = [...newHistory];
10596
+ this.history = [...newHistory];
10597
+ this.replaceHistoryCallCount++;
10598
+ }
10599
+ // ============================================
10600
+ // Test Helper Methods
10601
+ // ============================================
10602
+ /**
10603
+ * Check if replaceHistory was called.
10604
+ */
10605
+ wasReplaceHistoryCalled() {
10606
+ return this.replaceHistoryCallCount > 0;
10607
+ }
10608
+ /**
10609
+ * Get the number of times replaceHistory was called.
10610
+ */
10611
+ getReplaceHistoryCallCount() {
10612
+ return this.replaceHistoryCallCount;
10613
+ }
10614
+ /**
10615
+ * Get the most recent history passed to replaceHistory.
10616
+ * Returns undefined if replaceHistory was never called.
10617
+ */
10618
+ getReplacementHistory() {
10619
+ return this.replacementHistory;
10620
+ }
10621
+ /**
10622
+ * Get all messages that were added via add* methods.
10623
+ */
10624
+ getAddedMessages() {
10625
+ return [...this.addedMessages];
10626
+ }
10627
+ /**
10628
+ * Reset all tracking state while preserving the conversation.
10629
+ */
10630
+ resetTracking() {
10631
+ this.replacementHistory = void 0;
10632
+ this.replaceHistoryCallCount = 0;
10633
+ this.addedMessages = [];
10634
+ }
10635
+ /**
10636
+ * Completely reset the mock to initial state.
10637
+ * Note: baseMessages cannot be changed after construction.
10638
+ */
10639
+ reset(history = []) {
10640
+ this.history = [...history];
10641
+ this.resetTracking();
10642
+ }
10643
+ /**
10644
+ * Set the history directly (for test setup).
10645
+ */
10646
+ setHistory(messages) {
10647
+ this.history = [...messages];
10648
+ }
10649
+ /**
10650
+ * Get the current history length.
10651
+ */
10652
+ getHistoryLength() {
10653
+ return this.history.length;
10654
+ }
10655
+ /**
10656
+ * Get total message count (base + history).
10657
+ */
10658
+ getTotalMessageCount() {
10659
+ return this.baseMessages.length + this.history.length;
10660
+ }
10661
+ };
10662
+ function createMockConversationManager(turnCount, baseMessages = []) {
10663
+ const history = [];
10664
+ for (let i = 0; i < turnCount; i++) {
10665
+ history.push({
10666
+ role: "user",
10667
+ content: `User message ${i + 1}: This is turn ${i + 1} of the conversation.`
10668
+ });
10669
+ history.push({
10670
+ role: "assistant",
10671
+ content: `Assistant response ${i + 1}: I acknowledge turn ${i + 1}.`
10672
+ });
10673
+ }
10674
+ return new MockConversationManager(history, baseMessages);
10675
+ }
10676
+
10677
+ // src/testing/mock-gadget.ts
10678
+ init_gadget();
10679
+ var MockGadgetImpl = class extends BaseGadget {
10680
+ name;
10681
+ description;
10682
+ parameterSchema;
10683
+ timeoutMs;
10684
+ calls = [];
10685
+ resultValue;
10686
+ resultFn;
10687
+ errorToThrow;
10688
+ delayMs;
10689
+ shouldTrackCalls;
10690
+ constructor(config) {
10691
+ super();
10692
+ this.name = config.name;
10693
+ this.description = config.description ?? `Mock gadget: ${config.name}`;
10694
+ this.parameterSchema = config.schema;
10695
+ this.resultValue = config.result;
10696
+ this.resultFn = config.resultFn;
10697
+ this.delayMs = config.delayMs ?? 0;
10698
+ this.shouldTrackCalls = config.trackCalls ?? true;
10699
+ this.timeoutMs = config.timeoutMs;
10700
+ if (config.error) {
10701
+ this.errorToThrow = typeof config.error === "string" ? new Error(config.error) : config.error;
10702
+ }
10039
10703
  }
10040
10704
  async execute(params) {
10041
10705
  if (this.shouldTrackCalls) {
@@ -10169,7 +10833,7 @@ function createTestStream(chunks) {
10169
10833
  function createTextStream(text3, options) {
10170
10834
  return async function* () {
10171
10835
  if (options?.delayMs) {
10172
- await sleep2(options.delayMs);
10836
+ await sleep3(options.delayMs);
10173
10837
  }
10174
10838
  const chunkSize = options?.chunkSize ?? text3.length;
10175
10839
  const chunks = [];
@@ -10191,7 +10855,7 @@ function createTextStream(text3, options) {
10191
10855
  }
10192
10856
  yield chunk;
10193
10857
  if (options?.chunkDelayMs && !isLast) {
10194
- await sleep2(options.chunkDelayMs);
10858
+ await sleep3(options.chunkDelayMs);
10195
10859
  }
10196
10860
  }
10197
10861
  }();
@@ -10229,365 +10893,9 @@ function createErrorStream(chunksBeforeError, error) {
10229
10893
  throw error;
10230
10894
  }();
10231
10895
  }
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
10896
  function sleep3(ms) {
10580
10897
  return new Promise((resolve) => setTimeout(resolve, ms));
10581
10898
  }
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
10899
  // Annotate the CommonJS export names for ESM import in node:
10592
10900
  0 && (module.exports = {
10593
10901
  MockBuilder,