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.
@@ -260,7 +260,9 @@ var init_input_content = __esm({
260
260
  // WAV (RIFF)
261
261
  { bytes: [82, 73, 70, 70], mimeType: "audio/wav" },
262
262
  // WebM
263
- { bytes: [26, 69, 223, 163], mimeType: "audio/webm" }
263
+ { bytes: [26, 69, 223, 163], mimeType: "audio/webm" },
264
+ // FLAC (fLaC)
265
+ { bytes: [102, 76, 97, 67], mimeType: "audio/flac" }
264
266
  ];
265
267
  }
266
268
  });
@@ -318,13 +320,13 @@ var init_prompt_config = __esm({
318
320
  });
319
321
 
320
322
  // src/core/messages.ts
321
- function normalizeContent(content) {
323
+ function normalizeMessageContent(content) {
322
324
  if (typeof content === "string") {
323
325
  return [{ type: "text", text: content }];
324
326
  }
325
327
  return content;
326
328
  }
327
- function extractText(content) {
329
+ function extractMessageText(content) {
328
330
  if (typeof content === "string") {
329
331
  return content;
330
332
  }
@@ -684,7 +686,17 @@ Produces: { "items": ["first", "second"] }`);
684
686
  this.messages.push({ role: "user", content: parts });
685
687
  return this;
686
688
  }
687
- addGadgetCall(gadget, parameters, result) {
689
+ /**
690
+ * Record a gadget execution result in the message history.
691
+ * Creates an assistant message with the gadget invocation and a user message with the result.
692
+ *
693
+ * @param gadget - Name of the gadget that was executed
694
+ * @param parameters - Parameters that were passed to the gadget
695
+ * @param result - Text result from the gadget execution
696
+ * @param media - Optional media outputs from the gadget
697
+ * @param mediaIds - Optional IDs for the media outputs
698
+ */
699
+ addGadgetCallResult(gadget, parameters, result, media, mediaIds) {
688
700
  const paramStr = this.formatBlockParameters(parameters, "");
689
701
  this.messages.push({
690
702
  role: "assistant",
@@ -692,10 +704,25 @@ Produces: { "items": ["first", "second"] }`);
692
704
  ${paramStr}
693
705
  ${this.endPrefix}`
694
706
  });
695
- this.messages.push({
696
- role: "user",
697
- content: `Result: ${result}`
698
- });
707
+ if (media && media.length > 0 && mediaIds && mediaIds.length > 0) {
708
+ const idRefs = media.map((m, i) => `[Media: ${mediaIds[i]} (${m.kind})]`).join("\n");
709
+ const textWithIds = `Result: ${result}
710
+ ${idRefs}`;
711
+ const parts = [text(textWithIds)];
712
+ for (const item of media) {
713
+ if (item.kind === "image") {
714
+ parts.push(imageFromBase64(item.data, item.mimeType));
715
+ } else if (item.kind === "audio") {
716
+ parts.push(audioFromBase64(item.data, item.mimeType));
717
+ }
718
+ }
719
+ this.messages.push({ role: "user", content: parts });
720
+ } else {
721
+ this.messages.push({
722
+ role: "user",
723
+ content: `Result: ${result}`
724
+ });
725
+ }
699
726
  return this;
700
727
  }
701
728
  /**
@@ -1045,22 +1072,226 @@ var init_registry = __esm({
1045
1072
  }
1046
1073
  });
1047
1074
 
1075
+ // src/gadgets/media-store.ts
1076
+ import { randomBytes } from "node:crypto";
1077
+ import { mkdir, rm, writeFile } from "node:fs/promises";
1078
+ import { homedir } from "node:os";
1079
+ import { join } from "node:path";
1080
+ function getLlmistTmpDir() {
1081
+ return join(homedir(), ".llmist", "tmp");
1082
+ }
1083
+ var MIME_TO_EXTENSION, MediaStore;
1084
+ var init_media_store = __esm({
1085
+ "src/gadgets/media-store.ts"() {
1086
+ "use strict";
1087
+ MIME_TO_EXTENSION = {
1088
+ // Images
1089
+ "image/png": ".png",
1090
+ "image/jpeg": ".jpg",
1091
+ "image/gif": ".gif",
1092
+ "image/webp": ".webp",
1093
+ "image/svg+xml": ".svg",
1094
+ "image/bmp": ".bmp",
1095
+ "image/tiff": ".tiff",
1096
+ // Audio
1097
+ "audio/mp3": ".mp3",
1098
+ "audio/mpeg": ".mp3",
1099
+ "audio/wav": ".wav",
1100
+ "audio/webm": ".webm",
1101
+ "audio/ogg": ".ogg",
1102
+ "audio/flac": ".flac",
1103
+ "audio/aac": ".aac",
1104
+ // Video
1105
+ "video/mp4": ".mp4",
1106
+ "video/webm": ".webm",
1107
+ "video/ogg": ".ogv",
1108
+ "video/quicktime": ".mov",
1109
+ "video/x-msvideo": ".avi",
1110
+ // Documents
1111
+ "application/pdf": ".pdf",
1112
+ "application/json": ".json",
1113
+ "text/plain": ".txt",
1114
+ "text/html": ".html",
1115
+ "text/css": ".css",
1116
+ "text/javascript": ".js"
1117
+ };
1118
+ MediaStore = class {
1119
+ items = /* @__PURE__ */ new Map();
1120
+ outputDir;
1121
+ counter = 0;
1122
+ initialized = false;
1123
+ /**
1124
+ * Create a new MediaStore.
1125
+ *
1126
+ * @param sessionId - Optional session ID for the output directory.
1127
+ * If not provided, a random ID is generated.
1128
+ */
1129
+ constructor(sessionId) {
1130
+ const id = sessionId ?? randomBytes(8).toString("hex");
1131
+ this.outputDir = join(getLlmistTmpDir(), `media-${id}`);
1132
+ }
1133
+ /**
1134
+ * Get the output directory path.
1135
+ */
1136
+ getOutputDir() {
1137
+ return this.outputDir;
1138
+ }
1139
+ /**
1140
+ * Ensure the output directory exists.
1141
+ * @throws Error if directory creation fails
1142
+ */
1143
+ async ensureDir() {
1144
+ if (this.initialized) return;
1145
+ try {
1146
+ await mkdir(this.outputDir, { recursive: true });
1147
+ this.initialized = true;
1148
+ } catch (error) {
1149
+ throw new Error(
1150
+ `MediaStore: Failed to create directory ${this.outputDir}: ${error instanceof Error ? error.message : String(error)}`
1151
+ );
1152
+ }
1153
+ }
1154
+ /**
1155
+ * Generate a unique media ID.
1156
+ * Format: "media_" + 6 random alphanumeric characters
1157
+ */
1158
+ generateId() {
1159
+ const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
1160
+ let id = "media_";
1161
+ const bytes = randomBytes(6);
1162
+ for (let i = 0; i < 6; i++) {
1163
+ id += chars[bytes[i] % chars.length];
1164
+ }
1165
+ return id;
1166
+ }
1167
+ /**
1168
+ * Get file extension from MIME type.
1169
+ */
1170
+ getExtension(mimeType) {
1171
+ return MIME_TO_EXTENSION[mimeType] ?? ".bin";
1172
+ }
1173
+ /**
1174
+ * Store media and return stored metadata with ID.
1175
+ *
1176
+ * @param media - The media output from a gadget
1177
+ * @param gadgetName - Name of the gadget that created this media
1178
+ * @returns Stored media information including generated ID
1179
+ * @throws Error if file write fails
1180
+ */
1181
+ async store(media, gadgetName) {
1182
+ await this.ensureDir();
1183
+ const id = this.generateId();
1184
+ const ext = this.getExtension(media.mimeType);
1185
+ const filename = media.fileName ?? `${gadgetName}_${String(++this.counter).padStart(3, "0")}${ext}`;
1186
+ const filePath = join(this.outputDir, filename);
1187
+ const buffer = Buffer.from(media.data, "base64");
1188
+ try {
1189
+ await writeFile(filePath, buffer);
1190
+ } catch (error) {
1191
+ throw new Error(
1192
+ `MediaStore: Failed to write media file ${filePath}: ${error instanceof Error ? error.message : String(error)}`
1193
+ );
1194
+ }
1195
+ const stored = {
1196
+ id,
1197
+ kind: media.kind,
1198
+ path: filePath,
1199
+ mimeType: media.mimeType,
1200
+ sizeBytes: buffer.length,
1201
+ description: media.description,
1202
+ metadata: media.metadata,
1203
+ gadgetName,
1204
+ createdAt: /* @__PURE__ */ new Date()
1205
+ };
1206
+ this.items.set(id, stored);
1207
+ return stored;
1208
+ }
1209
+ /**
1210
+ * Get stored media by ID.
1211
+ *
1212
+ * @param id - The media ID (e.g., "media_a1b2c3")
1213
+ * @returns The stored media or undefined if not found
1214
+ */
1215
+ get(id) {
1216
+ return this.items.get(id);
1217
+ }
1218
+ /**
1219
+ * Get the actual file path for a media ID.
1220
+ * Convenience method for gadgets that need the raw path.
1221
+ *
1222
+ * @param id - The media ID
1223
+ * @returns The file path or undefined if not found
1224
+ */
1225
+ getPath(id) {
1226
+ return this.items.get(id)?.path;
1227
+ }
1228
+ /**
1229
+ * List all stored media, optionally filtered by kind.
1230
+ *
1231
+ * @param kind - Optional media kind to filter by
1232
+ * @returns Array of stored media items
1233
+ */
1234
+ list(kind) {
1235
+ const all = Array.from(this.items.values());
1236
+ if (kind) {
1237
+ return all.filter((item) => item.kind === kind);
1238
+ }
1239
+ return all;
1240
+ }
1241
+ /**
1242
+ * Get the count of stored media items.
1243
+ */
1244
+ get size() {
1245
+ return this.items.size;
1246
+ }
1247
+ /**
1248
+ * Check if a media ID exists.
1249
+ */
1250
+ has(id) {
1251
+ return this.items.has(id);
1252
+ }
1253
+ /**
1254
+ * Clear in-memory store without deleting files.
1255
+ * Resets the counter but leaves files on disk.
1256
+ */
1257
+ clear() {
1258
+ this.items.clear();
1259
+ this.counter = 0;
1260
+ }
1261
+ /**
1262
+ * Delete all stored files and clear memory.
1263
+ * Removes the entire session directory.
1264
+ */
1265
+ async cleanup() {
1266
+ if (this.initialized) {
1267
+ try {
1268
+ await rm(this.outputDir, { recursive: true, force: true });
1269
+ } catch {
1270
+ }
1271
+ this.initialized = false;
1272
+ }
1273
+ this.clear();
1274
+ }
1275
+ };
1276
+ }
1277
+ });
1278
+
1048
1279
  // src/gadgets/exceptions.ts
1049
- var BreakLoopException, HumanInputException, TimeoutException, AbortError;
1280
+ var TaskCompletionSignal, HumanInputRequiredException, TimeoutException, AbortException;
1050
1281
  var init_exceptions = __esm({
1051
1282
  "src/gadgets/exceptions.ts"() {
1052
1283
  "use strict";
1053
- BreakLoopException = class extends Error {
1284
+ TaskCompletionSignal = class extends Error {
1054
1285
  constructor(message) {
1055
1286
  super(message ?? "Agent loop terminated by gadget");
1056
- this.name = "BreakLoopException";
1287
+ this.name = "TaskCompletionSignal";
1057
1288
  }
1058
1289
  };
1059
- HumanInputException = class extends Error {
1290
+ HumanInputRequiredException = class extends Error {
1060
1291
  question;
1061
1292
  constructor(question) {
1062
1293
  super(`Human input required: ${question}`);
1063
- this.name = "HumanInputException";
1294
+ this.name = "HumanInputRequiredException";
1064
1295
  this.question = question;
1065
1296
  }
1066
1297
  };
@@ -1074,10 +1305,10 @@ var init_exceptions = __esm({
1074
1305
  this.timeoutMs = timeoutMs;
1075
1306
  }
1076
1307
  };
1077
- AbortError = class extends Error {
1308
+ AbortException = class extends Error {
1078
1309
  constructor(message) {
1079
1310
  super(message || "Gadget execution was aborted");
1080
- this.name = "AbortError";
1311
+ this.name = "AbortException";
1081
1312
  }
1082
1313
  };
1083
1314
  }
@@ -1166,7 +1397,7 @@ var init_schema_to_json = __esm({
1166
1397
  });
1167
1398
 
1168
1399
  // src/gadgets/gadget.ts
1169
- function formatParamsAsBlock(params, prefix = "", argPrefix = GADGET_ARG_PREFIX) {
1400
+ function formatParamsForBlockExample(params, prefix = "", argPrefix = GADGET_ARG_PREFIX) {
1170
1401
  const lines = [];
1171
1402
  for (const [key, value] of Object.entries(params)) {
1172
1403
  const fullPath = prefix ? `${prefix}/${key}` : key;
@@ -1174,14 +1405,14 @@ function formatParamsAsBlock(params, prefix = "", argPrefix = GADGET_ARG_PREFIX)
1174
1405
  value.forEach((item, index) => {
1175
1406
  const itemPath = `${fullPath}/${index}`;
1176
1407
  if (typeof item === "object" && item !== null) {
1177
- lines.push(formatParamsAsBlock(item, itemPath, argPrefix));
1408
+ lines.push(formatParamsForBlockExample(item, itemPath, argPrefix));
1178
1409
  } else {
1179
1410
  lines.push(`${argPrefix}${itemPath}`);
1180
1411
  lines.push(String(item));
1181
1412
  }
1182
1413
  });
1183
1414
  } else if (typeof value === "object" && value !== null) {
1184
- lines.push(formatParamsAsBlock(value, fullPath, argPrefix));
1415
+ lines.push(formatParamsForBlockExample(value, fullPath, argPrefix));
1185
1416
  } else {
1186
1417
  lines.push(`${argPrefix}${fullPath}`);
1187
1418
  lines.push(String(value));
@@ -1270,7 +1501,7 @@ function formatSchemaAsPlainText(schema, indent = "", atRoot = true) {
1270
1501
  }
1271
1502
  return lines.join("\n");
1272
1503
  }
1273
- var BaseGadget;
1504
+ var AbstractGadget;
1274
1505
  var init_gadget = __esm({
1275
1506
  "src/gadgets/gadget.ts"() {
1276
1507
  "use strict";
@@ -1278,7 +1509,7 @@ var init_gadget = __esm({
1278
1509
  init_exceptions();
1279
1510
  init_schema_to_json();
1280
1511
  init_schema_validator();
1281
- BaseGadget = class {
1512
+ AbstractGadget = class {
1282
1513
  /**
1283
1514
  * The name of the gadget. Used for identification when LLM calls it.
1284
1515
  * If not provided, defaults to the class name.
@@ -1306,14 +1537,14 @@ var init_gadget = __esm({
1306
1537
  */
1307
1538
  examples;
1308
1539
  /**
1309
- * Throws an AbortError if the execution has been aborted.
1540
+ * Throws an AbortException if the execution has been aborted.
1310
1541
  *
1311
1542
  * Call this at key checkpoints in long-running gadgets to allow early exit
1312
1543
  * when the gadget has been cancelled (e.g., due to timeout). This enables
1313
1544
  * resource cleanup and prevents unnecessary work after cancellation.
1314
1545
  *
1315
1546
  * @param ctx - The execution context containing the abort signal
1316
- * @throws AbortError if ctx.signal.aborted is true
1547
+ * @throws AbortException if ctx.signal.aborted is true
1317
1548
  *
1318
1549
  * @example
1319
1550
  * ```typescript
@@ -1338,7 +1569,7 @@ var init_gadget = __esm({
1338
1569
  */
1339
1570
  throwIfAborted(ctx) {
1340
1571
  if (ctx?.signal?.aborted) {
1341
- throw new AbortError();
1572
+ throw new AbortException();
1342
1573
  }
1343
1574
  }
1344
1575
  /**
@@ -1479,7 +1710,9 @@ var init_gadget = __esm({
1479
1710
  parts.push(`# ${example.comment}`);
1480
1711
  }
1481
1712
  parts.push(`${effectiveStartPrefix}${gadgetName}`);
1482
- parts.push(formatParamsAsBlock(example.params, "", effectiveArgPrefix));
1713
+ parts.push(
1714
+ formatParamsForBlockExample(example.params, "", effectiveArgPrefix)
1715
+ );
1483
1716
  parts.push(effectiveEndPrefix);
1484
1717
  if (example.output !== void 0) {
1485
1718
  parts.push("");
@@ -1496,7 +1729,7 @@ var init_gadget = __esm({
1496
1729
 
1497
1730
  // src/gadgets/create-gadget.ts
1498
1731
  function createGadget(config) {
1499
- class DynamicGadget extends BaseGadget {
1732
+ class DynamicGadget extends AbstractGadget {
1500
1733
  name = config.name;
1501
1734
  description = config.description;
1502
1735
  parameterSchema = config.schema;
@@ -1681,6 +1914,18 @@ var init_output_viewer = __esm({
1681
1914
  }
1682
1915
  });
1683
1916
 
1917
+ // src/agent/agent-internal-key.ts
1918
+ function isValidAgentKey(key) {
1919
+ return key === AGENT_INTERNAL_KEY;
1920
+ }
1921
+ var AGENT_INTERNAL_KEY;
1922
+ var init_agent_internal_key = __esm({
1923
+ "src/agent/agent-internal-key.ts"() {
1924
+ "use strict";
1925
+ AGENT_INTERNAL_KEY = Symbol("AGENT_INTERNAL_KEY");
1926
+ }
1927
+ });
1928
+
1684
1929
  // src/agent/compaction/config.ts
1685
1930
  function resolveCompactionConfig(config = {}) {
1686
1931
  const trigger = config.triggerThresholdPercent ?? DEFAULT_COMPACTION_CONFIG.triggerThresholdPercent;
@@ -1928,9 +2173,9 @@ var init_hybrid = __esm({
1928
2173
  var init_strategies = __esm({
1929
2174
  "src/agent/compaction/strategies/index.ts"() {
1930
2175
  "use strict";
2176
+ init_hybrid();
1931
2177
  init_sliding_window();
1932
2178
  init_summarization();
1933
- init_hybrid();
1934
2179
  }
1935
2180
  });
1936
2181
 
@@ -2092,98 +2337,6 @@ var init_manager = __esm({
2092
2337
  }
2093
2338
  });
2094
2339
 
2095
- // src/agent/gadget-output-store.ts
2096
- import { randomBytes } from "node:crypto";
2097
- var GadgetOutputStore;
2098
- var init_gadget_output_store = __esm({
2099
- "src/agent/gadget-output-store.ts"() {
2100
- "use strict";
2101
- GadgetOutputStore = class {
2102
- outputs = /* @__PURE__ */ new Map();
2103
- /**
2104
- * Store a gadget output and return its ID.
2105
- *
2106
- * @param gadgetName - Name of the gadget that produced the output
2107
- * @param content - Full output content to store
2108
- * @returns Generated ID for retrieving the output later
2109
- */
2110
- store(gadgetName, content) {
2111
- const id = this.generateId(gadgetName);
2112
- const encoder = new TextEncoder();
2113
- const stored = {
2114
- id,
2115
- gadgetName,
2116
- content,
2117
- byteSize: encoder.encode(content).length,
2118
- lineCount: content.split("\n").length,
2119
- timestamp: /* @__PURE__ */ new Date()
2120
- };
2121
- this.outputs.set(id, stored);
2122
- return id;
2123
- }
2124
- /**
2125
- * Retrieve a stored output by ID.
2126
- *
2127
- * @param id - The output ID (e.g., "Search_d34db33f")
2128
- * @returns The stored output or undefined if not found
2129
- */
2130
- get(id) {
2131
- return this.outputs.get(id);
2132
- }
2133
- /**
2134
- * Check if an output exists.
2135
- *
2136
- * @param id - The output ID to check
2137
- * @returns True if the output exists
2138
- */
2139
- has(id) {
2140
- return this.outputs.has(id);
2141
- }
2142
- /**
2143
- * Get all stored output IDs.
2144
- *
2145
- * @returns Array of output IDs
2146
- */
2147
- getIds() {
2148
- return Array.from(this.outputs.keys());
2149
- }
2150
- /**
2151
- * Get the number of stored outputs.
2152
- */
2153
- get size() {
2154
- return this.outputs.size;
2155
- }
2156
- /**
2157
- * Clear all stored outputs.
2158
- * Called when the agent run completes.
2159
- */
2160
- clear() {
2161
- this.outputs.clear();
2162
- }
2163
- /**
2164
- * Generate a unique ID for a stored output.
2165
- * Format: {GadgetName}_{8 hex chars}
2166
- */
2167
- generateId(gadgetName) {
2168
- const hex = randomBytes(4).toString("hex");
2169
- return `${gadgetName}_${hex}`;
2170
- }
2171
- };
2172
- }
2173
- });
2174
-
2175
- // src/agent/agent-internal-key.ts
2176
- function isValidAgentKey(key) {
2177
- return key === AGENT_INTERNAL_KEY;
2178
- }
2179
- var AGENT_INTERNAL_KEY;
2180
- var init_agent_internal_key = __esm({
2181
- "src/agent/agent-internal-key.ts"() {
2182
- "use strict";
2183
- AGENT_INTERNAL_KEY = Symbol("AGENT_INTERNAL_KEY");
2184
- }
2185
- });
2186
-
2187
2340
  // src/agent/conversation-manager.ts
2188
2341
  var ConversationManager;
2189
2342
  var init_conversation_manager = __esm({
@@ -2214,8 +2367,8 @@ var init_conversation_manager = __esm({
2214
2367
  addAssistantMessage(content) {
2215
2368
  this.historyBuilder.addAssistant(content);
2216
2369
  }
2217
- addGadgetCall(gadgetName, parameters, result) {
2218
- this.historyBuilder.addGadgetCall(gadgetName, parameters, result);
2370
+ addGadgetCallResult(gadgetName, parameters, result, media, mediaIds) {
2371
+ this.historyBuilder.addGadgetCallResult(gadgetName, parameters, result, media, mediaIds);
2219
2372
  }
2220
2373
  getMessages() {
2221
2374
  return [...this.baseMessages, ...this.initialMessages, ...this.historyBuilder.build()];
@@ -2235,7 +2388,7 @@ var init_conversation_manager = __esm({
2235
2388
  if (msg.role === "user") {
2236
2389
  this.historyBuilder.addUser(msg.content);
2237
2390
  } else if (msg.role === "assistant") {
2238
- this.historyBuilder.addAssistant(extractText(msg.content));
2391
+ this.historyBuilder.addAssistant(extractMessageText(msg.content));
2239
2392
  }
2240
2393
  }
2241
2394
  }
@@ -2329,67 +2482,147 @@ var init_event_handlers = __esm({
2329
2482
  }
2330
2483
  });
2331
2484
 
2332
- // src/agent/hook-validators.ts
2333
- function validateBeforeLLMCallAction(action) {
2334
- if (!action || typeof action !== "object" || !("action" in action)) {
2335
- throw new HookValidationError(
2336
- "beforeLLMCall",
2337
- "Must return an action object with an 'action' field"
2338
- );
2339
- }
2340
- const actionType = action.action;
2341
- if (actionType !== "proceed" && actionType !== "skip") {
2342
- throw new HookValidationError(
2343
- "beforeLLMCall",
2344
- `Invalid action type: ${actionType}. Must be 'proceed' or 'skip'`
2345
- );
2346
- }
2347
- if (actionType === "skip" && !action.syntheticResponse) {
2348
- throw new HookValidationError(
2349
- "beforeLLMCall",
2350
- "When action is 'skip', syntheticResponse is required"
2351
- );
2352
- }
2353
- }
2354
- function validateAfterLLMCallAction(action) {
2355
- if (!action || typeof action !== "object" || !("action" in action)) {
2356
- throw new HookValidationError(
2357
- "afterLLMCall",
2358
- "Must return an action object with an 'action' field"
2359
- );
2360
- }
2361
- const actionType = action.action;
2362
- const validActions = ["continue", "append_messages", "modify_and_continue", "append_and_modify"];
2363
- if (!validActions.includes(actionType)) {
2364
- throw new HookValidationError(
2365
- "afterLLMCall",
2366
- `Invalid action type: ${actionType}. Must be one of: ${validActions.join(", ")}`
2367
- );
2368
- }
2369
- if (actionType === "append_messages" || actionType === "append_and_modify") {
2370
- if (!("messages" in action) || !action.messages || !Array.isArray(action.messages)) {
2371
- throw new HookValidationError(
2372
- "afterLLMCall",
2373
- `When action is '${actionType}', messages array is required`
2374
- );
2375
- }
2376
- if (action.messages.length === 0) {
2377
- throw new HookValidationError(
2378
- "afterLLMCall",
2379
- `When action is '${actionType}', messages array must not be empty`
2380
- );
2381
- }
2382
- for (let i = 0; i < action.messages.length; i++) {
2383
- const msg = action.messages[i];
2384
- if (!msg || typeof msg !== "object") {
2385
- throw new HookValidationError("afterLLMCall", `Message at index ${i} must be an object`);
2386
- }
2387
- if (!msg.role || !msg.content) {
2388
- throw new HookValidationError(
2389
- "afterLLMCall",
2390
- `Message at index ${i} must have 'role' and 'content' fields`
2391
- );
2392
- }
2485
+ // src/agent/gadget-output-store.ts
2486
+ import { randomBytes as randomBytes2 } from "node:crypto";
2487
+ var GadgetOutputStore;
2488
+ var init_gadget_output_store = __esm({
2489
+ "src/agent/gadget-output-store.ts"() {
2490
+ "use strict";
2491
+ GadgetOutputStore = class {
2492
+ outputs = /* @__PURE__ */ new Map();
2493
+ /**
2494
+ * Store a gadget output and return its ID.
2495
+ *
2496
+ * @param gadgetName - Name of the gadget that produced the output
2497
+ * @param content - Full output content to store
2498
+ * @returns Generated ID for retrieving the output later
2499
+ */
2500
+ store(gadgetName, content) {
2501
+ const id = this.generateId(gadgetName);
2502
+ const encoder = new TextEncoder();
2503
+ const stored = {
2504
+ id,
2505
+ gadgetName,
2506
+ content,
2507
+ byteSize: encoder.encode(content).length,
2508
+ lineCount: content.split("\n").length,
2509
+ timestamp: /* @__PURE__ */ new Date()
2510
+ };
2511
+ this.outputs.set(id, stored);
2512
+ return id;
2513
+ }
2514
+ /**
2515
+ * Retrieve a stored output by ID.
2516
+ *
2517
+ * @param id - The output ID (e.g., "Search_d34db33f")
2518
+ * @returns The stored output or undefined if not found
2519
+ */
2520
+ get(id) {
2521
+ return this.outputs.get(id);
2522
+ }
2523
+ /**
2524
+ * Check if an output exists.
2525
+ *
2526
+ * @param id - The output ID to check
2527
+ * @returns True if the output exists
2528
+ */
2529
+ has(id) {
2530
+ return this.outputs.has(id);
2531
+ }
2532
+ /**
2533
+ * Get all stored output IDs.
2534
+ *
2535
+ * @returns Array of output IDs
2536
+ */
2537
+ getIds() {
2538
+ return Array.from(this.outputs.keys());
2539
+ }
2540
+ /**
2541
+ * Get the number of stored outputs.
2542
+ */
2543
+ get size() {
2544
+ return this.outputs.size;
2545
+ }
2546
+ /**
2547
+ * Clear all stored outputs.
2548
+ * Called when the agent run completes.
2549
+ */
2550
+ clear() {
2551
+ this.outputs.clear();
2552
+ }
2553
+ /**
2554
+ * Generate a unique ID for a stored output.
2555
+ * Format: {GadgetName}_{8 hex chars}
2556
+ */
2557
+ generateId(gadgetName) {
2558
+ const hex = randomBytes2(4).toString("hex");
2559
+ return `${gadgetName}_${hex}`;
2560
+ }
2561
+ };
2562
+ }
2563
+ });
2564
+
2565
+ // src/agent/hook-validators.ts
2566
+ function validateBeforeLLMCallAction(action) {
2567
+ if (!action || typeof action !== "object" || !("action" in action)) {
2568
+ throw new HookValidationError(
2569
+ "beforeLLMCall",
2570
+ "Must return an action object with an 'action' field"
2571
+ );
2572
+ }
2573
+ const actionType = action.action;
2574
+ if (actionType !== "proceed" && actionType !== "skip") {
2575
+ throw new HookValidationError(
2576
+ "beforeLLMCall",
2577
+ `Invalid action type: ${actionType}. Must be 'proceed' or 'skip'`
2578
+ );
2579
+ }
2580
+ if (actionType === "skip" && !action.syntheticResponse) {
2581
+ throw new HookValidationError(
2582
+ "beforeLLMCall",
2583
+ "When action is 'skip', syntheticResponse is required"
2584
+ );
2585
+ }
2586
+ }
2587
+ function validateAfterLLMCallAction(action) {
2588
+ if (!action || typeof action !== "object" || !("action" in action)) {
2589
+ throw new HookValidationError(
2590
+ "afterLLMCall",
2591
+ "Must return an action object with an 'action' field"
2592
+ );
2593
+ }
2594
+ const actionType = action.action;
2595
+ const validActions = ["continue", "append_messages", "modify_and_continue", "append_and_modify"];
2596
+ if (!validActions.includes(actionType)) {
2597
+ throw new HookValidationError(
2598
+ "afterLLMCall",
2599
+ `Invalid action type: ${actionType}. Must be one of: ${validActions.join(", ")}`
2600
+ );
2601
+ }
2602
+ if (actionType === "append_messages" || actionType === "append_and_modify") {
2603
+ if (!("messages" in action) || !action.messages || !Array.isArray(action.messages)) {
2604
+ throw new HookValidationError(
2605
+ "afterLLMCall",
2606
+ `When action is '${actionType}', messages array is required`
2607
+ );
2608
+ }
2609
+ if (action.messages.length === 0) {
2610
+ throw new HookValidationError(
2611
+ "afterLLMCall",
2612
+ `When action is '${actionType}', messages array must not be empty`
2613
+ );
2614
+ }
2615
+ for (let i = 0; i < action.messages.length; i++) {
2616
+ const msg = action.messages[i];
2617
+ if (!msg || typeof msg !== "object") {
2618
+ throw new HookValidationError("afterLLMCall", `Message at index ${i} must be an object`);
2619
+ }
2620
+ if (!msg.role || !msg.content) {
2621
+ throw new HookValidationError(
2622
+ "afterLLMCall",
2623
+ `Message at index ${i} must have 'role' and 'content' fields`
2624
+ );
2625
+ }
2393
2626
  if (!["system", "user", "assistant"].includes(msg.role)) {
2394
2627
  throw new HookValidationError(
2395
2628
  "afterLLMCall",
@@ -2639,8 +2872,7 @@ var init_schema_introspector = __esm({
2639
2872
  const values = def?.values;
2640
2873
  const value = values?.[0] ?? def?.value;
2641
2874
  if (typeof value === "string") return "string";
2642
- if (typeof value === "number" || typeof value === "bigint")
2643
- return "number";
2875
+ if (typeof value === "number" || typeof value === "bigint") return "number";
2644
2876
  if (typeof value === "boolean") return "boolean";
2645
2877
  return "unknown";
2646
2878
  }
@@ -2912,7 +3144,13 @@ var init_cost_reporting_client = __esm({
2912
3144
  cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
2913
3145
  }
2914
3146
  }
2915
- this.reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
3147
+ this.reportCostFromUsage(
3148
+ model,
3149
+ inputTokens,
3150
+ outputTokens,
3151
+ cachedInputTokens,
3152
+ cacheCreationInputTokens
3153
+ );
2916
3154
  return result;
2917
3155
  }
2918
3156
  /**
@@ -2952,7 +3190,13 @@ var init_cost_reporting_client = __esm({
2952
3190
  }
2953
3191
  }
2954
3192
  } finally {
2955
- this.reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
3193
+ this.reportCostFromUsage(
3194
+ model,
3195
+ inputTokens,
3196
+ outputTokens,
3197
+ cachedInputTokens,
3198
+ cacheCreationInputTokens
3199
+ );
2956
3200
  }
2957
3201
  }
2958
3202
  /**
@@ -2990,7 +3234,13 @@ var init_cost_reporting_client = __esm({
2990
3234
  }
2991
3235
  } finally {
2992
3236
  if (inputTokens > 0 || outputTokens > 0) {
2993
- reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens);
3237
+ reportCostFromUsage(
3238
+ model,
3239
+ inputTokens,
3240
+ outputTokens,
3241
+ cachedInputTokens,
3242
+ cacheCreationInputTokens
3243
+ );
2994
3244
  }
2995
3245
  }
2996
3246
  }
@@ -3018,12 +3268,12 @@ var init_cost_reporting_client = __esm({
3018
3268
  });
3019
3269
 
3020
3270
  // src/gadgets/error-formatter.ts
3021
- var GadgetErrorFormatter;
3271
+ var GadgetExecutionErrorFormatter;
3022
3272
  var init_error_formatter = __esm({
3023
3273
  "src/gadgets/error-formatter.ts"() {
3024
3274
  "use strict";
3025
3275
  init_constants();
3026
- GadgetErrorFormatter = class {
3276
+ GadgetExecutionErrorFormatter = class {
3027
3277
  argPrefix;
3028
3278
  startPrefix;
3029
3279
  endPrefix;
@@ -3109,16 +3359,16 @@ function stripMarkdownFences(content) {
3109
3359
  cleaned = cleaned.replace(closingFence, "");
3110
3360
  return cleaned.trim();
3111
3361
  }
3112
- var globalInvocationCounter, StreamParser;
3362
+ var globalInvocationCounter, GadgetCallParser;
3113
3363
  var init_parser = __esm({
3114
3364
  "src/gadgets/parser.ts"() {
3115
3365
  "use strict";
3116
3366
  init_constants();
3117
3367
  init_block_params();
3118
3368
  globalInvocationCounter = 0;
3119
- StreamParser = class {
3369
+ GadgetCallParser = class {
3120
3370
  buffer = "";
3121
- lastReportedTextLength = 0;
3371
+ lastEmittedTextOffset = 0;
3122
3372
  startPrefix;
3123
3373
  endPrefix;
3124
3374
  argPrefix;
@@ -3127,16 +3377,20 @@ var init_parser = __esm({
3127
3377
  this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
3128
3378
  this.argPrefix = options.argPrefix ?? GADGET_ARG_PREFIX;
3129
3379
  }
3130
- takeTextUntil(index) {
3131
- if (index <= this.lastReportedTextLength) {
3380
+ /**
3381
+ * Extract and consume text up to the given index.
3382
+ * Returns undefined if no meaningful text to emit.
3383
+ */
3384
+ extractTextSegment(index) {
3385
+ if (index <= this.lastEmittedTextOffset) {
3132
3386
  return void 0;
3133
3387
  }
3134
- const segment = this.buffer.slice(this.lastReportedTextLength, index);
3135
- this.lastReportedTextLength = index;
3388
+ const segment = this.buffer.slice(this.lastEmittedTextOffset, index);
3389
+ this.lastEmittedTextOffset = index;
3136
3390
  return segment.trim().length > 0 ? segment : void 0;
3137
3391
  }
3138
3392
  /**
3139
- * Parse gadget name with optional invocation ID and dependencies.
3393
+ * Parse gadget invocation metadata from the header line.
3140
3394
  *
3141
3395
  * Supported formats:
3142
3396
  * - `GadgetName` - Auto-generate ID, no dependencies
@@ -3145,24 +3399,24 @@ var init_parser = __esm({
3145
3399
  *
3146
3400
  * Dependencies must be comma-separated invocation IDs.
3147
3401
  */
3148
- parseGadgetName(gadgetName) {
3149
- const parts = gadgetName.split(":");
3402
+ parseInvocationMetadata(headerLine) {
3403
+ const parts = headerLine.split(":");
3150
3404
  if (parts.length === 1) {
3151
3405
  return {
3152
- actualName: parts[0],
3406
+ gadgetName: parts[0],
3153
3407
  invocationId: `gadget_${++globalInvocationCounter}`,
3154
3408
  dependencies: []
3155
3409
  };
3156
3410
  } else if (parts.length === 2) {
3157
3411
  return {
3158
- actualName: parts[0],
3412
+ gadgetName: parts[0],
3159
3413
  invocationId: parts[1].trim(),
3160
3414
  dependencies: []
3161
3415
  };
3162
3416
  } else {
3163
3417
  const deps = parts[2].split(",").map((d) => d.trim()).filter((d) => d.length > 0);
3164
3418
  return {
3165
- actualName: parts[0],
3419
+ gadgetName: parts[0],
3166
3420
  invocationId: parts[1].trim(),
3167
3421
  dependencies: deps
3168
3422
  };
@@ -3194,15 +3448,15 @@ var init_parser = __esm({
3194
3448
  while (true) {
3195
3449
  const partStartIndex = this.buffer.indexOf(this.startPrefix, startIndex);
3196
3450
  if (partStartIndex === -1) break;
3197
- const textBefore = this.takeTextUntil(partStartIndex);
3451
+ const textBefore = this.extractTextSegment(partStartIndex);
3198
3452
  if (textBefore !== void 0) {
3199
3453
  yield { type: "text", content: textBefore };
3200
3454
  }
3201
3455
  const metadataStartIndex = partStartIndex + this.startPrefix.length;
3202
3456
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
3203
3457
  if (metadataEndIndex === -1) break;
3204
- const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3205
- const { actualName: actualGadgetName, invocationId, dependencies } = this.parseGadgetName(gadgetName);
3458
+ const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3459
+ const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
3206
3460
  const contentStartIndex = metadataEndIndex + 1;
3207
3461
  let partEndIndex;
3208
3462
  let endMarkerLength = 0;
@@ -3222,7 +3476,7 @@ var init_parser = __esm({
3222
3476
  yield {
3223
3477
  type: "gadget_call",
3224
3478
  call: {
3225
- gadgetName: actualGadgetName,
3479
+ gadgetName,
3226
3480
  invocationId,
3227
3481
  parametersRaw,
3228
3482
  parameters,
@@ -3231,33 +3485,33 @@ var init_parser = __esm({
3231
3485
  }
3232
3486
  };
3233
3487
  startIndex = partEndIndex + endMarkerLength;
3234
- this.lastReportedTextLength = startIndex;
3488
+ this.lastEmittedTextOffset = startIndex;
3235
3489
  }
3236
3490
  if (startIndex > 0) {
3237
3491
  this.buffer = this.buffer.substring(startIndex);
3238
- this.lastReportedTextLength = 0;
3492
+ this.lastEmittedTextOffset = 0;
3239
3493
  }
3240
3494
  }
3241
3495
  // Finalize parsing and return remaining text or incomplete gadgets
3242
3496
  *finalize() {
3243
- const startIndex = this.buffer.indexOf(this.startPrefix, this.lastReportedTextLength);
3497
+ const startIndex = this.buffer.indexOf(this.startPrefix, this.lastEmittedTextOffset);
3244
3498
  if (startIndex !== -1) {
3245
- const textBefore = this.takeTextUntil(startIndex);
3499
+ const textBefore = this.extractTextSegment(startIndex);
3246
3500
  if (textBefore !== void 0) {
3247
3501
  yield { type: "text", content: textBefore };
3248
3502
  }
3249
3503
  const metadataStartIndex = startIndex + this.startPrefix.length;
3250
3504
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
3251
3505
  if (metadataEndIndex !== -1) {
3252
- const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3253
- const { actualName: actualGadgetName, invocationId, dependencies } = this.parseGadgetName(gadgetName);
3506
+ const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3507
+ const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
3254
3508
  const contentStartIndex = metadataEndIndex + 1;
3255
3509
  const parametersRaw = this.buffer.substring(contentStartIndex).trim();
3256
3510
  const { parameters, parseError } = this.parseParameters(parametersRaw);
3257
3511
  yield {
3258
3512
  type: "gadget_call",
3259
3513
  call: {
3260
- gadgetName: actualGadgetName,
3514
+ gadgetName,
3261
3515
  invocationId,
3262
3516
  parametersRaw,
3263
3517
  parameters,
@@ -3268,7 +3522,7 @@ var init_parser = __esm({
3268
3522
  return;
3269
3523
  }
3270
3524
  }
3271
- const remainingText = this.takeTextUntil(this.buffer.length);
3525
+ const remainingText = this.extractTextSegment(this.buffer.length);
3272
3526
  if (remainingText !== void 0) {
3273
3527
  yield { type: "text", content: remainingText };
3274
3528
  }
@@ -3276,7 +3530,7 @@ var init_parser = __esm({
3276
3530
  // Reset parser state (note: global invocation counter is NOT reset to ensure unique IDs)
3277
3531
  reset() {
3278
3532
  this.buffer = "";
3279
- this.lastReportedTextLength = 0;
3533
+ this.lastEmittedTextOffset = 0;
3280
3534
  }
3281
3535
  };
3282
3536
  }
@@ -3295,13 +3549,16 @@ var init_executor = __esm({
3295
3549
  init_exceptions();
3296
3550
  init_parser();
3297
3551
  GadgetExecutor = class {
3298
- constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client) {
3552
+ constructor(registry, requestHumanInput, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig) {
3299
3553
  this.registry = registry;
3300
- this.onHumanInputRequired = onHumanInputRequired;
3554
+ this.requestHumanInput = requestHumanInput;
3301
3555
  this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
3302
3556
  this.client = client;
3557
+ this.mediaStore = mediaStore;
3558
+ this.agentConfig = agentConfig;
3559
+ this.subagentConfig = subagentConfig;
3303
3560
  this.logger = logger ?? createLogger({ name: "llmist:executor" });
3304
- this.errorFormatter = new GadgetErrorFormatter(errorFormatterOptions);
3561
+ this.errorFormatter = new GadgetExecutionErrorFormatter(errorFormatterOptions);
3305
3562
  this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
3306
3563
  }
3307
3564
  logger;
@@ -3321,13 +3578,17 @@ var init_executor = __esm({
3321
3578
  });
3322
3579
  }
3323
3580
  /**
3324
- * Normalizes gadget execute result to consistent format.
3325
- * Handles both string returns (backwards compat) and object returns with cost.
3581
+ * Unify gadget execute result to consistent internal format.
3582
+ * Handles string returns (backwards compat), object returns with cost,
3583
+ * and object returns with media.
3326
3584
  */
3327
- normalizeExecuteResult(raw) {
3585
+ unifyExecuteResult(raw) {
3328
3586
  if (typeof raw === "string") {
3329
3587
  return { result: raw, cost: 0 };
3330
3588
  }
3589
+ if ("media" in raw && raw.media) {
3590
+ return { result: raw.result, media: raw.media, cost: raw.cost ?? 0 };
3591
+ }
3331
3592
  return { result: raw.result, cost: raw.cost ?? 0 };
3332
3593
  }
3333
3594
  // Execute a gadget call asynchronously
@@ -3439,7 +3700,9 @@ var init_executor = __esm({
3439
3700
  const ctx = {
3440
3701
  reportCost,
3441
3702
  llmist: this.client ? new CostReportingLLMistWrapper(this.client, reportCost) : void 0,
3442
- signal: abortController.signal
3703
+ signal: abortController.signal,
3704
+ agentConfig: this.agentConfig,
3705
+ subagentConfig: this.subagentConfig
3443
3706
  };
3444
3707
  let rawResult;
3445
3708
  if (timeoutMs && timeoutMs > 0) {
@@ -3454,8 +3717,21 @@ var init_executor = __esm({
3454
3717
  } else {
3455
3718
  rawResult = await Promise.resolve(gadget.execute(validatedParameters, ctx));
3456
3719
  }
3457
- const { result, cost: returnCost } = this.normalizeExecuteResult(rawResult);
3720
+ const { result, media, cost: returnCost } = this.unifyExecuteResult(rawResult);
3458
3721
  const totalCost = callbackCost + returnCost;
3722
+ let mediaIds;
3723
+ let storedMedia;
3724
+ if (media && media.length > 0 && this.mediaStore) {
3725
+ storedMedia = await Promise.all(
3726
+ media.map((item) => this.mediaStore.store(item, call.gadgetName))
3727
+ );
3728
+ mediaIds = storedMedia.map((m) => m.id);
3729
+ this.logger.debug("Stored media outputs", {
3730
+ gadgetName: call.gadgetName,
3731
+ mediaIds,
3732
+ count: media.length
3733
+ });
3734
+ }
3459
3735
  const executionTimeMs = Date.now() - startTime;
3460
3736
  this.logger.info("Gadget executed successfully", {
3461
3737
  gadgetName: call.gadgetName,
@@ -3463,7 +3739,8 @@ var init_executor = __esm({
3463
3739
  executionTimeMs,
3464
3740
  cost: totalCost > 0 ? totalCost : void 0,
3465
3741
  callbackCost: callbackCost > 0 ? callbackCost : void 0,
3466
- returnCost: returnCost > 0 ? returnCost : void 0
3742
+ returnCost: returnCost > 0 ? returnCost : void 0,
3743
+ mediaCount: media?.length
3467
3744
  });
3468
3745
  this.logger.debug("Gadget result", {
3469
3746
  gadgetName: call.gadgetName,
@@ -3471,7 +3748,8 @@ var init_executor = __esm({
3471
3748
  parameters: validatedParameters,
3472
3749
  result,
3473
3750
  cost: totalCost,
3474
- executionTimeMs
3751
+ executionTimeMs,
3752
+ mediaIds
3475
3753
  });
3476
3754
  return {
3477
3755
  gadgetName: call.gadgetName,
@@ -3479,10 +3757,13 @@ var init_executor = __esm({
3479
3757
  parameters: validatedParameters,
3480
3758
  result,
3481
3759
  executionTimeMs,
3482
- cost: totalCost
3760
+ cost: totalCost,
3761
+ media,
3762
+ mediaIds,
3763
+ storedMedia
3483
3764
  };
3484
3765
  } catch (error) {
3485
- if (error instanceof BreakLoopException) {
3766
+ if (error instanceof TaskCompletionSignal) {
3486
3767
  this.logger.info("Gadget requested loop termination", {
3487
3768
  gadgetName: call.gadgetName,
3488
3769
  message: error.message
@@ -3510,7 +3791,7 @@ var init_executor = __esm({
3510
3791
  executionTimeMs: Date.now() - startTime
3511
3792
  };
3512
3793
  }
3513
- if (error instanceof AbortError) {
3794
+ if (error instanceof AbortException) {
3514
3795
  this.logger.info("Gadget execution was aborted", {
3515
3796
  gadgetName: call.gadgetName,
3516
3797
  executionTimeMs: Date.now() - startTime
@@ -3523,14 +3804,14 @@ var init_executor = __esm({
3523
3804
  executionTimeMs: Date.now() - startTime
3524
3805
  };
3525
3806
  }
3526
- if (error instanceof HumanInputException) {
3807
+ if (error instanceof HumanInputRequiredException) {
3527
3808
  this.logger.info("Gadget requested human input", {
3528
3809
  gadgetName: call.gadgetName,
3529
3810
  question: error.question
3530
3811
  });
3531
- if (this.onHumanInputRequired) {
3812
+ if (this.requestHumanInput) {
3532
3813
  try {
3533
- const answer = await this.onHumanInputRequired(error.question);
3814
+ const answer = await this.requestHumanInput(error.question);
3534
3815
  this.logger.debug("Human input received", {
3535
3816
  gadgetName: call.gadgetName,
3536
3817
  answerLength: answer.length
@@ -3628,13 +3909,13 @@ var init_stream_processor = __esm({
3628
3909
  parser;
3629
3910
  executor;
3630
3911
  stopOnGadgetError;
3631
- shouldContinueAfterError;
3632
- accumulatedText = "";
3633
- shouldStopExecution = false;
3912
+ canRecoverFromGadgetError;
3913
+ responseText = "";
3914
+ executionHalted = false;
3634
3915
  observerFailureCount = 0;
3635
3916
  // Dependency tracking for gadget execution DAG
3636
3917
  /** Gadgets waiting for their dependencies to complete */
3637
- pendingGadgets = /* @__PURE__ */ new Map();
3918
+ gadgetsAwaitingDependencies = /* @__PURE__ */ new Map();
3638
3919
  /** Completed gadget results, keyed by invocation ID */
3639
3920
  completedResults = /* @__PURE__ */ new Map();
3640
3921
  /** Invocation IDs of gadgets that have failed (error or skipped due to dependency) */
@@ -3645,19 +3926,22 @@ var init_stream_processor = __esm({
3645
3926
  this.hooks = options.hooks ?? {};
3646
3927
  this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
3647
3928
  this.stopOnGadgetError = options.stopOnGadgetError ?? true;
3648
- this.shouldContinueAfterError = options.shouldContinueAfterError;
3649
- this.parser = new StreamParser({
3929
+ this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
3930
+ this.parser = new GadgetCallParser({
3650
3931
  startPrefix: options.gadgetStartPrefix,
3651
3932
  endPrefix: options.gadgetEndPrefix,
3652
3933
  argPrefix: options.gadgetArgPrefix
3653
3934
  });
3654
3935
  this.executor = new GadgetExecutor(
3655
3936
  options.registry,
3656
- options.onHumanInputRequired,
3937
+ options.requestHumanInput,
3657
3938
  this.logger.getSubLogger({ name: "executor" }),
3658
3939
  options.defaultGadgetTimeoutMs,
3659
3940
  { argPrefix: options.gadgetArgPrefix },
3660
- options.client
3941
+ options.client,
3942
+ options.mediaStore,
3943
+ options.agentConfig,
3944
+ options.subagentConfig
3661
3945
  );
3662
3946
  }
3663
3947
  /**
@@ -3678,7 +3962,7 @@ var init_stream_processor = __esm({
3678
3962
  if (this.hooks.interceptors?.interceptRawChunk) {
3679
3963
  const context = {
3680
3964
  iteration: this.iteration,
3681
- accumulatedText: this.accumulatedText,
3965
+ accumulatedText: this.responseText,
3682
3966
  logger: this.logger
3683
3967
  };
3684
3968
  const intercepted = this.hooks.interceptors.interceptRawChunk(processedChunk, context);
@@ -3689,7 +3973,7 @@ var init_stream_processor = __esm({
3689
3973
  }
3690
3974
  }
3691
3975
  if (processedChunk) {
3692
- this.accumulatedText += processedChunk;
3976
+ this.responseText += processedChunk;
3693
3977
  }
3694
3978
  }
3695
3979
  if (this.hooks.observers?.onStreamChunk && (processedChunk || chunk.usage)) {
@@ -3698,7 +3982,7 @@ var init_stream_processor = __esm({
3698
3982
  const context = {
3699
3983
  iteration: this.iteration,
3700
3984
  rawChunk: processedChunk,
3701
- accumulatedText: this.accumulatedText,
3985
+ accumulatedText: this.responseText,
3702
3986
  usage,
3703
3987
  logger: this.logger
3704
3988
  };
@@ -3721,12 +4005,12 @@ var init_stream_processor = __esm({
3721
4005
  }
3722
4006
  }
3723
4007
  }
3724
- if (this.shouldStopExecution) {
4008
+ if (this.executionHalted) {
3725
4009
  this.logger.info("Breaking from LLM stream due to gadget error");
3726
4010
  break;
3727
4011
  }
3728
4012
  }
3729
- if (!this.shouldStopExecution) {
4013
+ if (!this.executionHalted) {
3730
4014
  for (const event of this.parser.finalize()) {
3731
4015
  const processedEvents = await this.processEvent(event);
3732
4016
  outputs.push(...processedEvents);
@@ -3750,11 +4034,11 @@ var init_stream_processor = __esm({
3750
4034
  }
3751
4035
  }
3752
4036
  }
3753
- let finalMessage = this.accumulatedText;
4037
+ let finalMessage = this.responseText;
3754
4038
  if (this.hooks.interceptors?.interceptAssistantMessage) {
3755
4039
  const context = {
3756
4040
  iteration: this.iteration,
3757
- rawResponse: this.accumulatedText,
4041
+ rawResponse: this.responseText,
3758
4042
  logger: this.logger
3759
4043
  };
3760
4044
  finalMessage = this.hooks.interceptors.interceptAssistantMessage(finalMessage, context);
@@ -3765,7 +4049,7 @@ var init_stream_processor = __esm({
3765
4049
  didExecuteGadgets,
3766
4050
  finishReason,
3767
4051
  usage,
3768
- rawResponse: this.accumulatedText,
4052
+ rawResponse: this.responseText,
3769
4053
  finalMessage
3770
4054
  };
3771
4055
  }
@@ -3788,7 +4072,7 @@ var init_stream_processor = __esm({
3788
4072
  if (this.hooks.interceptors?.interceptTextChunk) {
3789
4073
  const context = {
3790
4074
  iteration: this.iteration,
3791
- accumulatedText: this.accumulatedText,
4075
+ accumulatedText: this.responseText,
3792
4076
  logger: this.logger
3793
4077
  };
3794
4078
  const intercepted = this.hooks.interceptors.interceptTextChunk(content, context);
@@ -3807,7 +4091,7 @@ var init_stream_processor = __esm({
3807
4091
  * After each execution, pending gadgets are checked to see if they can now run.
3808
4092
  */
3809
4093
  async processGadgetCall(call) {
3810
- if (this.shouldStopExecution) {
4094
+ if (this.executionHalted) {
3811
4095
  this.logger.debug("Skipping gadget execution due to previous error", {
3812
4096
  gadgetName: call.gadgetName
3813
4097
  });
@@ -3846,7 +4130,7 @@ var init_stream_processor = __esm({
3846
4130
  invocationId: call.invocationId,
3847
4131
  waitingOn: unsatisfied
3848
4132
  });
3849
- this.pendingGadgets.set(call.invocationId, call);
4133
+ this.gadgetsAwaitingDependencies.set(call.invocationId, call);
3850
4134
  return events;
3851
4135
  }
3852
4136
  }
@@ -3868,14 +4152,14 @@ var init_stream_processor = __esm({
3868
4152
  error: call.parseError,
3869
4153
  rawParameters: call.parametersRaw
3870
4154
  });
3871
- const shouldContinue = await this.checkContinueAfterError(
4155
+ const shouldContinue = await this.checkCanRecoverFromError(
3872
4156
  call.parseError,
3873
4157
  call.gadgetName,
3874
4158
  "parse",
3875
4159
  call.parameters
3876
4160
  );
3877
4161
  if (!shouldContinue) {
3878
- this.shouldStopExecution = true;
4162
+ this.executionHalted = true;
3879
4163
  }
3880
4164
  }
3881
4165
  let parameters = call.parameters ?? {};
@@ -3999,14 +4283,14 @@ var init_stream_processor = __esm({
3999
4283
  events.push({ type: "gadget_result", result });
4000
4284
  if (result.error) {
4001
4285
  const errorType = this.determineErrorType(call, result);
4002
- const shouldContinue = await this.checkContinueAfterError(
4286
+ const shouldContinue = await this.checkCanRecoverFromError(
4003
4287
  result.error,
4004
4288
  result.gadgetName,
4005
4289
  errorType,
4006
4290
  result.parameters
4007
4291
  );
4008
4292
  if (!shouldContinue) {
4009
- this.shouldStopExecution = true;
4293
+ this.executionHalted = true;
4010
4294
  }
4011
4295
  }
4012
4296
  return events;
@@ -4093,11 +4377,11 @@ var init_stream_processor = __esm({
4093
4377
  async processPendingGadgets() {
4094
4378
  const events = [];
4095
4379
  let progress = true;
4096
- while (progress && this.pendingGadgets.size > 0) {
4380
+ while (progress && this.gadgetsAwaitingDependencies.size > 0) {
4097
4381
  progress = false;
4098
4382
  const readyToExecute = [];
4099
4383
  const readyToSkip = [];
4100
- for (const [invocationId, call] of this.pendingGadgets) {
4384
+ for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
4101
4385
  const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
4102
4386
  if (failedDep) {
4103
4387
  readyToSkip.push({ call, failedDep });
@@ -4109,7 +4393,7 @@ var init_stream_processor = __esm({
4109
4393
  }
4110
4394
  }
4111
4395
  for (const { call, failedDep } of readyToSkip) {
4112
- this.pendingGadgets.delete(call.invocationId);
4396
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4113
4397
  const skipEvents = await this.handleFailedDependency(call, failedDep);
4114
4398
  events.push(...skipEvents);
4115
4399
  progress = true;
@@ -4120,7 +4404,7 @@ var init_stream_processor = __esm({
4120
4404
  invocationIds: readyToExecute.map((c) => c.invocationId)
4121
4405
  });
4122
4406
  for (const call of readyToExecute) {
4123
- this.pendingGadgets.delete(call.invocationId);
4407
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4124
4408
  }
4125
4409
  const executePromises = readyToExecute.map((call) => this.executeGadgetWithHooks(call));
4126
4410
  const results = await Promise.all(executePromises);
@@ -4130,9 +4414,9 @@ var init_stream_processor = __esm({
4130
4414
  progress = true;
4131
4415
  }
4132
4416
  }
4133
- if (this.pendingGadgets.size > 0) {
4134
- const pendingIds = new Set(this.pendingGadgets.keys());
4135
- for (const [invocationId, call] of this.pendingGadgets) {
4417
+ if (this.gadgetsAwaitingDependencies.size > 0) {
4418
+ const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
4419
+ for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
4136
4420
  const missingDeps = call.dependencies.filter((dep) => !this.completedResults.has(dep));
4137
4421
  const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
4138
4422
  const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
@@ -4163,7 +4447,7 @@ var init_stream_processor = __esm({
4163
4447
  };
4164
4448
  events.push(skipEvent);
4165
4449
  }
4166
- this.pendingGadgets.clear();
4450
+ this.gadgetsAwaitingDependencies.clear();
4167
4451
  }
4168
4452
  return events;
4169
4453
  }
@@ -4193,19 +4477,19 @@ var init_stream_processor = __esm({
4193
4477
  );
4194
4478
  }
4195
4479
  /**
4196
- * Check if execution should continue after an error.
4480
+ * Check if execution can recover from an error.
4197
4481
  *
4198
4482
  * Returns true if we should continue processing subsequent gadgets, false if we should stop.
4199
4483
  *
4200
4484
  * Logic:
4201
- * - If custom shouldContinueAfterError is provided, use it
4485
+ * - If custom canRecoverFromGadgetError is provided, use it
4202
4486
  * - Otherwise, use stopOnGadgetError config:
4203
4487
  * - stopOnGadgetError=true → return false (stop execution)
4204
4488
  * - stopOnGadgetError=false → return true (continue execution)
4205
4489
  */
4206
- async checkContinueAfterError(error, gadgetName, errorType, parameters) {
4207
- if (this.shouldContinueAfterError) {
4208
- return await this.shouldContinueAfterError({
4490
+ async checkCanRecoverFromError(error, gadgetName, errorType, parameters) {
4491
+ if (this.canRecoverFromGadgetError) {
4492
+ return await this.canRecoverFromGadgetError({
4209
4493
  error,
4210
4494
  gadgetName,
4211
4495
  errorType,
@@ -4246,13 +4530,14 @@ var init_agent = __esm({
4246
4530
  init_constants();
4247
4531
  init_messages();
4248
4532
  init_model_shortcuts();
4533
+ init_media_store();
4249
4534
  init_output_viewer();
4250
4535
  init_logger();
4251
- init_manager();
4252
- init_gadget_output_store();
4253
4536
  init_agent_internal_key();
4537
+ init_manager();
4254
4538
  init_conversation_manager();
4255
4539
  init_event_handlers();
4540
+ init_gadget_output_store();
4256
4541
  init_hook_validators();
4257
4542
  init_stream_processor();
4258
4543
  Agent = class {
@@ -4267,22 +4552,27 @@ var init_agent = __esm({
4267
4552
  gadgetStartPrefix;
4268
4553
  gadgetEndPrefix;
4269
4554
  gadgetArgPrefix;
4270
- onHumanInputRequired;
4555
+ requestHumanInput;
4271
4556
  textOnlyHandler;
4272
4557
  textWithGadgetsHandler;
4273
4558
  stopOnGadgetError;
4274
- shouldContinueAfterError;
4559
+ canRecoverFromGadgetError;
4275
4560
  defaultGadgetTimeoutMs;
4276
4561
  defaultMaxTokens;
4277
- userPromptProvided;
4562
+ hasUserPrompt;
4278
4563
  // Gadget output limiting
4279
4564
  outputStore;
4280
4565
  outputLimitEnabled;
4281
4566
  outputLimitCharLimit;
4282
4567
  // Context compaction
4283
4568
  compactionManager;
4569
+ // Media storage (for gadgets returning images, audio, etc.)
4570
+ mediaStore;
4284
4571
  // Cancellation
4285
4572
  signal;
4573
+ // Subagent configuration
4574
+ agentContextConfig;
4575
+ subagentConfig;
4286
4576
  /**
4287
4577
  * Creates a new Agent instance.
4288
4578
  * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
@@ -4302,15 +4592,16 @@ var init_agent = __esm({
4302
4592
  this.gadgetStartPrefix = options.gadgetStartPrefix;
4303
4593
  this.gadgetEndPrefix = options.gadgetEndPrefix;
4304
4594
  this.gadgetArgPrefix = options.gadgetArgPrefix;
4305
- this.onHumanInputRequired = options.onHumanInputRequired;
4595
+ this.requestHumanInput = options.requestHumanInput;
4306
4596
  this.textOnlyHandler = options.textOnlyHandler ?? "terminate";
4307
4597
  this.textWithGadgetsHandler = options.textWithGadgetsHandler;
4308
4598
  this.stopOnGadgetError = options.stopOnGadgetError ?? true;
4309
- this.shouldContinueAfterError = options.shouldContinueAfterError;
4599
+ this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
4310
4600
  this.defaultGadgetTimeoutMs = options.defaultGadgetTimeoutMs;
4311
4601
  this.defaultMaxTokens = this.resolveMaxTokensFromCatalog(options.model);
4312
4602
  this.outputLimitEnabled = options.gadgetOutputLimit ?? DEFAULT_GADGET_OUTPUT_LIMIT;
4313
4603
  this.outputStore = new GadgetOutputStore();
4604
+ this.mediaStore = new MediaStore();
4314
4605
  const limitPercent = options.gadgetOutputLimitPercent ?? DEFAULT_GADGET_OUTPUT_LIMIT_PERCENT;
4315
4606
  const limits = this.client.modelRegistry.getModelLimits(this.model);
4316
4607
  const contextWindow = limits?.contextWindow ?? FALLBACK_CONTEXT_WINDOW;
@@ -4321,7 +4612,7 @@ var init_agent = __esm({
4321
4612
  createGadgetOutputViewer(this.outputStore, this.outputLimitCharLimit)
4322
4613
  );
4323
4614
  }
4324
- this.hooks = this.mergeOutputLimiterHook(options.hooks);
4615
+ this.hooks = this.chainOutputLimiterWithUserHooks(options.hooks);
4325
4616
  const baseBuilder = new LLMMessageBuilder(options.promptConfig);
4326
4617
  if (options.systemPrompt) {
4327
4618
  baseBuilder.addSystem(options.systemPrompt);
@@ -4341,7 +4632,7 @@ var init_agent = __esm({
4341
4632
  endPrefix: options.gadgetEndPrefix,
4342
4633
  argPrefix: options.gadgetArgPrefix
4343
4634
  });
4344
- this.userPromptProvided = !!options.userPrompt;
4635
+ this.hasUserPrompt = !!options.userPrompt;
4345
4636
  if (options.userPrompt) {
4346
4637
  this.conversation.addUserMessage(options.userPrompt);
4347
4638
  }
@@ -4354,6 +4645,11 @@ var init_agent = __esm({
4354
4645
  );
4355
4646
  }
4356
4647
  this.signal = options.signal;
4648
+ this.agentContextConfig = {
4649
+ model: this.model,
4650
+ temperature: this.temperature
4651
+ };
4652
+ this.subagentConfig = options.subagentConfig;
4357
4653
  }
4358
4654
  /**
4359
4655
  * Get the gadget registry for this agent.
@@ -4376,6 +4672,36 @@ var init_agent = __esm({
4376
4672
  getRegistry() {
4377
4673
  return this.registry;
4378
4674
  }
4675
+ /**
4676
+ * Get the media store for this agent session.
4677
+ *
4678
+ * The media store holds all media outputs (images, audio, etc.) produced by gadgets
4679
+ * during this agent's execution. Use this to:
4680
+ * - Access stored media files by ID
4681
+ * - List all stored media
4682
+ * - Clean up temporary files after execution
4683
+ *
4684
+ * @returns The MediaStore instance for this agent
4685
+ *
4686
+ * @example
4687
+ * ```typescript
4688
+ * const agent = new AgentBuilder()
4689
+ * .withModel("sonnet")
4690
+ * .build();
4691
+ *
4692
+ * // After execution, access stored media
4693
+ * const store = agent.getMediaStore();
4694
+ * for (const media of store.list()) {
4695
+ * console.log(`${media.id}: ${media.path}`);
4696
+ * }
4697
+ *
4698
+ * // Clean up when done
4699
+ * await store.cleanup();
4700
+ * ```
4701
+ */
4702
+ getMediaStore() {
4703
+ return this.mediaStore;
4704
+ }
4379
4705
  /**
4380
4706
  * Manually trigger context compaction.
4381
4707
  *
@@ -4430,7 +4756,7 @@ var init_agent = __esm({
4430
4756
  * @throws {Error} If no user prompt was provided (when using build() without ask())
4431
4757
  */
4432
4758
  async *run() {
4433
- if (!this.userPromptProvided) {
4759
+ if (!this.hasUserPrompt) {
4434
4760
  throw new Error(
4435
4761
  "No user prompt provided. Use .ask(prompt) instead of .build(), or call agent.run() after providing a prompt."
4436
4762
  );
@@ -4547,11 +4873,14 @@ var init_agent = __esm({
4547
4873
  gadgetArgPrefix: this.gadgetArgPrefix,
4548
4874
  hooks: this.hooks,
4549
4875
  logger: this.logger.getSubLogger({ name: "stream-processor" }),
4550
- onHumanInputRequired: this.onHumanInputRequired,
4876
+ requestHumanInput: this.requestHumanInput,
4551
4877
  stopOnGadgetError: this.stopOnGadgetError,
4552
- shouldContinueAfterError: this.shouldContinueAfterError,
4878
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
4553
4879
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
4554
- client: this.client
4880
+ client: this.client,
4881
+ mediaStore: this.mediaStore,
4882
+ agentConfig: this.agentContextConfig,
4883
+ subagentConfig: this.subagentConfig
4555
4884
  });
4556
4885
  const result = await processor.process(stream2);
4557
4886
  for (const output of result.outputs) {
@@ -4604,19 +4933,21 @@ var init_agent = __esm({
4604
4933
  if (msg.role === "user") {
4605
4934
  this.conversation.addUserMessage(msg.content);
4606
4935
  } else if (msg.role === "assistant") {
4607
- this.conversation.addAssistantMessage(extractText(msg.content));
4936
+ this.conversation.addAssistantMessage(extractMessageText(msg.content));
4608
4937
  } else if (msg.role === "system") {
4609
- this.conversation.addUserMessage(`[System] ${extractText(msg.content)}`);
4938
+ this.conversation.addUserMessage(`[System] ${extractMessageText(msg.content)}`);
4610
4939
  }
4611
4940
  }
4612
4941
  }
4613
4942
  }
4614
4943
  if (result.didExecuteGadgets) {
4615
4944
  if (this.textWithGadgetsHandler) {
4616
- const textContent = result.outputs.filter((output) => output.type === "text").map((output) => output.content).join("");
4945
+ const textContent = result.outputs.filter(
4946
+ (output) => output.type === "text"
4947
+ ).map((output) => output.content).join("");
4617
4948
  if (textContent.trim()) {
4618
4949
  const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
4619
- this.conversation.addGadgetCall(
4950
+ this.conversation.addGadgetCallResult(
4620
4951
  gadgetName,
4621
4952
  parameterMapping(textContent),
4622
4953
  resultMapping ? resultMapping(textContent) : textContent
@@ -4626,16 +4957,18 @@ var init_agent = __esm({
4626
4957
  for (const output of result.outputs) {
4627
4958
  if (output.type === "gadget_result") {
4628
4959
  const gadgetResult = output.result;
4629
- this.conversation.addGadgetCall(
4960
+ this.conversation.addGadgetCallResult(
4630
4961
  gadgetResult.gadgetName,
4631
4962
  gadgetResult.parameters,
4632
- gadgetResult.error ?? gadgetResult.result ?? ""
4963
+ gadgetResult.error ?? gadgetResult.result ?? "",
4964
+ gadgetResult.media,
4965
+ gadgetResult.mediaIds
4633
4966
  );
4634
4967
  }
4635
4968
  }
4636
4969
  } else {
4637
4970
  if (finalMessage.trim()) {
4638
- this.conversation.addGadgetCall(
4971
+ this.conversation.addGadgetCallResult(
4639
4972
  "TellUser",
4640
4973
  { message: finalMessage, done: false, type: "info" },
4641
4974
  `\u2139\uFE0F ${finalMessage}`
@@ -4761,10 +5094,10 @@ var init_agent = __esm({
4761
5094
  return this.client.modelRegistry.getModelLimits(unprefixedModelId)?.maxOutputTokens;
4762
5095
  }
4763
5096
  /**
4764
- * Merge the output limiter interceptor into user-provided hooks.
5097
+ * Chain the output limiter interceptor with user-provided hooks.
4765
5098
  * The limiter runs first, then chains to any user interceptor.
4766
5099
  */
4767
- mergeOutputLimiterHook(userHooks) {
5100
+ chainOutputLimiterWithUserHooks(userHooks) {
4768
5101
  if (!this.outputLimitEnabled) {
4769
5102
  return userHooks ?? {};
4770
5103
  }
@@ -4843,20 +5176,21 @@ var init_builder = __esm({
4843
5176
  promptConfig;
4844
5177
  gadgets = [];
4845
5178
  initialMessages = [];
4846
- onHumanInputRequired;
5179
+ requestHumanInput;
4847
5180
  gadgetStartPrefix;
4848
5181
  gadgetEndPrefix;
4849
5182
  gadgetArgPrefix;
4850
5183
  textOnlyHandler;
4851
5184
  textWithGadgetsHandler;
4852
5185
  stopOnGadgetError;
4853
- shouldContinueAfterError;
5186
+ canRecoverFromGadgetError;
4854
5187
  defaultGadgetTimeoutMs;
4855
5188
  gadgetOutputLimit;
4856
5189
  gadgetOutputLimitPercent;
4857
5190
  compactionConfig;
4858
5191
  signal;
4859
5192
  trailingMessage;
5193
+ subagentConfig;
4860
5194
  constructor(client) {
4861
5195
  this.client = client;
4862
5196
  }
@@ -4947,13 +5281,13 @@ var init_builder = __esm({
4947
5281
  *
4948
5282
  * @example
4949
5283
  * ```typescript
4950
- * .withPromptConfig({
5284
+ * .withPromptTemplateConfig({
4951
5285
  * mainInstruction: "Use the gadget markers below:",
4952
5286
  * rules: ["Always use markers", "Never use function calling"]
4953
5287
  * })
4954
5288
  * ```
4955
5289
  */
4956
- withPromptConfig(config) {
5290
+ withPromptTemplateConfig(config) {
4957
5291
  this.promptConfig = config;
4958
5292
  return this;
4959
5293
  }
@@ -5033,7 +5367,7 @@ var init_builder = __esm({
5033
5367
  * ```
5034
5368
  */
5035
5369
  onHumanInput(handler) {
5036
- this.onHumanInputRequired = handler;
5370
+ this.requestHumanInput = handler;
5037
5371
  return this;
5038
5372
  }
5039
5373
  /**
@@ -5168,9 +5502,9 @@ var init_builder = __esm({
5168
5502
  * Provides fine-grained control over whether to continue after different types of errors.
5169
5503
  * Overrides `stopOnGadgetError` when provided.
5170
5504
  *
5171
- * **Note:** This builder method configures the underlying `shouldContinueAfterError` option
5505
+ * **Note:** This builder method configures the underlying `canRecoverFromGadgetError` option
5172
5506
  * in `AgentOptions`. The method is named `withErrorHandler` for better developer experience,
5173
- * but maps to the `shouldContinueAfterError` property internally.
5507
+ * but maps to the `canRecoverFromGadgetError` property internally.
5174
5508
  *
5175
5509
  * @param handler - Function that decides whether to continue after an error.
5176
5510
  * Return `true` to continue execution, `false` to stop.
@@ -5191,7 +5525,7 @@ var init_builder = __esm({
5191
5525
  * ```
5192
5526
  */
5193
5527
  withErrorHandler(handler) {
5194
- this.shouldContinueAfterError = handler;
5528
+ this.canRecoverFromGadgetError = handler;
5195
5529
  return this;
5196
5530
  }
5197
5531
  /**
@@ -5332,6 +5666,27 @@ var init_builder = __esm({
5332
5666
  this.signal = signal;
5333
5667
  return this;
5334
5668
  }
5669
+ /**
5670
+ * Set subagent configuration overrides.
5671
+ *
5672
+ * Subagent gadgets (like BrowseWeb) can read these settings from ExecutionContext
5673
+ * to inherit model and other options from the CLI configuration.
5674
+ *
5675
+ * @param config - Subagent configuration map keyed by gadget name
5676
+ * @returns This builder for chaining
5677
+ *
5678
+ * @example
5679
+ * ```typescript
5680
+ * .withSubagentConfig({
5681
+ * BrowseWeb: { model: "inherit", maxIterations: 20, headless: true },
5682
+ * CodeAnalyzer: { model: "sonnet", maxIterations: 10 }
5683
+ * })
5684
+ * ```
5685
+ */
5686
+ withSubagentConfig(config) {
5687
+ this.subagentConfig = config;
5688
+ return this;
5689
+ }
5335
5690
  /**
5336
5691
  * Add an ephemeral trailing message that appears at the end of each LLM request.
5337
5692
  *
@@ -5496,19 +5851,20 @@ ${endPrefix}`
5496
5851
  hooks: this.composeHooks(),
5497
5852
  promptConfig: this.promptConfig,
5498
5853
  initialMessages: this.initialMessages,
5499
- onHumanInputRequired: this.onHumanInputRequired,
5854
+ requestHumanInput: this.requestHumanInput,
5500
5855
  gadgetStartPrefix: this.gadgetStartPrefix,
5501
5856
  gadgetEndPrefix: this.gadgetEndPrefix,
5502
5857
  gadgetArgPrefix: this.gadgetArgPrefix,
5503
5858
  textOnlyHandler: this.textOnlyHandler,
5504
5859
  textWithGadgetsHandler: this.textWithGadgetsHandler,
5505
5860
  stopOnGadgetError: this.stopOnGadgetError,
5506
- shouldContinueAfterError: this.shouldContinueAfterError,
5861
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
5507
5862
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
5508
5863
  gadgetOutputLimit: this.gadgetOutputLimit,
5509
5864
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
5510
5865
  compactionConfig: this.compactionConfig,
5511
- signal: this.signal
5866
+ signal: this.signal,
5867
+ subagentConfig: this.subagentConfig
5512
5868
  };
5513
5869
  }
5514
5870
  ask(userPrompt) {
@@ -5677,19 +6033,20 @@ ${endPrefix}`
5677
6033
  hooks: this.composeHooks(),
5678
6034
  promptConfig: this.promptConfig,
5679
6035
  initialMessages: this.initialMessages,
5680
- onHumanInputRequired: this.onHumanInputRequired,
6036
+ requestHumanInput: this.requestHumanInput,
5681
6037
  gadgetStartPrefix: this.gadgetStartPrefix,
5682
6038
  gadgetEndPrefix: this.gadgetEndPrefix,
5683
6039
  gadgetArgPrefix: this.gadgetArgPrefix,
5684
6040
  textOnlyHandler: this.textOnlyHandler,
5685
6041
  textWithGadgetsHandler: this.textWithGadgetsHandler,
5686
6042
  stopOnGadgetError: this.stopOnGadgetError,
5687
- shouldContinueAfterError: this.shouldContinueAfterError,
6043
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
5688
6044
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
5689
6045
  gadgetOutputLimit: this.gadgetOutputLimit,
5690
6046
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
5691
6047
  compactionConfig: this.compactionConfig,
5692
- signal: this.signal
6048
+ signal: this.signal,
6049
+ subagentConfig: this.subagentConfig
5693
6050
  };
5694
6051
  return new Agent(AGENT_INTERNAL_KEY, options);
5695
6052
  }
@@ -5995,9 +6352,9 @@ var init_base_provider = __esm({
5995
6352
  */
5996
6353
  async *stream(options, descriptor, spec) {
5997
6354
  const preparedMessages = this.prepareMessages(options.messages);
5998
- const payload = this.buildRequestPayload(options, descriptor, spec, preparedMessages);
6355
+ const payload = this.buildApiRequest(options, descriptor, spec, preparedMessages);
5999
6356
  const rawStream = await this.executeStreamRequest(payload, options.signal);
6000
- yield* this.wrapStream(rawStream);
6357
+ yield* this.normalizeProviderStream(rawStream);
6001
6358
  }
6002
6359
  /**
6003
6360
  * Prepare messages for the request.
@@ -6097,11 +6454,11 @@ var init_anthropic = __esm({
6097
6454
  "Anthropic does not support speech generation. Use OpenAI (TTS) or Google Gemini (TTS) instead."
6098
6455
  );
6099
6456
  }
6100
- buildRequestPayload(options, descriptor, spec, messages) {
6457
+ buildApiRequest(options, descriptor, spec, messages) {
6101
6458
  const systemMessages = messages.filter((message) => message.role === "system");
6102
6459
  const system = systemMessages.length > 0 ? systemMessages.map((m, index) => ({
6103
6460
  type: "text",
6104
- text: extractText(m.content),
6461
+ text: extractMessageText(m.content),
6105
6462
  // Add cache_control to the LAST system message block
6106
6463
  ...index === systemMessages.length - 1 ? { cache_control: { type: "ephemeral" } } : {}
6107
6464
  })) : void 0;
@@ -6138,7 +6495,7 @@ var init_anthropic = __esm({
6138
6495
  * Handles text, images (base64 only), and applies cache_control.
6139
6496
  */
6140
6497
  convertToAnthropicContent(content, addCacheControl) {
6141
- const parts = normalizeContent(content);
6498
+ const parts = normalizeMessageContent(content);
6142
6499
  return parts.map((part, index) => {
6143
6500
  const isLastPart = index === parts.length - 1;
6144
6501
  const cacheControl = addCacheControl && isLastPart ? { cache_control: { type: "ephemeral" } } : {};
@@ -6184,7 +6541,7 @@ var init_anthropic = __esm({
6184
6541
  const stream2 = await client.messages.create(payload, signal ? { signal } : void 0);
6185
6542
  return stream2;
6186
6543
  }
6187
- async *wrapStream(iterable) {
6544
+ async *normalizeProviderStream(iterable) {
6188
6545
  const stream2 = iterable;
6189
6546
  let inputTokens = 0;
6190
6547
  let cachedInputTokens = 0;
@@ -6261,7 +6618,7 @@ var init_anthropic = __esm({
6261
6618
  async countTokens(messages, descriptor, _spec) {
6262
6619
  const client = this.client;
6263
6620
  const systemMessages = messages.filter((message) => message.role === "system");
6264
- const system = systemMessages.length > 0 ? systemMessages.map((m) => extractText(m.content)).join("\n\n") : void 0;
6621
+ const system = systemMessages.length > 0 ? systemMessages.map((m) => extractMessageText(m.content)).join("\n\n") : void 0;
6265
6622
  const conversation = messages.filter(
6266
6623
  (message) => message.role !== "system"
6267
6624
  ).map((message) => ({
@@ -6283,7 +6640,7 @@ var init_anthropic = __esm({
6283
6640
  let totalChars = 0;
6284
6641
  let imageCount = 0;
6285
6642
  for (const msg of messages) {
6286
- const parts = normalizeContent(msg.content);
6643
+ const parts = normalizeMessageContent(msg.content);
6287
6644
  for (const part of parts) {
6288
6645
  if (part.type === "text") {
6289
6646
  totalChars += part.text.length;
@@ -6974,7 +7331,7 @@ var init_gemini = __esm({
6974
7331
  format: spec?.defaultFormat ?? "wav"
6975
7332
  };
6976
7333
  }
6977
- buildRequestPayload(options, descriptor, _spec, messages) {
7334
+ buildApiRequest(options, descriptor, _spec, messages) {
6978
7335
  const contents = this.convertMessagesToContents(messages);
6979
7336
  const generationConfig = this.buildGenerationConfig(options);
6980
7337
  const config = {
@@ -7026,7 +7383,7 @@ var init_gemini = __esm({
7026
7383
  if (message.role === "system") {
7027
7384
  expandedMessages.push({
7028
7385
  role: "user",
7029
- content: extractText(message.content)
7386
+ content: extractMessageText(message.content)
7030
7387
  });
7031
7388
  expandedMessages.push({
7032
7389
  role: "assistant",
@@ -7076,7 +7433,7 @@ var init_gemini = __esm({
7076
7433
  * Handles text, images, and audio (Gemini supports all three).
7077
7434
  */
7078
7435
  convertToGeminiParts(content) {
7079
- const parts = normalizeContent(content);
7436
+ const parts = normalizeMessageContent(content);
7080
7437
  return parts.map((part) => {
7081
7438
  if (part.type === "text") {
7082
7439
  return { text: part.text };
@@ -7121,10 +7478,10 @@ var init_gemini = __esm({
7121
7478
  }
7122
7479
  return Object.keys(config).length > 0 ? config : null;
7123
7480
  }
7124
- async *wrapStream(iterable) {
7481
+ async *normalizeProviderStream(iterable) {
7125
7482
  const stream2 = iterable;
7126
7483
  for await (const chunk of stream2) {
7127
- const text3 = this.extractText(chunk);
7484
+ const text3 = this.extractMessageText(chunk);
7128
7485
  if (text3) {
7129
7486
  yield { text: text3, rawEvent: chunk };
7130
7487
  }
@@ -7135,7 +7492,7 @@ var init_gemini = __esm({
7135
7492
  }
7136
7493
  }
7137
7494
  }
7138
- extractText(chunk) {
7495
+ extractMessageText(chunk) {
7139
7496
  if (!chunk?.candidates) {
7140
7497
  return "";
7141
7498
  }
@@ -7200,7 +7557,7 @@ var init_gemini = __esm({
7200
7557
  let totalChars = 0;
7201
7558
  let mediaCount = 0;
7202
7559
  for (const msg of messages) {
7203
- const parts = normalizeContent(msg.content);
7560
+ const parts = normalizeMessageContent(msg.content);
7204
7561
  for (const part of parts) {
7205
7562
  if (part.type === "text") {
7206
7563
  totalChars += part.text.length;
@@ -7721,14 +8078,7 @@ var OPENAI_TTS_VOICES, OPENAI_TTS_EXTENDED_VOICES, OPENAI_TTS_FORMATS, openaiSpe
7721
8078
  var init_openai_speech_models = __esm({
7722
8079
  "src/providers/openai-speech-models.ts"() {
7723
8080
  "use strict";
7724
- OPENAI_TTS_VOICES = [
7725
- "alloy",
7726
- "echo",
7727
- "fable",
7728
- "onyx",
7729
- "nova",
7730
- "shimmer"
7731
- ];
8081
+ OPENAI_TTS_VOICES = ["alloy", "echo", "fable", "onyx", "nova", "shimmer"];
7732
8082
  OPENAI_TTS_EXTENDED_VOICES = [
7733
8083
  ...OPENAI_TTS_VOICES,
7734
8084
  "ash",
@@ -7953,7 +8303,7 @@ var init_openai = __esm({
7953
8303
  format
7954
8304
  };
7955
8305
  }
7956
- buildRequestPayload(options, descriptor, spec, messages) {
8306
+ buildApiRequest(options, descriptor, spec, messages) {
7957
8307
  const { maxTokens, temperature, topP, stopSequences, extra } = options;
7958
8308
  const supportsTemperature = spec?.metadata?.supportsTemperature !== false;
7959
8309
  const shouldIncludeTemperature = typeof temperature === "number" && supportsTemperature;
@@ -7988,7 +8338,7 @@ var init_openai = __esm({
7988
8338
  ...message.name ? { name: message.name } : {}
7989
8339
  };
7990
8340
  }
7991
- const textContent = typeof message.content === "string" ? message.content : extractText(message.content);
8341
+ const textContent = typeof message.content === "string" ? message.content : extractMessageText(message.content);
7992
8342
  if (role === "system") {
7993
8343
  return {
7994
8344
  role: "system",
@@ -8048,7 +8398,7 @@ var init_openai = __esm({
8048
8398
  const stream2 = await client.chat.completions.create(payload, signal ? { signal } : void 0);
8049
8399
  return stream2;
8050
8400
  }
8051
- async *wrapStream(iterable) {
8401
+ async *normalizeProviderStream(iterable) {
8052
8402
  const stream2 = iterable;
8053
8403
  for await (const chunk of stream2) {
8054
8404
  const text3 = chunk.choices.map((choice) => choice.delta?.content ?? "").join("");
@@ -8106,9 +8456,9 @@ var init_openai = __esm({
8106
8456
  tokenCount += OPENAI_MESSAGE_OVERHEAD_TOKENS;
8107
8457
  const roleText = ROLE_MAP[message.role];
8108
8458
  tokenCount += encoding.encode(roleText).length;
8109
- const textContent = extractText(message.content);
8459
+ const textContent = extractMessageText(message.content);
8110
8460
  tokenCount += encoding.encode(textContent).length;
8111
- const parts = normalizeContent(message.content);
8461
+ const parts = normalizeMessageContent(message.content);
8112
8462
  for (const part of parts) {
8113
8463
  if (part.type === "image") {
8114
8464
  imageCount++;
@@ -8133,7 +8483,7 @@ var init_openai = __esm({
8133
8483
  let totalChars = 0;
8134
8484
  let imageCount = 0;
8135
8485
  for (const msg of messages) {
8136
- const parts = normalizeContent(msg.content);
8486
+ const parts = normalizeMessageContent(msg.content);
8137
8487
  for (const part of parts) {
8138
8488
  if (part.type === "text") {
8139
8489
  totalChars += part.text.length;
@@ -8426,9 +8776,7 @@ var init_image = __esm({
8426
8776
  return this.findImageAdapter(modelId) !== void 0;
8427
8777
  }
8428
8778
  findImageAdapter(modelId) {
8429
- return this.adapters.find(
8430
- (adapter) => adapter.supportsImageGeneration?.(modelId) ?? false
8431
- );
8779
+ return this.adapters.find((adapter) => adapter.supportsImageGeneration?.(modelId) ?? false);
8432
8780
  }
8433
8781
  };
8434
8782
  }
@@ -8480,9 +8828,7 @@ var init_speech = __esm({
8480
8828
  return this.findSpeechAdapter(modelId) !== void 0;
8481
8829
  }
8482
8830
  findSpeechAdapter(modelId) {
8483
- return this.adapters.find(
8484
- (adapter) => adapter.supportsSpeechGeneration?.(modelId) ?? false
8485
- );
8831
+ return this.adapters.find((adapter) => adapter.supportsSpeechGeneration?.(modelId) ?? false);
8486
8832
  }
8487
8833
  };
8488
8834
  }
@@ -8593,11 +8939,7 @@ var init_vision = __esm({
8593
8939
  if (!parsed) {
8594
8940
  throw new Error("Invalid data URL format");
8595
8941
  }
8596
- builder.addUserWithImage(
8597
- options.prompt,
8598
- parsed.data,
8599
- parsed.mimeType
8600
- );
8942
+ builder.addUserWithImage(options.prompt, parsed.data, parsed.mimeType);
8601
8943
  } else {
8602
8944
  const buffer = Buffer.from(options.image, "base64");
8603
8945
  builder.addUserWithImage(options.prompt, buffer, options.mimeType);
@@ -8983,37 +9325,262 @@ var init_client = __esm({
8983
9325
  }
8984
9326
  });
8985
9327
 
8986
- // src/gadgets/validation.ts
8987
- function validateAndApplyDefaults(schema, params) {
8988
- const result = schema.safeParse(params);
8989
- if (result.success) {
8990
- return {
8991
- success: true,
8992
- data: result.data
8993
- };
8994
- }
8995
- const issues = result.error.issues.map((issue) => ({
8996
- path: issue.path.join(".") || "root",
8997
- message: issue.message
8998
- }));
8999
- const formattedError = `Invalid parameters: ${issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`;
9328
+ // src/testing/cli-helpers.ts
9329
+ import { PassThrough, Readable, Writable } from "node:stream";
9330
+ function createTestEnvironment(options = {}) {
9331
+ const stdin = createMockReadable(options.stdin);
9332
+ const stdout = new PassThrough();
9333
+ const stderr = new PassThrough();
9334
+ let exitCode;
9000
9335
  return {
9001
- success: false,
9002
- error: formattedError,
9003
- issues
9336
+ stdin,
9337
+ stdout,
9338
+ stderr,
9339
+ isTTY: options.isTTY ?? false,
9340
+ argv: options.argv ?? ["node", "llmist"],
9341
+ env: { ...filterDefinedEnv(process.env), ...options.env },
9342
+ get exitCode() {
9343
+ return exitCode;
9344
+ },
9345
+ setExitCode: (code) => {
9346
+ exitCode = code;
9347
+ }
9004
9348
  };
9005
9349
  }
9006
- function validateGadgetParams(gadget, params) {
9007
- if (!gadget.parameterSchema) {
9008
- return {
9009
- success: true,
9010
- data: params
9011
- };
9350
+ function createMockReadable(input) {
9351
+ if (!input) {
9352
+ const stream3 = new Readable({ read() {
9353
+ } });
9354
+ stream3.push(null);
9355
+ return stream3;
9012
9356
  }
9013
- return validateAndApplyDefaults(gadget.parameterSchema, params);
9357
+ const content = Array.isArray(input) ? `${input.join("\n")}
9358
+ ` : input;
9359
+ const stream2 = new Readable({ read() {
9360
+ } });
9361
+ stream2.push(content);
9362
+ stream2.push(null);
9363
+ return stream2;
9014
9364
  }
9015
-
9016
- // src/testing/gadget-testing.ts
9365
+ function createMockWritable() {
9366
+ const chunks = [];
9367
+ const stream2 = new Writable({
9368
+ write(chunk, _encoding, callback) {
9369
+ chunks.push(Buffer.from(chunk));
9370
+ callback();
9371
+ }
9372
+ });
9373
+ stream2.getData = () => Buffer.concat(chunks).toString("utf8");
9374
+ return stream2;
9375
+ }
9376
+ async function collectOutput(stream2, timeout = 5e3) {
9377
+ return new Promise((resolve, reject) => {
9378
+ const chunks = [];
9379
+ const timeoutId = setTimeout(() => {
9380
+ resolve(Buffer.concat(chunks).toString("utf8"));
9381
+ }, timeout);
9382
+ stream2.on("data", (chunk) => {
9383
+ chunks.push(Buffer.from(chunk));
9384
+ });
9385
+ stream2.on("end", () => {
9386
+ clearTimeout(timeoutId);
9387
+ resolve(Buffer.concat(chunks).toString("utf8"));
9388
+ });
9389
+ stream2.on("error", (err) => {
9390
+ clearTimeout(timeoutId);
9391
+ reject(err);
9392
+ });
9393
+ });
9394
+ }
9395
+ function getBufferedOutput(stream2) {
9396
+ const chunks = [];
9397
+ for (; ; ) {
9398
+ const chunk = stream2.read();
9399
+ if (chunk === null) break;
9400
+ chunks.push(chunk);
9401
+ }
9402
+ return Buffer.concat(chunks).toString("utf8");
9403
+ }
9404
+ function createMockPrompt(responses) {
9405
+ let index = 0;
9406
+ return async (_question) => {
9407
+ if (index >= responses.length) {
9408
+ throw new Error(`Mock prompt exhausted: no response for question ${index + 1}`);
9409
+ }
9410
+ return responses[index++];
9411
+ };
9412
+ }
9413
+ var MockPromptRecorder = class {
9414
+ responses;
9415
+ index = 0;
9416
+ questions = [];
9417
+ constructor(responses) {
9418
+ this.responses = responses;
9419
+ }
9420
+ /**
9421
+ * The prompt function to use in tests.
9422
+ */
9423
+ prompt = async (question) => {
9424
+ this.questions.push(question);
9425
+ if (this.index >= this.responses.length) {
9426
+ throw new Error(`Mock prompt exhausted after ${this.index} questions`);
9427
+ }
9428
+ return this.responses[this.index++];
9429
+ };
9430
+ /**
9431
+ * Get all questions that were asked.
9432
+ */
9433
+ getQuestions() {
9434
+ return [...this.questions];
9435
+ }
9436
+ /**
9437
+ * Get the number of questions asked.
9438
+ */
9439
+ getQuestionCount() {
9440
+ return this.questions.length;
9441
+ }
9442
+ /**
9443
+ * Reset the recorder state.
9444
+ */
9445
+ reset(newResponses) {
9446
+ this.index = 0;
9447
+ this.questions = [];
9448
+ if (newResponses) {
9449
+ this.responses = newResponses;
9450
+ }
9451
+ }
9452
+ };
9453
+ async function waitFor(condition, timeout = 5e3, interval = 50) {
9454
+ const startTime = Date.now();
9455
+ while (!condition()) {
9456
+ if (Date.now() - startTime > timeout) {
9457
+ throw new Error(`waitFor timed out after ${timeout}ms`);
9458
+ }
9459
+ await sleep(interval);
9460
+ }
9461
+ }
9462
+ function sleep(ms) {
9463
+ return new Promise((resolve) => setTimeout(resolve, ms));
9464
+ }
9465
+ function filterDefinedEnv(env) {
9466
+ const result = {};
9467
+ for (const [key, value] of Object.entries(env)) {
9468
+ if (value !== void 0) {
9469
+ result[key] = value;
9470
+ }
9471
+ }
9472
+ return result;
9473
+ }
9474
+
9475
+ // src/testing/conversation-fixtures.ts
9476
+ function createConversation(turnCount, options) {
9477
+ const messages = [];
9478
+ const userPrefix = options?.userPrefix ?? "User message";
9479
+ const assistantPrefix = options?.assistantPrefix ?? "Assistant response";
9480
+ const contentLength = options?.contentLength ?? 100;
9481
+ for (let i = 0; i < turnCount; i++) {
9482
+ const padding = " ".repeat(Math.max(0, contentLength - 30));
9483
+ messages.push({
9484
+ role: "user",
9485
+ content: `${userPrefix} ${i + 1}: This is turn ${i + 1} of the conversation.${padding}`
9486
+ });
9487
+ messages.push({
9488
+ role: "assistant",
9489
+ content: `${assistantPrefix} ${i + 1}: I acknowledge turn ${i + 1}.${padding}`
9490
+ });
9491
+ }
9492
+ return messages;
9493
+ }
9494
+ function createConversationWithGadgets(turnCount, gadgetCallsPerTurn = 1, options) {
9495
+ const messages = [];
9496
+ const gadgetNames = options?.gadgetNames ?? ["search", "calculate", "read"];
9497
+ const contentLength = options?.contentLength ?? 50;
9498
+ let gadgetIndex = 0;
9499
+ for (let turn = 0; turn < turnCount; turn++) {
9500
+ messages.push({
9501
+ role: "user",
9502
+ content: `User request ${turn + 1}${"x".repeat(contentLength)}`
9503
+ });
9504
+ for (let g = 0; g < gadgetCallsPerTurn; g++) {
9505
+ const gadgetName = gadgetNames[gadgetIndex % gadgetNames.length];
9506
+ gadgetIndex++;
9507
+ messages.push({
9508
+ role: "assistant",
9509
+ content: `!!!GADGET_START:${gadgetName}
9510
+ !!!ARG:query
9511
+ test query ${turn}-${g}
9512
+ !!!GADGET_END`
9513
+ });
9514
+ messages.push({
9515
+ role: "user",
9516
+ content: `Result: Gadget ${gadgetName} returned result for query ${turn}-${g}`
9517
+ });
9518
+ }
9519
+ messages.push({
9520
+ role: "assistant",
9521
+ content: `Final response for turn ${turn + 1}${"y".repeat(contentLength)}`
9522
+ });
9523
+ }
9524
+ return messages;
9525
+ }
9526
+ function estimateTokens(messages) {
9527
+ return Math.ceil(messages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0) / 4);
9528
+ }
9529
+ function createUserMessage(content) {
9530
+ return { role: "user", content };
9531
+ }
9532
+ function createAssistantMessage(content) {
9533
+ return { role: "assistant", content };
9534
+ }
9535
+ function createSystemMessage(content) {
9536
+ return { role: "system", content };
9537
+ }
9538
+ function createMinimalConversation() {
9539
+ return [
9540
+ { role: "user", content: "Hello" },
9541
+ { role: "assistant", content: "Hi there!" }
9542
+ ];
9543
+ }
9544
+ function createLargeConversation(targetTokens, options) {
9545
+ const tokensPerTurn = options?.tokensPerTurn ?? 200;
9546
+ const turnsNeeded = Math.ceil(targetTokens / tokensPerTurn);
9547
+ const charsPerMessage = Math.floor(tokensPerTurn * 4 / 2);
9548
+ return createConversation(turnsNeeded, {
9549
+ contentLength: charsPerMessage
9550
+ });
9551
+ }
9552
+
9553
+ // src/gadgets/validation.ts
9554
+ function validateAndApplyDefaults(schema, params) {
9555
+ const result = schema.safeParse(params);
9556
+ if (result.success) {
9557
+ return {
9558
+ success: true,
9559
+ data: result.data
9560
+ };
9561
+ }
9562
+ const issues = result.error.issues.map((issue) => ({
9563
+ path: issue.path.join(".") || "root",
9564
+ message: issue.message
9565
+ }));
9566
+ const formattedError = `Invalid parameters: ${issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`;
9567
+ return {
9568
+ success: false,
9569
+ error: formattedError,
9570
+ issues
9571
+ };
9572
+ }
9573
+ function validateGadgetParams(gadget, params) {
9574
+ if (!gadget.parameterSchema) {
9575
+ return {
9576
+ success: true,
9577
+ data: params
9578
+ };
9579
+ }
9580
+ return validateAndApplyDefaults(gadget.parameterSchema, params);
9581
+ }
9582
+
9583
+ // src/testing/gadget-testing.ts
9017
9584
  async function testGadget(gadget, params, options) {
9018
9585
  let validatedParams = params;
9019
9586
  if (!options?.skipValidation) {
@@ -9220,7 +9787,7 @@ function getMockManager(options) {
9220
9787
 
9221
9788
  // src/testing/mock-stream.ts
9222
9789
  init_constants();
9223
- function sleep(ms) {
9790
+ function sleep2(ms) {
9224
9791
  return new Promise((resolve) => setTimeout(resolve, ms));
9225
9792
  }
9226
9793
  function generateInvocationId() {
@@ -9301,7 +9868,7 @@ ${blockParams}${GADGET_END_PREFIX}`;
9301
9868
  }
9302
9869
  async function* createMockStream(response) {
9303
9870
  if (response.delayMs) {
9304
- await sleep(response.delayMs);
9871
+ await sleep2(response.delayMs);
9305
9872
  }
9306
9873
  const streamDelay = response.streamDelayMs ?? 0;
9307
9874
  let fullText = response.text ?? "";
@@ -9326,7 +9893,7 @@ async function* createMockStream(response) {
9326
9893
  }
9327
9894
  yield chunk;
9328
9895
  if (streamDelay > 0 && !isLast) {
9329
- await sleep(streamDelay);
9896
+ await sleep2(streamDelay);
9330
9897
  }
9331
9898
  }
9332
9899
  } else {
@@ -9603,7 +10170,9 @@ var MockBuilder = class {
9603
10170
  */
9604
10171
  whenMessageContains(text3) {
9605
10172
  this.matchers.push(
9606
- (ctx) => ctx.messages.some((msg) => extractText(msg.content).toLowerCase().includes(text3.toLowerCase()))
10173
+ (ctx) => ctx.messages.some(
10174
+ (msg) => extractMessageText(msg.content).toLowerCase().includes(text3.toLowerCase())
10175
+ )
9607
10176
  );
9608
10177
  return this;
9609
10178
  }
@@ -9617,7 +10186,7 @@ var MockBuilder = class {
9617
10186
  this.matchers.push((ctx) => {
9618
10187
  const lastMsg = ctx.messages[ctx.messages.length - 1];
9619
10188
  if (!lastMsg) return false;
9620
- return extractText(lastMsg.content).toLowerCase().includes(text3.toLowerCase());
10189
+ return extractMessageText(lastMsg.content).toLowerCase().includes(text3.toLowerCase());
9621
10190
  });
9622
10191
  return this;
9623
10192
  }
@@ -9628,7 +10197,7 @@ var MockBuilder = class {
9628
10197
  * mockLLM().whenMessageMatches(/calculate \d+/)
9629
10198
  */
9630
10199
  whenMessageMatches(regex) {
9631
- this.matchers.push((ctx) => ctx.messages.some((msg) => regex.test(extractText(msg.content))));
10200
+ this.matchers.push((ctx) => ctx.messages.some((msg) => regex.test(extractMessageText(msg.content))));
9632
10201
  return this;
9633
10202
  }
9634
10203
  /**
@@ -9640,7 +10209,7 @@ var MockBuilder = class {
9640
10209
  whenRoleContains(role, text3) {
9641
10210
  this.matchers.push(
9642
10211
  (ctx) => ctx.messages.some(
9643
- (msg) => msg.role === role && extractText(msg.content).toLowerCase().includes(text3.toLowerCase())
10212
+ (msg) => msg.role === role && extractMessageText(msg.content).toLowerCase().includes(text3.toLowerCase())
9644
10213
  )
9645
10214
  );
9646
10215
  return this;
@@ -10041,41 +10610,170 @@ function createMockClient(options) {
10041
10610
  });
10042
10611
  }
10043
10612
 
10044
- // src/testing/mock-gadget.ts
10045
- init_gadget();
10046
- var MockGadgetImpl = class extends BaseGadget {
10047
- name;
10048
- description;
10049
- parameterSchema;
10050
- timeoutMs;
10051
- calls = [];
10052
- resultValue;
10053
- resultFn;
10054
- errorToThrow;
10055
- delayMs;
10056
- shouldTrackCalls;
10057
- constructor(config) {
10058
- super();
10059
- this.name = config.name;
10060
- this.description = config.description ?? `Mock gadget: ${config.name}`;
10061
- this.parameterSchema = config.schema;
10062
- this.resultValue = config.result;
10063
- this.resultFn = config.resultFn;
10064
- this.delayMs = config.delayMs ?? 0;
10065
- this.shouldTrackCalls = config.trackCalls ?? true;
10066
- this.timeoutMs = config.timeoutMs;
10067
- if (config.error) {
10068
- this.errorToThrow = typeof config.error === "string" ? new Error(config.error) : config.error;
10069
- }
10070
- }
10071
- async execute(params) {
10072
- if (this.shouldTrackCalls) {
10073
- this.calls.push({ params: { ...params }, timestamp: Date.now() });
10074
- }
10075
- if (this.delayMs > 0) {
10076
- await new Promise((resolve) => setTimeout(resolve, this.delayMs));
10077
- }
10078
- if (this.errorToThrow) {
10613
+ // src/testing/mock-conversation.ts
10614
+ var MockConversationManager = class {
10615
+ history;
10616
+ baseMessages;
10617
+ replacementHistory;
10618
+ replaceHistoryCallCount = 0;
10619
+ addedMessages = [];
10620
+ constructor(history = [], baseMessages = []) {
10621
+ this.history = [...history];
10622
+ this.baseMessages = [...baseMessages];
10623
+ }
10624
+ addUserMessage(content) {
10625
+ const msg = { role: "user", content };
10626
+ this.history.push(msg);
10627
+ this.addedMessages.push(msg);
10628
+ }
10629
+ addAssistantMessage(content) {
10630
+ const msg = { role: "assistant", content };
10631
+ this.history.push(msg);
10632
+ this.addedMessages.push(msg);
10633
+ }
10634
+ addGadgetCallResult(gadgetName, parameters, result) {
10635
+ const assistantMsg = {
10636
+ role: "assistant",
10637
+ content: `!!!GADGET_START:${gadgetName}
10638
+ ${JSON.stringify(parameters)}
10639
+ !!!GADGET_END`
10640
+ };
10641
+ const resultMsg = {
10642
+ role: "user",
10643
+ content: `Result: ${result}`
10644
+ };
10645
+ this.history.push(assistantMsg);
10646
+ this.history.push(resultMsg);
10647
+ this.addedMessages.push(assistantMsg);
10648
+ this.addedMessages.push(resultMsg);
10649
+ }
10650
+ getMessages() {
10651
+ return [...this.baseMessages, ...this.history];
10652
+ }
10653
+ getHistoryMessages() {
10654
+ return [...this.history];
10655
+ }
10656
+ getBaseMessages() {
10657
+ return [...this.baseMessages];
10658
+ }
10659
+ replaceHistory(newHistory) {
10660
+ this.replacementHistory = [...newHistory];
10661
+ this.history = [...newHistory];
10662
+ this.replaceHistoryCallCount++;
10663
+ }
10664
+ // ============================================
10665
+ // Test Helper Methods
10666
+ // ============================================
10667
+ /**
10668
+ * Check if replaceHistory was called.
10669
+ */
10670
+ wasReplaceHistoryCalled() {
10671
+ return this.replaceHistoryCallCount > 0;
10672
+ }
10673
+ /**
10674
+ * Get the number of times replaceHistory was called.
10675
+ */
10676
+ getReplaceHistoryCallCount() {
10677
+ return this.replaceHistoryCallCount;
10678
+ }
10679
+ /**
10680
+ * Get the most recent history passed to replaceHistory.
10681
+ * Returns undefined if replaceHistory was never called.
10682
+ */
10683
+ getReplacementHistory() {
10684
+ return this.replacementHistory;
10685
+ }
10686
+ /**
10687
+ * Get all messages that were added via add* methods.
10688
+ */
10689
+ getAddedMessages() {
10690
+ return [...this.addedMessages];
10691
+ }
10692
+ /**
10693
+ * Reset all tracking state while preserving the conversation.
10694
+ */
10695
+ resetTracking() {
10696
+ this.replacementHistory = void 0;
10697
+ this.replaceHistoryCallCount = 0;
10698
+ this.addedMessages = [];
10699
+ }
10700
+ /**
10701
+ * Completely reset the mock to initial state.
10702
+ * Note: baseMessages cannot be changed after construction.
10703
+ */
10704
+ reset(history = []) {
10705
+ this.history = [...history];
10706
+ this.resetTracking();
10707
+ }
10708
+ /**
10709
+ * Set the history directly (for test setup).
10710
+ */
10711
+ setHistory(messages) {
10712
+ this.history = [...messages];
10713
+ }
10714
+ /**
10715
+ * Get the current history length.
10716
+ */
10717
+ getHistoryLength() {
10718
+ return this.history.length;
10719
+ }
10720
+ /**
10721
+ * Get total message count (base + history).
10722
+ */
10723
+ getTotalMessageCount() {
10724
+ return this.baseMessages.length + this.history.length;
10725
+ }
10726
+ };
10727
+ function createMockConversationManager(turnCount, baseMessages = []) {
10728
+ const history = [];
10729
+ for (let i = 0; i < turnCount; i++) {
10730
+ history.push({
10731
+ role: "user",
10732
+ content: `User message ${i + 1}: This is turn ${i + 1} of the conversation.`
10733
+ });
10734
+ history.push({
10735
+ role: "assistant",
10736
+ content: `Assistant response ${i + 1}: I acknowledge turn ${i + 1}.`
10737
+ });
10738
+ }
10739
+ return new MockConversationManager(history, baseMessages);
10740
+ }
10741
+
10742
+ // src/testing/mock-gadget.ts
10743
+ init_gadget();
10744
+ var MockGadgetImpl = class extends AbstractGadget {
10745
+ name;
10746
+ description;
10747
+ parameterSchema;
10748
+ timeoutMs;
10749
+ calls = [];
10750
+ resultValue;
10751
+ resultFn;
10752
+ errorToThrow;
10753
+ delayMs;
10754
+ shouldTrackCalls;
10755
+ constructor(config) {
10756
+ super();
10757
+ this.name = config.name;
10758
+ this.description = config.description ?? `Mock gadget: ${config.name}`;
10759
+ this.parameterSchema = config.schema;
10760
+ this.resultValue = config.result;
10761
+ this.resultFn = config.resultFn;
10762
+ this.delayMs = config.delayMs ?? 0;
10763
+ this.shouldTrackCalls = config.trackCalls ?? true;
10764
+ this.timeoutMs = config.timeoutMs;
10765
+ if (config.error) {
10766
+ this.errorToThrow = typeof config.error === "string" ? new Error(config.error) : config.error;
10767
+ }
10768
+ }
10769
+ async execute(params) {
10770
+ if (this.shouldTrackCalls) {
10771
+ this.calls.push({ params: { ...params }, timestamp: Date.now() });
10772
+ }
10773
+ if (this.delayMs > 0) {
10774
+ await new Promise((resolve) => setTimeout(resolve, this.delayMs));
10775
+ }
10776
+ if (this.errorToThrow) {
10079
10777
  throw this.errorToThrow;
10080
10778
  }
10081
10779
  if (this.resultFn) {
@@ -10200,7 +10898,7 @@ function createTestStream(chunks) {
10200
10898
  function createTextStream(text3, options) {
10201
10899
  return async function* () {
10202
10900
  if (options?.delayMs) {
10203
- await sleep2(options.delayMs);
10901
+ await sleep3(options.delayMs);
10204
10902
  }
10205
10903
  const chunkSize = options?.chunkSize ?? text3.length;
10206
10904
  const chunks = [];
@@ -10222,7 +10920,7 @@ function createTextStream(text3, options) {
10222
10920
  }
10223
10921
  yield chunk;
10224
10922
  if (options?.chunkDelayMs && !isLast) {
10225
- await sleep2(options.chunkDelayMs);
10923
+ await sleep3(options.chunkDelayMs);
10226
10924
  }
10227
10925
  }
10228
10926
  }();
@@ -10260,366 +10958,10 @@ function createErrorStream(chunksBeforeError, error) {
10260
10958
  throw error;
10261
10959
  }();
10262
10960
  }
10263
- function sleep2(ms) {
10961
+ function sleep3(ms) {
10264
10962
  return new Promise((resolve) => setTimeout(resolve, ms));
10265
10963
  }
10266
10964
 
10267
- // src/testing/conversation-fixtures.ts
10268
- function createConversation(turnCount, options) {
10269
- const messages = [];
10270
- const userPrefix = options?.userPrefix ?? "User message";
10271
- const assistantPrefix = options?.assistantPrefix ?? "Assistant response";
10272
- const contentLength = options?.contentLength ?? 100;
10273
- for (let i = 0; i < turnCount; i++) {
10274
- const padding = " ".repeat(Math.max(0, contentLength - 30));
10275
- messages.push({
10276
- role: "user",
10277
- content: `${userPrefix} ${i + 1}: This is turn ${i + 1} of the conversation.${padding}`
10278
- });
10279
- messages.push({
10280
- role: "assistant",
10281
- content: `${assistantPrefix} ${i + 1}: I acknowledge turn ${i + 1}.${padding}`
10282
- });
10283
- }
10284
- return messages;
10285
- }
10286
- function createConversationWithGadgets(turnCount, gadgetCallsPerTurn = 1, options) {
10287
- const messages = [];
10288
- const gadgetNames = options?.gadgetNames ?? ["search", "calculate", "read"];
10289
- const contentLength = options?.contentLength ?? 50;
10290
- let gadgetIndex = 0;
10291
- for (let turn = 0; turn < turnCount; turn++) {
10292
- messages.push({
10293
- role: "user",
10294
- content: `User request ${turn + 1}${"x".repeat(contentLength)}`
10295
- });
10296
- for (let g = 0; g < gadgetCallsPerTurn; g++) {
10297
- const gadgetName = gadgetNames[gadgetIndex % gadgetNames.length];
10298
- gadgetIndex++;
10299
- messages.push({
10300
- role: "assistant",
10301
- content: `!!!GADGET_START:${gadgetName}
10302
- !!!ARG:query
10303
- test query ${turn}-${g}
10304
- !!!GADGET_END`
10305
- });
10306
- messages.push({
10307
- role: "user",
10308
- content: `Result: Gadget ${gadgetName} returned result for query ${turn}-${g}`
10309
- });
10310
- }
10311
- messages.push({
10312
- role: "assistant",
10313
- content: `Final response for turn ${turn + 1}${"y".repeat(contentLength)}`
10314
- });
10315
- }
10316
- return messages;
10317
- }
10318
- function estimateTokens(messages) {
10319
- return Math.ceil(
10320
- messages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0) / 4
10321
- );
10322
- }
10323
- function createUserMessage(content) {
10324
- return { role: "user", content };
10325
- }
10326
- function createAssistantMessage(content) {
10327
- return { role: "assistant", content };
10328
- }
10329
- function createSystemMessage(content) {
10330
- return { role: "system", content };
10331
- }
10332
- function createMinimalConversation() {
10333
- return [
10334
- { role: "user", content: "Hello" },
10335
- { role: "assistant", content: "Hi there!" }
10336
- ];
10337
- }
10338
- function createLargeConversation(targetTokens, options) {
10339
- const tokensPerTurn = options?.tokensPerTurn ?? 200;
10340
- const turnsNeeded = Math.ceil(targetTokens / tokensPerTurn);
10341
- const charsPerMessage = Math.floor(tokensPerTurn * 4 / 2);
10342
- return createConversation(turnsNeeded, {
10343
- contentLength: charsPerMessage
10344
- });
10345
- }
10346
-
10347
- // src/testing/mock-conversation.ts
10348
- var MockConversationManager = class {
10349
- history;
10350
- baseMessages;
10351
- replacementHistory;
10352
- replaceHistoryCallCount = 0;
10353
- addedMessages = [];
10354
- constructor(history = [], baseMessages = []) {
10355
- this.history = [...history];
10356
- this.baseMessages = [...baseMessages];
10357
- }
10358
- addUserMessage(content) {
10359
- const msg = { role: "user", content };
10360
- this.history.push(msg);
10361
- this.addedMessages.push(msg);
10362
- }
10363
- addAssistantMessage(content) {
10364
- const msg = { role: "assistant", content };
10365
- this.history.push(msg);
10366
- this.addedMessages.push(msg);
10367
- }
10368
- addGadgetCall(gadgetName, parameters, result) {
10369
- const assistantMsg = {
10370
- role: "assistant",
10371
- content: `!!!GADGET_START:${gadgetName}
10372
- ${JSON.stringify(parameters)}
10373
- !!!GADGET_END`
10374
- };
10375
- const resultMsg = {
10376
- role: "user",
10377
- content: `Result: ${result}`
10378
- };
10379
- this.history.push(assistantMsg);
10380
- this.history.push(resultMsg);
10381
- this.addedMessages.push(assistantMsg);
10382
- this.addedMessages.push(resultMsg);
10383
- }
10384
- getMessages() {
10385
- return [...this.baseMessages, ...this.history];
10386
- }
10387
- getHistoryMessages() {
10388
- return [...this.history];
10389
- }
10390
- getBaseMessages() {
10391
- return [...this.baseMessages];
10392
- }
10393
- replaceHistory(newHistory) {
10394
- this.replacementHistory = [...newHistory];
10395
- this.history = [...newHistory];
10396
- this.replaceHistoryCallCount++;
10397
- }
10398
- // ============================================
10399
- // Test Helper Methods
10400
- // ============================================
10401
- /**
10402
- * Check if replaceHistory was called.
10403
- */
10404
- wasReplaceHistoryCalled() {
10405
- return this.replaceHistoryCallCount > 0;
10406
- }
10407
- /**
10408
- * Get the number of times replaceHistory was called.
10409
- */
10410
- getReplaceHistoryCallCount() {
10411
- return this.replaceHistoryCallCount;
10412
- }
10413
- /**
10414
- * Get the most recent history passed to replaceHistory.
10415
- * Returns undefined if replaceHistory was never called.
10416
- */
10417
- getReplacementHistory() {
10418
- return this.replacementHistory;
10419
- }
10420
- /**
10421
- * Get all messages that were added via add* methods.
10422
- */
10423
- getAddedMessages() {
10424
- return [...this.addedMessages];
10425
- }
10426
- /**
10427
- * Reset all tracking state while preserving the conversation.
10428
- */
10429
- resetTracking() {
10430
- this.replacementHistory = void 0;
10431
- this.replaceHistoryCallCount = 0;
10432
- this.addedMessages = [];
10433
- }
10434
- /**
10435
- * Completely reset the mock to initial state.
10436
- * Note: baseMessages cannot be changed after construction.
10437
- */
10438
- reset(history = []) {
10439
- this.history = [...history];
10440
- this.resetTracking();
10441
- }
10442
- /**
10443
- * Set the history directly (for test setup).
10444
- */
10445
- setHistory(messages) {
10446
- this.history = [...messages];
10447
- }
10448
- /**
10449
- * Get the current history length.
10450
- */
10451
- getHistoryLength() {
10452
- return this.history.length;
10453
- }
10454
- /**
10455
- * Get total message count (base + history).
10456
- */
10457
- getTotalMessageCount() {
10458
- return this.baseMessages.length + this.history.length;
10459
- }
10460
- };
10461
- function createMockConversationManager(turnCount, baseMessages = []) {
10462
- const history = [];
10463
- for (let i = 0; i < turnCount; i++) {
10464
- history.push({
10465
- role: "user",
10466
- content: `User message ${i + 1}: This is turn ${i + 1} of the conversation.`
10467
- });
10468
- history.push({
10469
- role: "assistant",
10470
- content: `Assistant response ${i + 1}: I acknowledge turn ${i + 1}.`
10471
- });
10472
- }
10473
- return new MockConversationManager(history, baseMessages);
10474
- }
10475
-
10476
- // src/testing/cli-helpers.ts
10477
- import { PassThrough, Readable, Writable } from "node:stream";
10478
- function createTestEnvironment(options = {}) {
10479
- const stdin = createMockReadable(options.stdin);
10480
- const stdout = new PassThrough();
10481
- const stderr = new PassThrough();
10482
- let exitCode;
10483
- return {
10484
- stdin,
10485
- stdout,
10486
- stderr,
10487
- isTTY: options.isTTY ?? false,
10488
- argv: options.argv ?? ["node", "llmist"],
10489
- env: { ...filterDefinedEnv(process.env), ...options.env },
10490
- get exitCode() {
10491
- return exitCode;
10492
- },
10493
- setExitCode: (code) => {
10494
- exitCode = code;
10495
- }
10496
- };
10497
- }
10498
- function createMockReadable(input) {
10499
- if (!input) {
10500
- const stream3 = new Readable({ read() {
10501
- } });
10502
- stream3.push(null);
10503
- return stream3;
10504
- }
10505
- const content = Array.isArray(input) ? `${input.join("\n")}
10506
- ` : input;
10507
- const stream2 = new Readable({ read() {
10508
- } });
10509
- stream2.push(content);
10510
- stream2.push(null);
10511
- return stream2;
10512
- }
10513
- function createMockWritable() {
10514
- const chunks = [];
10515
- const stream2 = new Writable({
10516
- write(chunk, _encoding, callback) {
10517
- chunks.push(Buffer.from(chunk));
10518
- callback();
10519
- }
10520
- });
10521
- stream2.getData = () => Buffer.concat(chunks).toString("utf8");
10522
- return stream2;
10523
- }
10524
- async function collectOutput(stream2, timeout = 5e3) {
10525
- return new Promise((resolve, reject) => {
10526
- const chunks = [];
10527
- const timeoutId = setTimeout(() => {
10528
- resolve(Buffer.concat(chunks).toString("utf8"));
10529
- }, timeout);
10530
- stream2.on("data", (chunk) => {
10531
- chunks.push(Buffer.from(chunk));
10532
- });
10533
- stream2.on("end", () => {
10534
- clearTimeout(timeoutId);
10535
- resolve(Buffer.concat(chunks).toString("utf8"));
10536
- });
10537
- stream2.on("error", (err) => {
10538
- clearTimeout(timeoutId);
10539
- reject(err);
10540
- });
10541
- });
10542
- }
10543
- function getBufferedOutput(stream2) {
10544
- const chunks = [];
10545
- for (; ; ) {
10546
- const chunk = stream2.read();
10547
- if (chunk === null) break;
10548
- chunks.push(chunk);
10549
- }
10550
- return Buffer.concat(chunks).toString("utf8");
10551
- }
10552
- function createMockPrompt(responses) {
10553
- let index = 0;
10554
- return async (_question) => {
10555
- if (index >= responses.length) {
10556
- throw new Error(`Mock prompt exhausted: no response for question ${index + 1}`);
10557
- }
10558
- return responses[index++];
10559
- };
10560
- }
10561
- var MockPromptRecorder = class {
10562
- responses;
10563
- index = 0;
10564
- questions = [];
10565
- constructor(responses) {
10566
- this.responses = responses;
10567
- }
10568
- /**
10569
- * The prompt function to use in tests.
10570
- */
10571
- prompt = async (question) => {
10572
- this.questions.push(question);
10573
- if (this.index >= this.responses.length) {
10574
- throw new Error(`Mock prompt exhausted after ${this.index} questions`);
10575
- }
10576
- return this.responses[this.index++];
10577
- };
10578
- /**
10579
- * Get all questions that were asked.
10580
- */
10581
- getQuestions() {
10582
- return [...this.questions];
10583
- }
10584
- /**
10585
- * Get the number of questions asked.
10586
- */
10587
- getQuestionCount() {
10588
- return this.questions.length;
10589
- }
10590
- /**
10591
- * Reset the recorder state.
10592
- */
10593
- reset(newResponses) {
10594
- this.index = 0;
10595
- this.questions = [];
10596
- if (newResponses) {
10597
- this.responses = newResponses;
10598
- }
10599
- }
10600
- };
10601
- async function waitFor(condition, timeout = 5e3, interval = 50) {
10602
- const startTime = Date.now();
10603
- while (!condition()) {
10604
- if (Date.now() - startTime > timeout) {
10605
- throw new Error(`waitFor timed out after ${timeout}ms`);
10606
- }
10607
- await sleep3(interval);
10608
- }
10609
- }
10610
- function sleep3(ms) {
10611
- return new Promise((resolve) => setTimeout(resolve, ms));
10612
- }
10613
- function filterDefinedEnv(env) {
10614
- const result = {};
10615
- for (const [key, value] of Object.entries(env)) {
10616
- if (value !== void 0) {
10617
- result[key] = value;
10618
- }
10619
- }
10620
- return result;
10621
- }
10622
-
10623
10965
  export {
10624
10966
  isTextPart,
10625
10967
  isImagePart,
@@ -10652,20 +10994,23 @@ export {
10652
10994
  resolveRulesTemplate,
10653
10995
  resolveHintTemplate,
10654
10996
  init_prompt_config,
10655
- normalizeContent,
10656
- extractText,
10997
+ normalizeMessageContent,
10998
+ extractMessageText,
10657
10999
  LLMMessageBuilder,
10658
11000
  init_messages,
10659
- BreakLoopException,
10660
- HumanInputException,
10661
- AbortError,
11001
+ MediaStore,
11002
+ init_media_store,
11003
+ TaskCompletionSignal,
11004
+ HumanInputRequiredException,
11005
+ TimeoutException,
11006
+ AbortException,
10662
11007
  init_exceptions,
10663
11008
  createLogger,
10664
11009
  defaultLogger,
10665
11010
  init_logger,
10666
11011
  schemaToJSONSchema,
10667
11012
  init_schema_to_json,
10668
- BaseGadget,
11013
+ AbstractGadget,
10669
11014
  init_gadget,
10670
11015
  createGadget,
10671
11016
  init_create_gadget,
@@ -10681,15 +11026,15 @@ export {
10681
11026
  init_strategies,
10682
11027
  CompactionManager,
10683
11028
  init_manager,
10684
- GadgetOutputStore,
10685
- init_gadget_output_store,
10686
11029
  ConversationManager,
10687
11030
  init_conversation_manager,
10688
11031
  runWithHandlers,
10689
11032
  collectEvents,
10690
11033
  collectText,
10691
11034
  init_event_handlers,
10692
- StreamParser,
11035
+ GadgetOutputStore,
11036
+ init_gadget_output_store,
11037
+ GadgetCallParser,
10693
11038
  init_parser,
10694
11039
  GadgetExecutor,
10695
11040
  init_executor,
@@ -10721,6 +11066,22 @@ export {
10721
11066
  init_builder,
10722
11067
  validateAndApplyDefaults,
10723
11068
  validateGadgetParams,
11069
+ createTestEnvironment,
11070
+ createMockReadable,
11071
+ createMockWritable,
11072
+ collectOutput,
11073
+ getBufferedOutput,
11074
+ createMockPrompt,
11075
+ MockPromptRecorder,
11076
+ waitFor,
11077
+ createConversation,
11078
+ createConversationWithGadgets,
11079
+ estimateTokens,
11080
+ createUserMessage,
11081
+ createAssistantMessage,
11082
+ createSystemMessage,
11083
+ createMinimalConversation,
11084
+ createLargeConversation,
10724
11085
  testGadget,
10725
11086
  testGadgetBatch,
10726
11087
  MockManager,
@@ -10732,6 +11093,8 @@ export {
10732
11093
  MockBuilder,
10733
11094
  mockLLM,
10734
11095
  createMockClient,
11096
+ MockConversationManager,
11097
+ createMockConversationManager,
10735
11098
  createMockGadget,
10736
11099
  MockGadgetBuilder,
10737
11100
  mockGadget,
@@ -10741,24 +11104,6 @@ export {
10741
11104
  collectStreamText,
10742
11105
  getStreamFinalChunk,
10743
11106
  createEmptyStream,
10744
- createErrorStream,
10745
- createConversation,
10746
- createConversationWithGadgets,
10747
- estimateTokens,
10748
- createUserMessage,
10749
- createAssistantMessage,
10750
- createSystemMessage,
10751
- createMinimalConversation,
10752
- createLargeConversation,
10753
- MockConversationManager,
10754
- createMockConversationManager,
10755
- createTestEnvironment,
10756
- createMockReadable,
10757
- createMockWritable,
10758
- collectOutput,
10759
- getBufferedOutput,
10760
- createMockPrompt,
10761
- MockPromptRecorder,
10762
- waitFor
11107
+ createErrorStream
10763
11108
  };
10764
- //# sourceMappingURL=chunk-YHS2DYXP.js.map
11109
+ //# sourceMappingURL=chunk-67MMSOAT.js.map