llmist 2.6.0 → 3.1.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.
@@ -301,13 +301,13 @@ var init_prompt_config = __esm({
301
301
  });
302
302
 
303
303
  // src/core/messages.ts
304
- function normalizeContent(content) {
304
+ function normalizeMessageContent(content) {
305
305
  if (typeof content === "string") {
306
306
  return [{ type: "text", text: content }];
307
307
  }
308
308
  return content;
309
309
  }
310
- function extractText(content) {
310
+ function extractMessageText(content) {
311
311
  if (typeof content === "string") {
312
312
  return content;
313
313
  }
@@ -667,7 +667,17 @@ Produces: { "items": ["first", "second"] }`);
667
667
  this.messages.push({ role: "user", content: parts });
668
668
  return this;
669
669
  }
670
- addGadgetCall(gadget, parameters, result, media, mediaIds) {
670
+ /**
671
+ * Record a gadget execution result in the message history.
672
+ * Creates an assistant message with the gadget invocation and a user message with the result.
673
+ *
674
+ * @param gadget - Name of the gadget that was executed
675
+ * @param parameters - Parameters that were passed to the gadget
676
+ * @param result - Text result from the gadget execution
677
+ * @param media - Optional media outputs from the gadget
678
+ * @param mediaIds - Optional IDs for the media outputs
679
+ */
680
+ addGadgetCallResult(gadget, parameters, result, media, mediaIds) {
671
681
  const paramStr = this.formatBlockParameters(parameters, "");
672
682
  this.messages.push({
673
683
  role: "assistant",
@@ -1232,21 +1242,21 @@ var init_media_store = __esm({
1232
1242
  });
1233
1243
 
1234
1244
  // src/gadgets/exceptions.ts
1235
- var BreakLoopException, HumanInputException, TimeoutException, AbortError;
1245
+ var TaskCompletionSignal, HumanInputRequiredException, TimeoutException, AbortException;
1236
1246
  var init_exceptions = __esm({
1237
1247
  "src/gadgets/exceptions.ts"() {
1238
1248
  "use strict";
1239
- BreakLoopException = class extends Error {
1249
+ TaskCompletionSignal = class extends Error {
1240
1250
  constructor(message) {
1241
1251
  super(message ?? "Agent loop terminated by gadget");
1242
- this.name = "BreakLoopException";
1252
+ this.name = "TaskCompletionSignal";
1243
1253
  }
1244
1254
  };
1245
- HumanInputException = class extends Error {
1255
+ HumanInputRequiredException = class extends Error {
1246
1256
  question;
1247
1257
  constructor(question) {
1248
1258
  super(`Human input required: ${question}`);
1249
- this.name = "HumanInputException";
1259
+ this.name = "HumanInputRequiredException";
1250
1260
  this.question = question;
1251
1261
  }
1252
1262
  };
@@ -1260,10 +1270,10 @@ var init_exceptions = __esm({
1260
1270
  this.timeoutMs = timeoutMs;
1261
1271
  }
1262
1272
  };
1263
- AbortError = class extends Error {
1273
+ AbortException = class extends Error {
1264
1274
  constructor(message) {
1265
1275
  super(message || "Gadget execution was aborted");
1266
- this.name = "AbortError";
1276
+ this.name = "AbortException";
1267
1277
  }
1268
1278
  };
1269
1279
  }
@@ -1353,7 +1363,7 @@ var init_schema_to_json = __esm({
1353
1363
  });
1354
1364
 
1355
1365
  // src/gadgets/gadget.ts
1356
- function formatParamsAsBlock(params, prefix = "", argPrefix = GADGET_ARG_PREFIX) {
1366
+ function formatParamsForBlockExample(params, prefix = "", argPrefix = GADGET_ARG_PREFIX) {
1357
1367
  const lines = [];
1358
1368
  for (const [key, value] of Object.entries(params)) {
1359
1369
  const fullPath = prefix ? `${prefix}/${key}` : key;
@@ -1361,14 +1371,14 @@ function formatParamsAsBlock(params, prefix = "", argPrefix = GADGET_ARG_PREFIX)
1361
1371
  value.forEach((item, index) => {
1362
1372
  const itemPath = `${fullPath}/${index}`;
1363
1373
  if (typeof item === "object" && item !== null) {
1364
- lines.push(formatParamsAsBlock(item, itemPath, argPrefix));
1374
+ lines.push(formatParamsForBlockExample(item, itemPath, argPrefix));
1365
1375
  } else {
1366
1376
  lines.push(`${argPrefix}${itemPath}`);
1367
1377
  lines.push(String(item));
1368
1378
  }
1369
1379
  });
1370
1380
  } else if (typeof value === "object" && value !== null) {
1371
- lines.push(formatParamsAsBlock(value, fullPath, argPrefix));
1381
+ lines.push(formatParamsForBlockExample(value, fullPath, argPrefix));
1372
1382
  } else {
1373
1383
  lines.push(`${argPrefix}${fullPath}`);
1374
1384
  lines.push(String(value));
@@ -1457,7 +1467,7 @@ function formatSchemaAsPlainText(schema, indent = "", atRoot = true) {
1457
1467
  }
1458
1468
  return lines.join("\n");
1459
1469
  }
1460
- var BaseGadget;
1470
+ var AbstractGadget;
1461
1471
  var init_gadget = __esm({
1462
1472
  "src/gadgets/gadget.ts"() {
1463
1473
  "use strict";
@@ -1465,7 +1475,7 @@ var init_gadget = __esm({
1465
1475
  init_exceptions();
1466
1476
  init_schema_to_json();
1467
1477
  init_schema_validator();
1468
- BaseGadget = class {
1478
+ AbstractGadget = class {
1469
1479
  /**
1470
1480
  * The name of the gadget. Used for identification when LLM calls it.
1471
1481
  * If not provided, defaults to the class name.
@@ -1493,14 +1503,14 @@ var init_gadget = __esm({
1493
1503
  */
1494
1504
  examples;
1495
1505
  /**
1496
- * Throws an AbortError if the execution has been aborted.
1506
+ * Throws an AbortException if the execution has been aborted.
1497
1507
  *
1498
1508
  * Call this at key checkpoints in long-running gadgets to allow early exit
1499
1509
  * when the gadget has been cancelled (e.g., due to timeout). This enables
1500
1510
  * resource cleanup and prevents unnecessary work after cancellation.
1501
1511
  *
1502
1512
  * @param ctx - The execution context containing the abort signal
1503
- * @throws AbortError if ctx.signal.aborted is true
1513
+ * @throws AbortException if ctx.signal.aborted is true
1504
1514
  *
1505
1515
  * @example
1506
1516
  * ```typescript
@@ -1525,7 +1535,7 @@ var init_gadget = __esm({
1525
1535
  */
1526
1536
  throwIfAborted(ctx) {
1527
1537
  if (ctx?.signal?.aborted) {
1528
- throw new AbortError();
1538
+ throw new AbortException();
1529
1539
  }
1530
1540
  }
1531
1541
  /**
@@ -1667,7 +1677,7 @@ var init_gadget = __esm({
1667
1677
  }
1668
1678
  parts.push(`${effectiveStartPrefix}${gadgetName}`);
1669
1679
  parts.push(
1670
- formatParamsAsBlock(example.params, "", effectiveArgPrefix)
1680
+ formatParamsForBlockExample(example.params, "", effectiveArgPrefix)
1671
1681
  );
1672
1682
  parts.push(effectiveEndPrefix);
1673
1683
  if (example.output !== void 0) {
@@ -1685,7 +1695,7 @@ var init_gadget = __esm({
1685
1695
 
1686
1696
  // src/gadgets/create-gadget.ts
1687
1697
  function createGadget(config) {
1688
- class DynamicGadget extends BaseGadget {
1698
+ class DynamicGadget extends AbstractGadget {
1689
1699
  name = config.name;
1690
1700
  description = config.description;
1691
1701
  parameterSchema = config.schema;
@@ -2323,8 +2333,8 @@ var init_conversation_manager = __esm({
2323
2333
  addAssistantMessage(content) {
2324
2334
  this.historyBuilder.addAssistant(content);
2325
2335
  }
2326
- addGadgetCall(gadgetName, parameters, result, media, mediaIds) {
2327
- this.historyBuilder.addGadgetCall(gadgetName, parameters, result, media, mediaIds);
2336
+ addGadgetCallResult(gadgetName, parameters, result, media, mediaIds) {
2337
+ this.historyBuilder.addGadgetCallResult(gadgetName, parameters, result, media, mediaIds);
2328
2338
  }
2329
2339
  getMessages() {
2330
2340
  return [...this.baseMessages, ...this.initialMessages, ...this.historyBuilder.build()];
@@ -2344,7 +2354,7 @@ var init_conversation_manager = __esm({
2344
2354
  if (msg.role === "user") {
2345
2355
  this.historyBuilder.addUser(msg.content);
2346
2356
  } else if (msg.role === "assistant") {
2347
- this.historyBuilder.addAssistant(extractText(msg.content));
2357
+ this.historyBuilder.addAssistant(extractMessageText(msg.content));
2348
2358
  }
2349
2359
  }
2350
2360
  }
@@ -3194,12 +3204,12 @@ var init_cost_reporting_client = __esm({
3194
3204
  });
3195
3205
 
3196
3206
  // src/gadgets/error-formatter.ts
3197
- var GadgetErrorFormatter;
3207
+ var GadgetExecutionErrorFormatter;
3198
3208
  var init_error_formatter = __esm({
3199
3209
  "src/gadgets/error-formatter.ts"() {
3200
3210
  "use strict";
3201
3211
  init_constants();
3202
- GadgetErrorFormatter = class {
3212
+ GadgetExecutionErrorFormatter = class {
3203
3213
  argPrefix;
3204
3214
  startPrefix;
3205
3215
  endPrefix;
@@ -3285,16 +3295,16 @@ function stripMarkdownFences(content) {
3285
3295
  cleaned = cleaned.replace(closingFence, "");
3286
3296
  return cleaned.trim();
3287
3297
  }
3288
- var globalInvocationCounter, StreamParser;
3298
+ var globalInvocationCounter, GadgetCallParser;
3289
3299
  var init_parser = __esm({
3290
3300
  "src/gadgets/parser.ts"() {
3291
3301
  "use strict";
3292
3302
  init_constants();
3293
3303
  init_block_params();
3294
3304
  globalInvocationCounter = 0;
3295
- StreamParser = class {
3305
+ GadgetCallParser = class {
3296
3306
  buffer = "";
3297
- lastReportedTextLength = 0;
3307
+ lastEmittedTextOffset = 0;
3298
3308
  startPrefix;
3299
3309
  endPrefix;
3300
3310
  argPrefix;
@@ -3303,16 +3313,20 @@ var init_parser = __esm({
3303
3313
  this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
3304
3314
  this.argPrefix = options.argPrefix ?? GADGET_ARG_PREFIX;
3305
3315
  }
3306
- takeTextUntil(index) {
3307
- if (index <= this.lastReportedTextLength) {
3316
+ /**
3317
+ * Extract and consume text up to the given index.
3318
+ * Returns undefined if no meaningful text to emit.
3319
+ */
3320
+ extractTextSegment(index) {
3321
+ if (index <= this.lastEmittedTextOffset) {
3308
3322
  return void 0;
3309
3323
  }
3310
- const segment = this.buffer.slice(this.lastReportedTextLength, index);
3311
- this.lastReportedTextLength = index;
3324
+ const segment = this.buffer.slice(this.lastEmittedTextOffset, index);
3325
+ this.lastEmittedTextOffset = index;
3312
3326
  return segment.trim().length > 0 ? segment : void 0;
3313
3327
  }
3314
3328
  /**
3315
- * Parse gadget name with optional invocation ID and dependencies.
3329
+ * Parse gadget invocation metadata from the header line.
3316
3330
  *
3317
3331
  * Supported formats:
3318
3332
  * - `GadgetName` - Auto-generate ID, no dependencies
@@ -3321,24 +3335,24 @@ var init_parser = __esm({
3321
3335
  *
3322
3336
  * Dependencies must be comma-separated invocation IDs.
3323
3337
  */
3324
- parseGadgetName(gadgetName) {
3325
- const parts = gadgetName.split(":");
3338
+ parseInvocationMetadata(headerLine) {
3339
+ const parts = headerLine.split(":");
3326
3340
  if (parts.length === 1) {
3327
3341
  return {
3328
- actualName: parts[0],
3342
+ gadgetName: parts[0],
3329
3343
  invocationId: `gadget_${++globalInvocationCounter}`,
3330
3344
  dependencies: []
3331
3345
  };
3332
3346
  } else if (parts.length === 2) {
3333
3347
  return {
3334
- actualName: parts[0],
3348
+ gadgetName: parts[0],
3335
3349
  invocationId: parts[1].trim(),
3336
3350
  dependencies: []
3337
3351
  };
3338
3352
  } else {
3339
3353
  const deps = parts[2].split(",").map((d) => d.trim()).filter((d) => d.length > 0);
3340
3354
  return {
3341
- actualName: parts[0],
3355
+ gadgetName: parts[0],
3342
3356
  invocationId: parts[1].trim(),
3343
3357
  dependencies: deps
3344
3358
  };
@@ -3370,19 +3384,15 @@ var init_parser = __esm({
3370
3384
  while (true) {
3371
3385
  const partStartIndex = this.buffer.indexOf(this.startPrefix, startIndex);
3372
3386
  if (partStartIndex === -1) break;
3373
- const textBefore = this.takeTextUntil(partStartIndex);
3387
+ const textBefore = this.extractTextSegment(partStartIndex);
3374
3388
  if (textBefore !== void 0) {
3375
3389
  yield { type: "text", content: textBefore };
3376
3390
  }
3377
3391
  const metadataStartIndex = partStartIndex + this.startPrefix.length;
3378
3392
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
3379
3393
  if (metadataEndIndex === -1) break;
3380
- const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3381
- const {
3382
- actualName: actualGadgetName,
3383
- invocationId,
3384
- dependencies
3385
- } = this.parseGadgetName(gadgetName);
3394
+ const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3395
+ const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
3386
3396
  const contentStartIndex = metadataEndIndex + 1;
3387
3397
  let partEndIndex;
3388
3398
  let endMarkerLength = 0;
@@ -3402,7 +3412,7 @@ var init_parser = __esm({
3402
3412
  yield {
3403
3413
  type: "gadget_call",
3404
3414
  call: {
3405
- gadgetName: actualGadgetName,
3415
+ gadgetName,
3406
3416
  invocationId,
3407
3417
  parametersRaw,
3408
3418
  parameters,
@@ -3411,37 +3421,33 @@ var init_parser = __esm({
3411
3421
  }
3412
3422
  };
3413
3423
  startIndex = partEndIndex + endMarkerLength;
3414
- this.lastReportedTextLength = startIndex;
3424
+ this.lastEmittedTextOffset = startIndex;
3415
3425
  }
3416
3426
  if (startIndex > 0) {
3417
3427
  this.buffer = this.buffer.substring(startIndex);
3418
- this.lastReportedTextLength = 0;
3428
+ this.lastEmittedTextOffset = 0;
3419
3429
  }
3420
3430
  }
3421
3431
  // Finalize parsing and return remaining text or incomplete gadgets
3422
3432
  *finalize() {
3423
- const startIndex = this.buffer.indexOf(this.startPrefix, this.lastReportedTextLength);
3433
+ const startIndex = this.buffer.indexOf(this.startPrefix, this.lastEmittedTextOffset);
3424
3434
  if (startIndex !== -1) {
3425
- const textBefore = this.takeTextUntil(startIndex);
3435
+ const textBefore = this.extractTextSegment(startIndex);
3426
3436
  if (textBefore !== void 0) {
3427
3437
  yield { type: "text", content: textBefore };
3428
3438
  }
3429
3439
  const metadataStartIndex = startIndex + this.startPrefix.length;
3430
3440
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
3431
3441
  if (metadataEndIndex !== -1) {
3432
- const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3433
- const {
3434
- actualName: actualGadgetName,
3435
- invocationId,
3436
- dependencies
3437
- } = this.parseGadgetName(gadgetName);
3442
+ const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3443
+ const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
3438
3444
  const contentStartIndex = metadataEndIndex + 1;
3439
3445
  const parametersRaw = this.buffer.substring(contentStartIndex).trim();
3440
3446
  const { parameters, parseError } = this.parseParameters(parametersRaw);
3441
3447
  yield {
3442
3448
  type: "gadget_call",
3443
3449
  call: {
3444
- gadgetName: actualGadgetName,
3450
+ gadgetName,
3445
3451
  invocationId,
3446
3452
  parametersRaw,
3447
3453
  parameters,
@@ -3452,7 +3458,7 @@ var init_parser = __esm({
3452
3458
  return;
3453
3459
  }
3454
3460
  }
3455
- const remainingText = this.takeTextUntil(this.buffer.length);
3461
+ const remainingText = this.extractTextSegment(this.buffer.length);
3456
3462
  if (remainingText !== void 0) {
3457
3463
  yield { type: "text", content: remainingText };
3458
3464
  }
@@ -3460,7 +3466,7 @@ var init_parser = __esm({
3460
3466
  // Reset parser state (note: global invocation counter is NOT reset to ensure unique IDs)
3461
3467
  reset() {
3462
3468
  this.buffer = "";
3463
- this.lastReportedTextLength = 0;
3469
+ this.lastEmittedTextOffset = 0;
3464
3470
  }
3465
3471
  };
3466
3472
  }
@@ -3479,14 +3485,17 @@ var init_executor = __esm({
3479
3485
  init_exceptions();
3480
3486
  init_parser();
3481
3487
  GadgetExecutor = class {
3482
- constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore) {
3488
+ constructor(registry, requestHumanInput, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig, onNestedEvent) {
3483
3489
  this.registry = registry;
3484
- this.onHumanInputRequired = onHumanInputRequired;
3490
+ this.requestHumanInput = requestHumanInput;
3485
3491
  this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
3486
3492
  this.client = client;
3487
3493
  this.mediaStore = mediaStore;
3494
+ this.agentConfig = agentConfig;
3495
+ this.subagentConfig = subagentConfig;
3496
+ this.onNestedEvent = onNestedEvent;
3488
3497
  this.logger = logger ?? createLogger({ name: "llmist:executor" });
3489
- this.errorFormatter = new GadgetErrorFormatter(errorFormatterOptions);
3498
+ this.errorFormatter = new GadgetExecutionErrorFormatter(errorFormatterOptions);
3490
3499
  this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
3491
3500
  }
3492
3501
  logger;
@@ -3506,11 +3515,11 @@ var init_executor = __esm({
3506
3515
  });
3507
3516
  }
3508
3517
  /**
3509
- * Normalizes gadget execute result to consistent format.
3518
+ * Unify gadget execute result to consistent internal format.
3510
3519
  * Handles string returns (backwards compat), object returns with cost,
3511
3520
  * and object returns with media.
3512
3521
  */
3513
- normalizeExecuteResult(raw) {
3522
+ unifyExecuteResult(raw) {
3514
3523
  if (typeof raw === "string") {
3515
3524
  return { result: raw, cost: 0 };
3516
3525
  }
@@ -3628,7 +3637,11 @@ var init_executor = __esm({
3628
3637
  const ctx = {
3629
3638
  reportCost,
3630
3639
  llmist: this.client ? new CostReportingLLMistWrapper(this.client, reportCost) : void 0,
3631
- signal: abortController.signal
3640
+ signal: abortController.signal,
3641
+ agentConfig: this.agentConfig,
3642
+ subagentConfig: this.subagentConfig,
3643
+ invocationId: call.invocationId,
3644
+ onNestedEvent: this.onNestedEvent
3632
3645
  };
3633
3646
  let rawResult;
3634
3647
  if (timeoutMs && timeoutMs > 0) {
@@ -3643,7 +3656,7 @@ var init_executor = __esm({
3643
3656
  } else {
3644
3657
  rawResult = await Promise.resolve(gadget.execute(validatedParameters, ctx));
3645
3658
  }
3646
- const { result, media, cost: returnCost } = this.normalizeExecuteResult(rawResult);
3659
+ const { result, media, cost: returnCost } = this.unifyExecuteResult(rawResult);
3647
3660
  const totalCost = callbackCost + returnCost;
3648
3661
  let mediaIds;
3649
3662
  let storedMedia;
@@ -3689,7 +3702,7 @@ var init_executor = __esm({
3689
3702
  storedMedia
3690
3703
  };
3691
3704
  } catch (error) {
3692
- if (error instanceof BreakLoopException) {
3705
+ if (error instanceof TaskCompletionSignal) {
3693
3706
  this.logger.info("Gadget requested loop termination", {
3694
3707
  gadgetName: call.gadgetName,
3695
3708
  message: error.message
@@ -3717,7 +3730,7 @@ var init_executor = __esm({
3717
3730
  executionTimeMs: Date.now() - startTime
3718
3731
  };
3719
3732
  }
3720
- if (error instanceof AbortError) {
3733
+ if (error instanceof AbortException) {
3721
3734
  this.logger.info("Gadget execution was aborted", {
3722
3735
  gadgetName: call.gadgetName,
3723
3736
  executionTimeMs: Date.now() - startTime
@@ -3730,14 +3743,14 @@ var init_executor = __esm({
3730
3743
  executionTimeMs: Date.now() - startTime
3731
3744
  };
3732
3745
  }
3733
- if (error instanceof HumanInputException) {
3746
+ if (error instanceof HumanInputRequiredException) {
3734
3747
  this.logger.info("Gadget requested human input", {
3735
3748
  gadgetName: call.gadgetName,
3736
3749
  question: error.question
3737
3750
  });
3738
- if (this.onHumanInputRequired) {
3751
+ if (this.requestHumanInput) {
3739
3752
  try {
3740
- const answer = await this.onHumanInputRequired(error.question);
3753
+ const answer = await this.requestHumanInput(error.question);
3741
3754
  this.logger.debug("Human input received", {
3742
3755
  gadgetName: call.gadgetName,
3743
3756
  answerLength: answer.length
@@ -3835,13 +3848,13 @@ var init_stream_processor = __esm({
3835
3848
  parser;
3836
3849
  executor;
3837
3850
  stopOnGadgetError;
3838
- shouldContinueAfterError;
3839
- accumulatedText = "";
3840
- shouldStopExecution = false;
3851
+ canRecoverFromGadgetError;
3852
+ responseText = "";
3853
+ executionHalted = false;
3841
3854
  observerFailureCount = 0;
3842
3855
  // Dependency tracking for gadget execution DAG
3843
3856
  /** Gadgets waiting for their dependencies to complete */
3844
- pendingGadgets = /* @__PURE__ */ new Map();
3857
+ gadgetsAwaitingDependencies = /* @__PURE__ */ new Map();
3845
3858
  /** Completed gadget results, keyed by invocation ID */
3846
3859
  completedResults = /* @__PURE__ */ new Map();
3847
3860
  /** Invocation IDs of gadgets that have failed (error or skipped due to dependency) */
@@ -3852,27 +3865,36 @@ var init_stream_processor = __esm({
3852
3865
  this.hooks = options.hooks ?? {};
3853
3866
  this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
3854
3867
  this.stopOnGadgetError = options.stopOnGadgetError ?? true;
3855
- this.shouldContinueAfterError = options.shouldContinueAfterError;
3856
- this.parser = new StreamParser({
3868
+ this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
3869
+ this.parser = new GadgetCallParser({
3857
3870
  startPrefix: options.gadgetStartPrefix,
3858
3871
  endPrefix: options.gadgetEndPrefix,
3859
3872
  argPrefix: options.gadgetArgPrefix
3860
3873
  });
3861
3874
  this.executor = new GadgetExecutor(
3862
3875
  options.registry,
3863
- options.onHumanInputRequired,
3876
+ options.requestHumanInput,
3864
3877
  this.logger.getSubLogger({ name: "executor" }),
3865
3878
  options.defaultGadgetTimeoutMs,
3866
3879
  { argPrefix: options.gadgetArgPrefix },
3867
3880
  options.client,
3868
- options.mediaStore
3881
+ options.mediaStore,
3882
+ options.agentConfig,
3883
+ options.subagentConfig,
3884
+ options.onNestedEvent
3869
3885
  );
3870
3886
  }
3871
3887
  /**
3872
- * Process an LLM stream and return structured results.
3888
+ * Process an LLM stream and yield events in real-time.
3889
+ *
3890
+ * This is an async generator that yields events immediately as they occur:
3891
+ * - Text events are yielded as text is streamed from the LLM
3892
+ * - gadget_call events are yielded immediately when a gadget call is parsed
3893
+ * - gadget_result events are yielded when gadget execution completes
3894
+ *
3895
+ * The final event is always a StreamCompletionEvent containing metadata.
3873
3896
  */
3874
- async process(stream2) {
3875
- const outputs = [];
3897
+ async *process(stream2) {
3876
3898
  let finishReason = null;
3877
3899
  let usage;
3878
3900
  let didExecuteGadgets = false;
@@ -3886,7 +3908,7 @@ var init_stream_processor = __esm({
3886
3908
  if (this.hooks.interceptors?.interceptRawChunk) {
3887
3909
  const context = {
3888
3910
  iteration: this.iteration,
3889
- accumulatedText: this.accumulatedText,
3911
+ accumulatedText: this.responseText,
3890
3912
  logger: this.logger
3891
3913
  };
3892
3914
  const intercepted = this.hooks.interceptors.interceptRawChunk(processedChunk, context);
@@ -3897,7 +3919,7 @@ var init_stream_processor = __esm({
3897
3919
  }
3898
3920
  }
3899
3921
  if (processedChunk) {
3900
- this.accumulatedText += processedChunk;
3922
+ this.responseText += processedChunk;
3901
3923
  }
3902
3924
  }
3903
3925
  if (this.hooks.observers?.onStreamChunk && (processedChunk || chunk.usage)) {
@@ -3906,7 +3928,7 @@ var init_stream_processor = __esm({
3906
3928
  const context = {
3907
3929
  iteration: this.iteration,
3908
3930
  rawChunk: processedChunk,
3909
- accumulatedText: this.accumulatedText,
3931
+ accumulatedText: this.responseText,
3910
3932
  usage,
3911
3933
  logger: this.logger
3912
3934
  };
@@ -3918,67 +3940,66 @@ var init_stream_processor = __esm({
3918
3940
  continue;
3919
3941
  }
3920
3942
  for (const event of this.parser.feed(processedChunk)) {
3921
- const processedEvents = await this.processEvent(event);
3922
- outputs.push(...processedEvents);
3923
- if (processedEvents.some((e) => e.type === "gadget_result")) {
3924
- didExecuteGadgets = true;
3925
- }
3926
- for (const evt of processedEvents) {
3927
- if (evt.type === "gadget_result" && evt.result.breaksLoop) {
3928
- shouldBreakLoop = true;
3943
+ for await (const processedEvent of this.processEventGenerator(event)) {
3944
+ yield processedEvent;
3945
+ if (processedEvent.type === "gadget_result") {
3946
+ didExecuteGadgets = true;
3947
+ if (processedEvent.result.breaksLoop) {
3948
+ shouldBreakLoop = true;
3949
+ }
3929
3950
  }
3930
3951
  }
3931
3952
  }
3932
- if (this.shouldStopExecution) {
3953
+ if (this.executionHalted) {
3933
3954
  this.logger.info("Breaking from LLM stream due to gadget error");
3934
3955
  break;
3935
3956
  }
3936
3957
  }
3937
- if (!this.shouldStopExecution) {
3958
+ if (!this.executionHalted) {
3938
3959
  for (const event of this.parser.finalize()) {
3939
- const processedEvents = await this.processEvent(event);
3940
- outputs.push(...processedEvents);
3941
- if (processedEvents.some((e) => e.type === "gadget_result")) {
3942
- didExecuteGadgets = true;
3943
- }
3944
- for (const evt of processedEvents) {
3945
- if (evt.type === "gadget_result" && evt.result.breaksLoop) {
3946
- shouldBreakLoop = true;
3960
+ for await (const processedEvent of this.processEventGenerator(event)) {
3961
+ yield processedEvent;
3962
+ if (processedEvent.type === "gadget_result") {
3963
+ didExecuteGadgets = true;
3964
+ if (processedEvent.result.breaksLoop) {
3965
+ shouldBreakLoop = true;
3966
+ }
3947
3967
  }
3948
3968
  }
3949
3969
  }
3950
- const finalPendingEvents = await this.processPendingGadgets();
3951
- outputs.push(...finalPendingEvents);
3952
- if (finalPendingEvents.some((e) => e.type === "gadget_result")) {
3953
- didExecuteGadgets = true;
3954
- }
3955
- for (const evt of finalPendingEvents) {
3956
- if (evt.type === "gadget_result" && evt.result.breaksLoop) {
3957
- shouldBreakLoop = true;
3970
+ for await (const evt of this.processPendingGadgetsGenerator()) {
3971
+ yield evt;
3972
+ if (evt.type === "gadget_result") {
3973
+ didExecuteGadgets = true;
3974
+ if (evt.result.breaksLoop) {
3975
+ shouldBreakLoop = true;
3976
+ }
3958
3977
  }
3959
3978
  }
3960
3979
  }
3961
- let finalMessage = this.accumulatedText;
3980
+ let finalMessage = this.responseText;
3962
3981
  if (this.hooks.interceptors?.interceptAssistantMessage) {
3963
3982
  const context = {
3964
3983
  iteration: this.iteration,
3965
- rawResponse: this.accumulatedText,
3984
+ rawResponse: this.responseText,
3966
3985
  logger: this.logger
3967
3986
  };
3968
3987
  finalMessage = this.hooks.interceptors.interceptAssistantMessage(finalMessage, context);
3969
3988
  }
3970
- return {
3971
- outputs,
3989
+ const completionEvent = {
3990
+ type: "stream_complete",
3972
3991
  shouldBreakLoop,
3973
3992
  didExecuteGadgets,
3974
3993
  finishReason,
3975
3994
  usage,
3976
- rawResponse: this.accumulatedText,
3995
+ rawResponse: this.responseText,
3977
3996
  finalMessage
3978
3997
  };
3998
+ yield completionEvent;
3979
3999
  }
3980
4000
  /**
3981
4001
  * Process a single parsed event (text or gadget call).
4002
+ * @deprecated Use processEventGenerator for real-time streaming
3982
4003
  */
3983
4004
  async processEvent(event) {
3984
4005
  if (event.type === "text") {
@@ -3988,6 +4009,23 @@ var init_stream_processor = __esm({
3988
4009
  }
3989
4010
  return [event];
3990
4011
  }
4012
+ /**
4013
+ * Process a single parsed event, yielding events in real-time.
4014
+ * Generator version of processEvent for streaming support.
4015
+ */
4016
+ async *processEventGenerator(event) {
4017
+ if (event.type === "text") {
4018
+ for (const e of await this.processTextEvent(event)) {
4019
+ yield e;
4020
+ }
4021
+ } else if (event.type === "gadget_call") {
4022
+ for await (const e of this.processGadgetCallGenerator(event.call)) {
4023
+ yield e;
4024
+ }
4025
+ } else {
4026
+ yield event;
4027
+ }
4028
+ }
3991
4029
  /**
3992
4030
  * Process a text event through interceptors.
3993
4031
  */
@@ -3996,7 +4034,7 @@ var init_stream_processor = __esm({
3996
4034
  if (this.hooks.interceptors?.interceptTextChunk) {
3997
4035
  const context = {
3998
4036
  iteration: this.iteration,
3999
- accumulatedText: this.accumulatedText,
4037
+ accumulatedText: this.responseText,
4000
4038
  logger: this.logger
4001
4039
  };
4002
4040
  const intercepted = this.hooks.interceptors.interceptTextChunk(content, context);
@@ -4015,7 +4053,7 @@ var init_stream_processor = __esm({
4015
4053
  * After each execution, pending gadgets are checked to see if they can now run.
4016
4054
  */
4017
4055
  async processGadgetCall(call) {
4018
- if (this.shouldStopExecution) {
4056
+ if (this.executionHalted) {
4019
4057
  this.logger.debug("Skipping gadget execution due to previous error", {
4020
4058
  gadgetName: call.gadgetName
4021
4059
  });
@@ -4054,7 +4092,7 @@ var init_stream_processor = __esm({
4054
4092
  invocationId: call.invocationId,
4055
4093
  waitingOn: unsatisfied
4056
4094
  });
4057
- this.pendingGadgets.set(call.invocationId, call);
4095
+ this.gadgetsAwaitingDependencies.set(call.invocationId, call);
4058
4096
  return events;
4059
4097
  }
4060
4098
  }
@@ -4064,9 +4102,68 @@ var init_stream_processor = __esm({
4064
4102
  events.push(...triggeredEvents);
4065
4103
  return events;
4066
4104
  }
4105
+ /**
4106
+ * Process a gadget call, yielding events in real-time.
4107
+ *
4108
+ * Key difference from processGadgetCall: yields gadget_call event IMMEDIATELY
4109
+ * when parsed (before execution), enabling real-time UI feedback.
4110
+ */
4111
+ async *processGadgetCallGenerator(call) {
4112
+ if (this.executionHalted) {
4113
+ this.logger.debug("Skipping gadget execution due to previous error", {
4114
+ gadgetName: call.gadgetName
4115
+ });
4116
+ return;
4117
+ }
4118
+ yield { type: "gadget_call", call };
4119
+ if (call.dependencies.length > 0) {
4120
+ if (call.dependencies.includes(call.invocationId)) {
4121
+ this.logger.warn("Gadget has self-referential dependency (depends on itself)", {
4122
+ gadgetName: call.gadgetName,
4123
+ invocationId: call.invocationId
4124
+ });
4125
+ this.failedInvocations.add(call.invocationId);
4126
+ const skipEvent = {
4127
+ type: "gadget_skipped",
4128
+ gadgetName: call.gadgetName,
4129
+ invocationId: call.invocationId,
4130
+ parameters: call.parameters ?? {},
4131
+ failedDependency: call.invocationId,
4132
+ failedDependencyError: `Gadget "${call.invocationId}" cannot depend on itself (self-referential dependency)`
4133
+ };
4134
+ yield skipEvent;
4135
+ return;
4136
+ }
4137
+ const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
4138
+ if (failedDep) {
4139
+ const skipEvents = await this.handleFailedDependency(call, failedDep);
4140
+ for (const evt of skipEvents) {
4141
+ yield evt;
4142
+ }
4143
+ return;
4144
+ }
4145
+ const unsatisfied = call.dependencies.filter((dep) => !this.completedResults.has(dep));
4146
+ if (unsatisfied.length > 0) {
4147
+ this.logger.debug("Queueing gadget for later - waiting on dependencies", {
4148
+ gadgetName: call.gadgetName,
4149
+ invocationId: call.invocationId,
4150
+ waitingOn: unsatisfied
4151
+ });
4152
+ this.gadgetsAwaitingDependencies.set(call.invocationId, call);
4153
+ return;
4154
+ }
4155
+ }
4156
+ for await (const evt of this.executeGadgetGenerator(call)) {
4157
+ yield evt;
4158
+ }
4159
+ for await (const evt of this.processPendingGadgetsGenerator()) {
4160
+ yield evt;
4161
+ }
4162
+ }
4067
4163
  /**
4068
4164
  * Execute a gadget through the full hook lifecycle.
4069
4165
  * This is the core execution logic, extracted from processGadgetCall.
4166
+ * @deprecated Use executeGadgetGenerator for real-time streaming
4070
4167
  */
4071
4168
  async executeGadgetWithHooks(call) {
4072
4169
  const events = [];
@@ -4076,14 +4173,14 @@ var init_stream_processor = __esm({
4076
4173
  error: call.parseError,
4077
4174
  rawParameters: call.parametersRaw
4078
4175
  });
4079
- const shouldContinue = await this.checkContinueAfterError(
4176
+ const shouldContinue = await this.checkCanRecoverFromError(
4080
4177
  call.parseError,
4081
4178
  call.gadgetName,
4082
4179
  "parse",
4083
4180
  call.parameters
4084
4181
  );
4085
4182
  if (!shouldContinue) {
4086
- this.shouldStopExecution = true;
4183
+ this.executionHalted = true;
4087
4184
  }
4088
4185
  }
4089
4186
  let parameters = call.parameters ?? {};
@@ -4207,18 +4304,171 @@ var init_stream_processor = __esm({
4207
4304
  events.push({ type: "gadget_result", result });
4208
4305
  if (result.error) {
4209
4306
  const errorType = this.determineErrorType(call, result);
4210
- const shouldContinue = await this.checkContinueAfterError(
4307
+ const shouldContinue = await this.checkCanRecoverFromError(
4211
4308
  result.error,
4212
4309
  result.gadgetName,
4213
4310
  errorType,
4214
4311
  result.parameters
4215
4312
  );
4216
4313
  if (!shouldContinue) {
4217
- this.shouldStopExecution = true;
4314
+ this.executionHalted = true;
4218
4315
  }
4219
4316
  }
4220
4317
  return events;
4221
4318
  }
4319
+ /**
4320
+ * Execute a gadget and yield the result event.
4321
+ * Generator version that yields gadget_result immediately when execution completes.
4322
+ */
4323
+ async *executeGadgetGenerator(call) {
4324
+ if (call.parseError) {
4325
+ this.logger.warn("Gadget has parse error", {
4326
+ gadgetName: call.gadgetName,
4327
+ error: call.parseError,
4328
+ rawParameters: call.parametersRaw
4329
+ });
4330
+ const shouldContinue = await this.checkCanRecoverFromError(
4331
+ call.parseError,
4332
+ call.gadgetName,
4333
+ "parse",
4334
+ call.parameters
4335
+ );
4336
+ if (!shouldContinue) {
4337
+ this.executionHalted = true;
4338
+ }
4339
+ }
4340
+ let parameters = call.parameters ?? {};
4341
+ if (this.hooks.interceptors?.interceptGadgetParameters) {
4342
+ const context = {
4343
+ iteration: this.iteration,
4344
+ gadgetName: call.gadgetName,
4345
+ invocationId: call.invocationId,
4346
+ logger: this.logger
4347
+ };
4348
+ parameters = this.hooks.interceptors.interceptGadgetParameters(parameters, context);
4349
+ }
4350
+ call.parameters = parameters;
4351
+ let shouldSkip = false;
4352
+ let syntheticResult;
4353
+ if (this.hooks.controllers?.beforeGadgetExecution) {
4354
+ const context = {
4355
+ iteration: this.iteration,
4356
+ gadgetName: call.gadgetName,
4357
+ invocationId: call.invocationId,
4358
+ parameters,
4359
+ logger: this.logger
4360
+ };
4361
+ const action = await this.hooks.controllers.beforeGadgetExecution(context);
4362
+ validateBeforeGadgetExecutionAction(action);
4363
+ if (action.action === "skip") {
4364
+ shouldSkip = true;
4365
+ syntheticResult = action.syntheticResult;
4366
+ this.logger.info("Controller skipped gadget execution", {
4367
+ gadgetName: call.gadgetName
4368
+ });
4369
+ }
4370
+ }
4371
+ const startObservers = [];
4372
+ if (this.hooks.observers?.onGadgetExecutionStart) {
4373
+ startObservers.push(async () => {
4374
+ const context = {
4375
+ iteration: this.iteration,
4376
+ gadgetName: call.gadgetName,
4377
+ invocationId: call.invocationId,
4378
+ parameters,
4379
+ logger: this.logger
4380
+ };
4381
+ await this.hooks.observers.onGadgetExecutionStart(context);
4382
+ });
4383
+ }
4384
+ await this.runObserversInParallel(startObservers);
4385
+ let result;
4386
+ if (shouldSkip) {
4387
+ result = {
4388
+ gadgetName: call.gadgetName,
4389
+ invocationId: call.invocationId,
4390
+ parameters,
4391
+ result: syntheticResult ?? "Execution skipped",
4392
+ executionTimeMs: 0
4393
+ };
4394
+ } else {
4395
+ result = await this.executor.execute(call);
4396
+ }
4397
+ const originalResult = result.result;
4398
+ if (result.result && this.hooks.interceptors?.interceptGadgetResult) {
4399
+ const context = {
4400
+ iteration: this.iteration,
4401
+ gadgetName: result.gadgetName,
4402
+ invocationId: result.invocationId,
4403
+ parameters,
4404
+ executionTimeMs: result.executionTimeMs,
4405
+ logger: this.logger
4406
+ };
4407
+ result.result = this.hooks.interceptors.interceptGadgetResult(result.result, context);
4408
+ }
4409
+ if (this.hooks.controllers?.afterGadgetExecution) {
4410
+ const context = {
4411
+ iteration: this.iteration,
4412
+ gadgetName: result.gadgetName,
4413
+ invocationId: result.invocationId,
4414
+ parameters,
4415
+ result: result.result,
4416
+ error: result.error,
4417
+ executionTimeMs: result.executionTimeMs,
4418
+ logger: this.logger
4419
+ };
4420
+ const action = await this.hooks.controllers.afterGadgetExecution(context);
4421
+ validateAfterGadgetExecutionAction(action);
4422
+ if (action.action === "recover" && result.error) {
4423
+ this.logger.info("Controller recovered from gadget error", {
4424
+ gadgetName: result.gadgetName,
4425
+ originalError: result.error
4426
+ });
4427
+ result = {
4428
+ ...result,
4429
+ error: void 0,
4430
+ result: action.fallbackResult
4431
+ };
4432
+ }
4433
+ }
4434
+ const completeObservers = [];
4435
+ if (this.hooks.observers?.onGadgetExecutionComplete) {
4436
+ completeObservers.push(async () => {
4437
+ const context = {
4438
+ iteration: this.iteration,
4439
+ gadgetName: result.gadgetName,
4440
+ invocationId: result.invocationId,
4441
+ parameters,
4442
+ originalResult,
4443
+ finalResult: result.result,
4444
+ error: result.error,
4445
+ executionTimeMs: result.executionTimeMs,
4446
+ breaksLoop: result.breaksLoop,
4447
+ cost: result.cost,
4448
+ logger: this.logger
4449
+ };
4450
+ await this.hooks.observers.onGadgetExecutionComplete(context);
4451
+ });
4452
+ }
4453
+ await this.runObserversInParallel(completeObservers);
4454
+ this.completedResults.set(result.invocationId, result);
4455
+ if (result.error) {
4456
+ this.failedInvocations.add(result.invocationId);
4457
+ }
4458
+ yield { type: "gadget_result", result };
4459
+ if (result.error) {
4460
+ const errorType = this.determineErrorType(call, result);
4461
+ const shouldContinue = await this.checkCanRecoverFromError(
4462
+ result.error,
4463
+ result.gadgetName,
4464
+ errorType,
4465
+ result.parameters
4466
+ );
4467
+ if (!shouldContinue) {
4468
+ this.executionHalted = true;
4469
+ }
4470
+ }
4471
+ }
4222
4472
  /**
4223
4473
  * Handle a gadget that cannot execute because a dependency failed.
4224
4474
  * Calls the onDependencySkipped controller to allow customization.
@@ -4301,11 +4551,11 @@ var init_stream_processor = __esm({
4301
4551
  async processPendingGadgets() {
4302
4552
  const events = [];
4303
4553
  let progress = true;
4304
- while (progress && this.pendingGadgets.size > 0) {
4554
+ while (progress && this.gadgetsAwaitingDependencies.size > 0) {
4305
4555
  progress = false;
4306
4556
  const readyToExecute = [];
4307
4557
  const readyToSkip = [];
4308
- for (const [invocationId, call] of this.pendingGadgets) {
4558
+ for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
4309
4559
  const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
4310
4560
  if (failedDep) {
4311
4561
  readyToSkip.push({ call, failedDep });
@@ -4317,7 +4567,7 @@ var init_stream_processor = __esm({
4317
4567
  }
4318
4568
  }
4319
4569
  for (const { call, failedDep } of readyToSkip) {
4320
- this.pendingGadgets.delete(call.invocationId);
4570
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4321
4571
  const skipEvents = await this.handleFailedDependency(call, failedDep);
4322
4572
  events.push(...skipEvents);
4323
4573
  progress = true;
@@ -4328,7 +4578,7 @@ var init_stream_processor = __esm({
4328
4578
  invocationIds: readyToExecute.map((c) => c.invocationId)
4329
4579
  });
4330
4580
  for (const call of readyToExecute) {
4331
- this.pendingGadgets.delete(call.invocationId);
4581
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4332
4582
  }
4333
4583
  const executePromises = readyToExecute.map((call) => this.executeGadgetWithHooks(call));
4334
4584
  const results = await Promise.all(executePromises);
@@ -4338,9 +4588,9 @@ var init_stream_processor = __esm({
4338
4588
  progress = true;
4339
4589
  }
4340
4590
  }
4341
- if (this.pendingGadgets.size > 0) {
4342
- const pendingIds = new Set(this.pendingGadgets.keys());
4343
- for (const [invocationId, call] of this.pendingGadgets) {
4591
+ if (this.gadgetsAwaitingDependencies.size > 0) {
4592
+ const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
4593
+ for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
4344
4594
  const missingDeps = call.dependencies.filter((dep) => !this.completedResults.has(dep));
4345
4595
  const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
4346
4596
  const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
@@ -4371,10 +4621,103 @@ var init_stream_processor = __esm({
4371
4621
  };
4372
4622
  events.push(skipEvent);
4373
4623
  }
4374
- this.pendingGadgets.clear();
4624
+ this.gadgetsAwaitingDependencies.clear();
4375
4625
  }
4376
4626
  return events;
4377
4627
  }
4628
+ /**
4629
+ * Process pending gadgets, yielding events in real-time.
4630
+ * Generator version that yields events as gadgets complete.
4631
+ *
4632
+ * Note: Gadgets are still executed in parallel for efficiency,
4633
+ * but results are yielded as they become available.
4634
+ */
4635
+ async *processPendingGadgetsGenerator() {
4636
+ let progress = true;
4637
+ while (progress && this.gadgetsAwaitingDependencies.size > 0) {
4638
+ progress = false;
4639
+ const readyToExecute = [];
4640
+ const readyToSkip = [];
4641
+ for (const [_invocationId, call] of this.gadgetsAwaitingDependencies) {
4642
+ const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
4643
+ if (failedDep) {
4644
+ readyToSkip.push({ call, failedDep });
4645
+ continue;
4646
+ }
4647
+ const allSatisfied = call.dependencies.every((dep) => this.completedResults.has(dep));
4648
+ if (allSatisfied) {
4649
+ readyToExecute.push(call);
4650
+ }
4651
+ }
4652
+ for (const { call, failedDep } of readyToSkip) {
4653
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4654
+ const skipEvents = await this.handleFailedDependency(call, failedDep);
4655
+ for (const evt of skipEvents) {
4656
+ yield evt;
4657
+ }
4658
+ progress = true;
4659
+ }
4660
+ if (readyToExecute.length > 0) {
4661
+ this.logger.debug("Executing ready gadgets in parallel", {
4662
+ count: readyToExecute.length,
4663
+ invocationIds: readyToExecute.map((c) => c.invocationId)
4664
+ });
4665
+ for (const call of readyToExecute) {
4666
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4667
+ }
4668
+ const eventSets = await Promise.all(
4669
+ readyToExecute.map(async (call) => {
4670
+ const events = [];
4671
+ for await (const evt of this.executeGadgetGenerator(call)) {
4672
+ events.push(evt);
4673
+ }
4674
+ return events;
4675
+ })
4676
+ );
4677
+ for (const events of eventSets) {
4678
+ for (const evt of events) {
4679
+ yield evt;
4680
+ }
4681
+ }
4682
+ progress = true;
4683
+ }
4684
+ }
4685
+ if (this.gadgetsAwaitingDependencies.size > 0) {
4686
+ const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
4687
+ for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
4688
+ const missingDeps = call.dependencies.filter((dep) => !this.completedResults.has(dep));
4689
+ const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
4690
+ const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
4691
+ let errorMessage;
4692
+ let logLevel = "warn";
4693
+ if (circularDeps.length > 0 && trulyMissingDeps.length > 0) {
4694
+ errorMessage = `Dependencies unresolvable: circular=[${circularDeps.join(", ")}], missing=[${trulyMissingDeps.join(", ")}]`;
4695
+ logLevel = "error";
4696
+ } else if (circularDeps.length > 0) {
4697
+ errorMessage = `Circular dependency detected: "${invocationId}" depends on "${circularDeps[0]}" which also depends on "${invocationId}" (directly or indirectly)`;
4698
+ } else {
4699
+ errorMessage = `Dependency "${missingDeps[0]}" was never executed - check that the invocation ID exists and is spelled correctly`;
4700
+ }
4701
+ this.logger[logLevel]("Gadget has unresolvable dependencies", {
4702
+ gadgetName: call.gadgetName,
4703
+ invocationId,
4704
+ circularDependencies: circularDeps,
4705
+ missingDependencies: trulyMissingDeps
4706
+ });
4707
+ this.failedInvocations.add(invocationId);
4708
+ const skipEvent = {
4709
+ type: "gadget_skipped",
4710
+ gadgetName: call.gadgetName,
4711
+ invocationId,
4712
+ parameters: call.parameters ?? {},
4713
+ failedDependency: missingDeps[0],
4714
+ failedDependencyError: errorMessage
4715
+ };
4716
+ yield skipEvent;
4717
+ }
4718
+ this.gadgetsAwaitingDependencies.clear();
4719
+ }
4720
+ }
4378
4721
  /**
4379
4722
  * Safely execute an observer, catching and logging any errors.
4380
4723
  * Observers are non-critical, so errors are logged but don't crash the system.
@@ -4401,19 +4744,19 @@ var init_stream_processor = __esm({
4401
4744
  );
4402
4745
  }
4403
4746
  /**
4404
- * Check if execution should continue after an error.
4747
+ * Check if execution can recover from an error.
4405
4748
  *
4406
4749
  * Returns true if we should continue processing subsequent gadgets, false if we should stop.
4407
4750
  *
4408
4751
  * Logic:
4409
- * - If custom shouldContinueAfterError is provided, use it
4752
+ * - If custom canRecoverFromGadgetError is provided, use it
4410
4753
  * - Otherwise, use stopOnGadgetError config:
4411
4754
  * - stopOnGadgetError=true → return false (stop execution)
4412
4755
  * - stopOnGadgetError=false → return true (continue execution)
4413
4756
  */
4414
- async checkContinueAfterError(error, gadgetName, errorType, parameters) {
4415
- if (this.shouldContinueAfterError) {
4416
- return await this.shouldContinueAfterError({
4757
+ async checkCanRecoverFromError(error, gadgetName, errorType, parameters) {
4758
+ if (this.canRecoverFromGadgetError) {
4759
+ return await this.canRecoverFromGadgetError({
4417
4760
  error,
4418
4761
  gadgetName,
4419
4762
  errorType,
@@ -4476,14 +4819,14 @@ var init_agent = __esm({
4476
4819
  gadgetStartPrefix;
4477
4820
  gadgetEndPrefix;
4478
4821
  gadgetArgPrefix;
4479
- onHumanInputRequired;
4822
+ requestHumanInput;
4480
4823
  textOnlyHandler;
4481
4824
  textWithGadgetsHandler;
4482
4825
  stopOnGadgetError;
4483
- shouldContinueAfterError;
4826
+ canRecoverFromGadgetError;
4484
4827
  defaultGadgetTimeoutMs;
4485
4828
  defaultMaxTokens;
4486
- userPromptProvided;
4829
+ hasUserPrompt;
4487
4830
  // Gadget output limiting
4488
4831
  outputStore;
4489
4832
  outputLimitEnabled;
@@ -4494,6 +4837,11 @@ var init_agent = __esm({
4494
4837
  mediaStore;
4495
4838
  // Cancellation
4496
4839
  signal;
4840
+ // Subagent configuration
4841
+ agentContextConfig;
4842
+ subagentConfig;
4843
+ // Nested event callback for subagent gadgets
4844
+ onNestedEvent;
4497
4845
  /**
4498
4846
  * Creates a new Agent instance.
4499
4847
  * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
@@ -4513,11 +4861,11 @@ var init_agent = __esm({
4513
4861
  this.gadgetStartPrefix = options.gadgetStartPrefix;
4514
4862
  this.gadgetEndPrefix = options.gadgetEndPrefix;
4515
4863
  this.gadgetArgPrefix = options.gadgetArgPrefix;
4516
- this.onHumanInputRequired = options.onHumanInputRequired;
4864
+ this.requestHumanInput = options.requestHumanInput;
4517
4865
  this.textOnlyHandler = options.textOnlyHandler ?? "terminate";
4518
4866
  this.textWithGadgetsHandler = options.textWithGadgetsHandler;
4519
4867
  this.stopOnGadgetError = options.stopOnGadgetError ?? true;
4520
- this.shouldContinueAfterError = options.shouldContinueAfterError;
4868
+ this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
4521
4869
  this.defaultGadgetTimeoutMs = options.defaultGadgetTimeoutMs;
4522
4870
  this.defaultMaxTokens = this.resolveMaxTokensFromCatalog(options.model);
4523
4871
  this.outputLimitEnabled = options.gadgetOutputLimit ?? DEFAULT_GADGET_OUTPUT_LIMIT;
@@ -4533,7 +4881,7 @@ var init_agent = __esm({
4533
4881
  createGadgetOutputViewer(this.outputStore, this.outputLimitCharLimit)
4534
4882
  );
4535
4883
  }
4536
- this.hooks = this.mergeOutputLimiterHook(options.hooks);
4884
+ this.hooks = this.chainOutputLimiterWithUserHooks(options.hooks);
4537
4885
  const baseBuilder = new LLMMessageBuilder(options.promptConfig);
4538
4886
  if (options.systemPrompt) {
4539
4887
  baseBuilder.addSystem(options.systemPrompt);
@@ -4553,7 +4901,7 @@ var init_agent = __esm({
4553
4901
  endPrefix: options.gadgetEndPrefix,
4554
4902
  argPrefix: options.gadgetArgPrefix
4555
4903
  });
4556
- this.userPromptProvided = !!options.userPrompt;
4904
+ this.hasUserPrompt = !!options.userPrompt;
4557
4905
  if (options.userPrompt) {
4558
4906
  this.conversation.addUserMessage(options.userPrompt);
4559
4907
  }
@@ -4566,6 +4914,12 @@ var init_agent = __esm({
4566
4914
  );
4567
4915
  }
4568
4916
  this.signal = options.signal;
4917
+ this.agentContextConfig = {
4918
+ model: this.model,
4919
+ temperature: this.temperature
4920
+ };
4921
+ this.subagentConfig = options.subagentConfig;
4922
+ this.onNestedEvent = options.onNestedEvent;
4569
4923
  }
4570
4924
  /**
4571
4925
  * Get the gadget registry for this agent.
@@ -4672,7 +5026,7 @@ var init_agent = __esm({
4672
5026
  * @throws {Error} If no user prompt was provided (when using build() without ask())
4673
5027
  */
4674
5028
  async *run() {
4675
- if (!this.userPromptProvided) {
5029
+ if (!this.hasUserPrompt) {
4676
5030
  throw new Error(
4677
5031
  "No user prompt provided. Use .ask(prompt) instead of .build(), or call agent.run() after providing a prompt."
4678
5032
  );
@@ -4789,17 +5143,37 @@ var init_agent = __esm({
4789
5143
  gadgetArgPrefix: this.gadgetArgPrefix,
4790
5144
  hooks: this.hooks,
4791
5145
  logger: this.logger.getSubLogger({ name: "stream-processor" }),
4792
- onHumanInputRequired: this.onHumanInputRequired,
5146
+ requestHumanInput: this.requestHumanInput,
4793
5147
  stopOnGadgetError: this.stopOnGadgetError,
4794
- shouldContinueAfterError: this.shouldContinueAfterError,
5148
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
4795
5149
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
4796
5150
  client: this.client,
4797
- mediaStore: this.mediaStore
5151
+ mediaStore: this.mediaStore,
5152
+ agentConfig: this.agentContextConfig,
5153
+ subagentConfig: this.subagentConfig,
5154
+ onNestedEvent: this.onNestedEvent
4798
5155
  });
4799
- const result = await processor.process(stream2);
4800
- for (const output of result.outputs) {
4801
- yield output;
5156
+ let streamMetadata = null;
5157
+ let gadgetCallCount = 0;
5158
+ const textOutputs = [];
5159
+ const gadgetResults = [];
5160
+ for await (const event of processor.process(stream2)) {
5161
+ if (event.type === "stream_complete") {
5162
+ streamMetadata = event;
5163
+ continue;
5164
+ }
5165
+ if (event.type === "text") {
5166
+ textOutputs.push(event.content);
5167
+ } else if (event.type === "gadget_result") {
5168
+ gadgetCallCount++;
5169
+ gadgetResults.push(event);
5170
+ }
5171
+ yield event;
4802
5172
  }
5173
+ if (!streamMetadata) {
5174
+ throw new Error("Stream processing completed without metadata event");
5175
+ }
5176
+ const result = streamMetadata;
4803
5177
  this.logger.info("LLM response completed", {
4804
5178
  finishReason: result.finishReason,
4805
5179
  usage: result.usage,
@@ -4824,9 +5198,6 @@ var init_agent = __esm({
4824
5198
  });
4825
5199
  let finalMessage = result.finalMessage;
4826
5200
  if (this.hooks.controllers?.afterLLMCall) {
4827
- const gadgetCallCount = result.outputs.filter(
4828
- (output) => output.type === "gadget_result"
4829
- ).length;
4830
5201
  const context = {
4831
5202
  iteration: currentIteration,
4832
5203
  maxIterations: this.maxIterations,
@@ -4847,31 +5218,29 @@ var init_agent = __esm({
4847
5218
  if (msg.role === "user") {
4848
5219
  this.conversation.addUserMessage(msg.content);
4849
5220
  } else if (msg.role === "assistant") {
4850
- this.conversation.addAssistantMessage(extractText(msg.content));
5221
+ this.conversation.addAssistantMessage(extractMessageText(msg.content));
4851
5222
  } else if (msg.role === "system") {
4852
- this.conversation.addUserMessage(`[System] ${extractText(msg.content)}`);
5223
+ this.conversation.addUserMessage(`[System] ${extractMessageText(msg.content)}`);
4853
5224
  }
4854
5225
  }
4855
5226
  }
4856
5227
  }
4857
5228
  if (result.didExecuteGadgets) {
4858
5229
  if (this.textWithGadgetsHandler) {
4859
- const textContent = result.outputs.filter(
4860
- (output) => output.type === "text"
4861
- ).map((output) => output.content).join("");
5230
+ const textContent = textOutputs.join("");
4862
5231
  if (textContent.trim()) {
4863
5232
  const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
4864
- this.conversation.addGadgetCall(
5233
+ this.conversation.addGadgetCallResult(
4865
5234
  gadgetName,
4866
5235
  parameterMapping(textContent),
4867
5236
  resultMapping ? resultMapping(textContent) : textContent
4868
5237
  );
4869
5238
  }
4870
5239
  }
4871
- for (const output of result.outputs) {
5240
+ for (const output of gadgetResults) {
4872
5241
  if (output.type === "gadget_result") {
4873
5242
  const gadgetResult = output.result;
4874
- this.conversation.addGadgetCall(
5243
+ this.conversation.addGadgetCallResult(
4875
5244
  gadgetResult.gadgetName,
4876
5245
  gadgetResult.parameters,
4877
5246
  gadgetResult.error ?? gadgetResult.result ?? "",
@@ -4882,7 +5251,7 @@ var init_agent = __esm({
4882
5251
  }
4883
5252
  } else {
4884
5253
  if (finalMessage.trim()) {
4885
- this.conversation.addGadgetCall(
5254
+ this.conversation.addGadgetCallResult(
4886
5255
  "TellUser",
4887
5256
  { message: finalMessage, done: false, type: "info" },
4888
5257
  `\u2139\uFE0F ${finalMessage}`
@@ -5008,10 +5377,10 @@ var init_agent = __esm({
5008
5377
  return this.client.modelRegistry.getModelLimits(unprefixedModelId)?.maxOutputTokens;
5009
5378
  }
5010
5379
  /**
5011
- * Merge the output limiter interceptor into user-provided hooks.
5380
+ * Chain the output limiter interceptor with user-provided hooks.
5012
5381
  * The limiter runs first, then chains to any user interceptor.
5013
5382
  */
5014
- mergeOutputLimiterHook(userHooks) {
5383
+ chainOutputLimiterWithUserHooks(userHooks) {
5015
5384
  if (!this.outputLimitEnabled) {
5016
5385
  return userHooks ?? {};
5017
5386
  }
@@ -5090,20 +5459,23 @@ var init_builder = __esm({
5090
5459
  promptConfig;
5091
5460
  gadgets = [];
5092
5461
  initialMessages = [];
5093
- onHumanInputRequired;
5462
+ requestHumanInput;
5094
5463
  gadgetStartPrefix;
5095
5464
  gadgetEndPrefix;
5096
5465
  gadgetArgPrefix;
5097
5466
  textOnlyHandler;
5098
5467
  textWithGadgetsHandler;
5099
5468
  stopOnGadgetError;
5100
- shouldContinueAfterError;
5469
+ canRecoverFromGadgetError;
5101
5470
  defaultGadgetTimeoutMs;
5102
5471
  gadgetOutputLimit;
5103
5472
  gadgetOutputLimitPercent;
5104
5473
  compactionConfig;
5105
5474
  signal;
5106
5475
  trailingMessage;
5476
+ subagentConfig;
5477
+ nestedEventCallback;
5478
+ parentContext;
5107
5479
  constructor(client) {
5108
5480
  this.client = client;
5109
5481
  }
@@ -5194,13 +5566,13 @@ var init_builder = __esm({
5194
5566
  *
5195
5567
  * @example
5196
5568
  * ```typescript
5197
- * .withPromptConfig({
5569
+ * .withPromptTemplateConfig({
5198
5570
  * mainInstruction: "Use the gadget markers below:",
5199
5571
  * rules: ["Always use markers", "Never use function calling"]
5200
5572
  * })
5201
5573
  * ```
5202
5574
  */
5203
- withPromptConfig(config) {
5575
+ withPromptTemplateConfig(config) {
5204
5576
  this.promptConfig = config;
5205
5577
  return this;
5206
5578
  }
@@ -5280,7 +5652,7 @@ var init_builder = __esm({
5280
5652
  * ```
5281
5653
  */
5282
5654
  onHumanInput(handler) {
5283
- this.onHumanInputRequired = handler;
5655
+ this.requestHumanInput = handler;
5284
5656
  return this;
5285
5657
  }
5286
5658
  /**
@@ -5415,9 +5787,9 @@ var init_builder = __esm({
5415
5787
  * Provides fine-grained control over whether to continue after different types of errors.
5416
5788
  * Overrides `stopOnGadgetError` when provided.
5417
5789
  *
5418
- * **Note:** This builder method configures the underlying `shouldContinueAfterError` option
5790
+ * **Note:** This builder method configures the underlying `canRecoverFromGadgetError` option
5419
5791
  * in `AgentOptions`. The method is named `withErrorHandler` for better developer experience,
5420
- * but maps to the `shouldContinueAfterError` property internally.
5792
+ * but maps to the `canRecoverFromGadgetError` property internally.
5421
5793
  *
5422
5794
  * @param handler - Function that decides whether to continue after an error.
5423
5795
  * Return `true` to continue execution, `false` to stop.
@@ -5438,7 +5810,7 @@ var init_builder = __esm({
5438
5810
  * ```
5439
5811
  */
5440
5812
  withErrorHandler(handler) {
5441
- this.shouldContinueAfterError = handler;
5813
+ this.canRecoverFromGadgetError = handler;
5442
5814
  return this;
5443
5815
  }
5444
5816
  /**
@@ -5579,6 +5951,95 @@ var init_builder = __esm({
5579
5951
  this.signal = signal;
5580
5952
  return this;
5581
5953
  }
5954
+ /**
5955
+ * Set subagent configuration overrides.
5956
+ *
5957
+ * Subagent gadgets (like BrowseWeb) can read these settings from ExecutionContext
5958
+ * to inherit model and other options from the CLI configuration.
5959
+ *
5960
+ * @param config - Subagent configuration map keyed by gadget name
5961
+ * @returns This builder for chaining
5962
+ *
5963
+ * @example
5964
+ * ```typescript
5965
+ * .withSubagentConfig({
5966
+ * BrowseWeb: { model: "inherit", maxIterations: 20, headless: true },
5967
+ * CodeAnalyzer: { model: "sonnet", maxIterations: 10 }
5968
+ * })
5969
+ * ```
5970
+ */
5971
+ withSubagentConfig(config) {
5972
+ this.subagentConfig = config;
5973
+ return this;
5974
+ }
5975
+ /**
5976
+ * Set the callback for nested subagent events.
5977
+ *
5978
+ * Subagent gadgets (like BrowseWeb) can use ExecutionContext.onNestedEvent
5979
+ * to report their internal LLM calls and gadget executions in real-time.
5980
+ * This callback receives those events, enabling hierarchical progress display.
5981
+ *
5982
+ * @param callback - Function to handle nested agent events
5983
+ * @returns This builder for chaining
5984
+ *
5985
+ * @example
5986
+ * ```typescript
5987
+ * .withNestedEventCallback((event) => {
5988
+ * if (event.type === "llm_call_start") {
5989
+ * console.log(` Nested LLM #${event.event.iteration} starting...`);
5990
+ * } else if (event.type === "gadget_call") {
5991
+ * console.log(` ⏵ ${event.event.call.gadgetName}...`);
5992
+ * }
5993
+ * })
5994
+ * ```
5995
+ */
5996
+ withNestedEventCallback(callback) {
5997
+ this.nestedEventCallback = callback;
5998
+ return this;
5999
+ }
6000
+ /**
6001
+ * Enable automatic nested event forwarding to parent agent.
6002
+ *
6003
+ * When building a subagent inside a gadget, call this method to automatically
6004
+ * forward all LLM calls and gadget events to the parent agent. This enables
6005
+ * hierarchical progress display without any manual event handling.
6006
+ *
6007
+ * The method extracts `invocationId` and `onNestedEvent` from the execution
6008
+ * context and sets up automatic forwarding via hooks and event wrapping.
6009
+ *
6010
+ * @param ctx - ExecutionContext passed to the gadget's execute() method
6011
+ * @param depth - Nesting depth (default: 1 for direct child)
6012
+ * @returns This builder for chaining
6013
+ *
6014
+ * @example
6015
+ * ```typescript
6016
+ * // In a subagent gadget like BrowseWeb - ONE LINE enables auto-forwarding:
6017
+ * execute: async (params, ctx) => {
6018
+ * const agent = new AgentBuilder(client)
6019
+ * .withModel(model)
6020
+ * .withGadgets(Navigate, Click, Screenshot)
6021
+ * .withParentContext(ctx) // <-- This is all you need!
6022
+ * .ask(params.task);
6023
+ *
6024
+ * for await (const event of agent.run()) {
6025
+ * // Events automatically forwarded - just process normally
6026
+ * if (event.type === "text") {
6027
+ * result = event.content;
6028
+ * }
6029
+ * }
6030
+ * }
6031
+ * ```
6032
+ */
6033
+ withParentContext(ctx, depth = 1) {
6034
+ if (ctx.onNestedEvent && ctx.invocationId) {
6035
+ this.parentContext = {
6036
+ invocationId: ctx.invocationId,
6037
+ onNestedEvent: ctx.onNestedEvent,
6038
+ depth
6039
+ };
6040
+ }
6041
+ return this;
6042
+ }
5582
6043
  /**
5583
6044
  * Add an ephemeral trailing message that appears at the end of each LLM request.
5584
6045
  *
@@ -5646,14 +6107,58 @@ ${endPrefix}`
5646
6107
  return this;
5647
6108
  }
5648
6109
  /**
5649
- * Compose the final hooks, including trailing message if configured.
6110
+ * Compose the final hooks, including:
6111
+ * - Trailing message injection (if configured)
6112
+ * - Nested event forwarding for LLM calls (if parentContext is set)
5650
6113
  */
5651
6114
  composeHooks() {
6115
+ let hooks = this.hooks;
6116
+ if (this.parentContext) {
6117
+ const { invocationId, onNestedEvent, depth } = this.parentContext;
6118
+ const existingOnLLMCallStart = hooks?.observers?.onLLMCallStart;
6119
+ const existingOnLLMCallComplete = hooks?.observers?.onLLMCallComplete;
6120
+ hooks = {
6121
+ ...hooks,
6122
+ observers: {
6123
+ ...hooks?.observers,
6124
+ onLLMCallStart: async (context) => {
6125
+ onNestedEvent({
6126
+ type: "llm_call_start",
6127
+ gadgetInvocationId: invocationId,
6128
+ depth,
6129
+ event: {
6130
+ iteration: context.iteration,
6131
+ model: context.options.model
6132
+ }
6133
+ });
6134
+ if (existingOnLLMCallStart) {
6135
+ await existingOnLLMCallStart(context);
6136
+ }
6137
+ },
6138
+ onLLMCallComplete: async (context) => {
6139
+ onNestedEvent({
6140
+ type: "llm_call_end",
6141
+ gadgetInvocationId: invocationId,
6142
+ depth,
6143
+ event: {
6144
+ iteration: context.iteration,
6145
+ model: context.options.model,
6146
+ outputTokens: context.usage?.outputTokens,
6147
+ finishReason: context.finishReason
6148
+ }
6149
+ });
6150
+ if (existingOnLLMCallComplete) {
6151
+ await existingOnLLMCallComplete(context);
6152
+ }
6153
+ }
6154
+ }
6155
+ };
6156
+ }
5652
6157
  if (!this.trailingMessage) {
5653
- return this.hooks;
6158
+ return hooks;
5654
6159
  }
5655
6160
  const trailingMsg = this.trailingMessage;
5656
- const existingBeforeLLMCall = this.hooks?.controllers?.beforeLLMCall;
6161
+ const existingBeforeLLMCall = hooks?.controllers?.beforeLLMCall;
5657
6162
  const trailingMessageController = async (ctx) => {
5658
6163
  const result = existingBeforeLLMCall ? await existingBeforeLLMCall(ctx) : { action: "proceed" };
5659
6164
  if (result.action === "skip") {
@@ -5668,9 +6173,9 @@ ${endPrefix}`
5668
6173
  };
5669
6174
  };
5670
6175
  return {
5671
- ...this.hooks,
6176
+ ...hooks,
5672
6177
  controllers: {
5673
- ...this.hooks?.controllers,
6178
+ ...hooks?.controllers,
5674
6179
  beforeLLMCall: trailingMessageController
5675
6180
  }
5676
6181
  };
@@ -5731,6 +6236,19 @@ ${endPrefix}`
5731
6236
  this.client = new LLMistClass();
5732
6237
  }
5733
6238
  const registry = GadgetRegistry.from(this.gadgets);
6239
+ let onNestedEvent = this.nestedEventCallback;
6240
+ if (this.parentContext) {
6241
+ const { invocationId, onNestedEvent: parentCallback, depth } = this.parentContext;
6242
+ const existingCallback = this.nestedEventCallback;
6243
+ onNestedEvent = (event) => {
6244
+ parentCallback({
6245
+ ...event,
6246
+ gadgetInvocationId: invocationId,
6247
+ depth: event.depth + depth
6248
+ });
6249
+ existingCallback?.(event);
6250
+ };
6251
+ }
5734
6252
  return {
5735
6253
  client: this.client,
5736
6254
  model: this.model ?? "openai:gpt-5-nano",
@@ -5743,19 +6261,21 @@ ${endPrefix}`
5743
6261
  hooks: this.composeHooks(),
5744
6262
  promptConfig: this.promptConfig,
5745
6263
  initialMessages: this.initialMessages,
5746
- onHumanInputRequired: this.onHumanInputRequired,
6264
+ requestHumanInput: this.requestHumanInput,
5747
6265
  gadgetStartPrefix: this.gadgetStartPrefix,
5748
6266
  gadgetEndPrefix: this.gadgetEndPrefix,
5749
6267
  gadgetArgPrefix: this.gadgetArgPrefix,
5750
6268
  textOnlyHandler: this.textOnlyHandler,
5751
6269
  textWithGadgetsHandler: this.textWithGadgetsHandler,
5752
6270
  stopOnGadgetError: this.stopOnGadgetError,
5753
- shouldContinueAfterError: this.shouldContinueAfterError,
6271
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
5754
6272
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
5755
6273
  gadgetOutputLimit: this.gadgetOutputLimit,
5756
6274
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
5757
6275
  compactionConfig: this.compactionConfig,
5758
- signal: this.signal
6276
+ signal: this.signal,
6277
+ subagentConfig: this.subagentConfig,
6278
+ onNestedEvent
5759
6279
  };
5760
6280
  }
5761
6281
  ask(userPrompt) {
@@ -5912,6 +6432,19 @@ ${endPrefix}`
5912
6432
  this.client = new LLMistClass();
5913
6433
  }
5914
6434
  const registry = GadgetRegistry.from(this.gadgets);
6435
+ let onNestedEvent = this.nestedEventCallback;
6436
+ if (this.parentContext) {
6437
+ const { invocationId, onNestedEvent: parentCallback, depth } = this.parentContext;
6438
+ const existingCallback = this.nestedEventCallback;
6439
+ onNestedEvent = (event) => {
6440
+ parentCallback({
6441
+ ...event,
6442
+ gadgetInvocationId: invocationId,
6443
+ depth: event.depth + depth
6444
+ });
6445
+ existingCallback?.(event);
6446
+ };
6447
+ }
5915
6448
  const options = {
5916
6449
  client: this.client,
5917
6450
  model: this.model ?? "openai:gpt-5-nano",
@@ -5924,19 +6457,21 @@ ${endPrefix}`
5924
6457
  hooks: this.composeHooks(),
5925
6458
  promptConfig: this.promptConfig,
5926
6459
  initialMessages: this.initialMessages,
5927
- onHumanInputRequired: this.onHumanInputRequired,
6460
+ requestHumanInput: this.requestHumanInput,
5928
6461
  gadgetStartPrefix: this.gadgetStartPrefix,
5929
6462
  gadgetEndPrefix: this.gadgetEndPrefix,
5930
6463
  gadgetArgPrefix: this.gadgetArgPrefix,
5931
6464
  textOnlyHandler: this.textOnlyHandler,
5932
6465
  textWithGadgetsHandler: this.textWithGadgetsHandler,
5933
6466
  stopOnGadgetError: this.stopOnGadgetError,
5934
- shouldContinueAfterError: this.shouldContinueAfterError,
6467
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
5935
6468
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
5936
6469
  gadgetOutputLimit: this.gadgetOutputLimit,
5937
6470
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
5938
6471
  compactionConfig: this.compactionConfig,
5939
- signal: this.signal
6472
+ signal: this.signal,
6473
+ subagentConfig: this.subagentConfig,
6474
+ onNestedEvent
5940
6475
  };
5941
6476
  return new Agent(AGENT_INTERNAL_KEY, options);
5942
6477
  }
@@ -6242,9 +6777,9 @@ var init_base_provider = __esm({
6242
6777
  */
6243
6778
  async *stream(options, descriptor, spec) {
6244
6779
  const preparedMessages = this.prepareMessages(options.messages);
6245
- const payload = this.buildRequestPayload(options, descriptor, spec, preparedMessages);
6780
+ const payload = this.buildApiRequest(options, descriptor, spec, preparedMessages);
6246
6781
  const rawStream = await this.executeStreamRequest(payload, options.signal);
6247
- yield* this.wrapStream(rawStream);
6782
+ yield* this.normalizeProviderStream(rawStream);
6248
6783
  }
6249
6784
  /**
6250
6785
  * Prepare messages for the request.
@@ -6344,11 +6879,11 @@ var init_anthropic = __esm({
6344
6879
  "Anthropic does not support speech generation. Use OpenAI (TTS) or Google Gemini (TTS) instead."
6345
6880
  );
6346
6881
  }
6347
- buildRequestPayload(options, descriptor, spec, messages) {
6882
+ buildApiRequest(options, descriptor, spec, messages) {
6348
6883
  const systemMessages = messages.filter((message) => message.role === "system");
6349
6884
  const system = systemMessages.length > 0 ? systemMessages.map((m, index) => ({
6350
6885
  type: "text",
6351
- text: extractText(m.content),
6886
+ text: extractMessageText(m.content),
6352
6887
  // Add cache_control to the LAST system message block
6353
6888
  ...index === systemMessages.length - 1 ? { cache_control: { type: "ephemeral" } } : {}
6354
6889
  })) : void 0;
@@ -6385,7 +6920,7 @@ var init_anthropic = __esm({
6385
6920
  * Handles text, images (base64 only), and applies cache_control.
6386
6921
  */
6387
6922
  convertToAnthropicContent(content, addCacheControl) {
6388
- const parts = normalizeContent(content);
6923
+ const parts = normalizeMessageContent(content);
6389
6924
  return parts.map((part, index) => {
6390
6925
  const isLastPart = index === parts.length - 1;
6391
6926
  const cacheControl = addCacheControl && isLastPart ? { cache_control: { type: "ephemeral" } } : {};
@@ -6431,7 +6966,7 @@ var init_anthropic = __esm({
6431
6966
  const stream2 = await client.messages.create(payload, signal ? { signal } : void 0);
6432
6967
  return stream2;
6433
6968
  }
6434
- async *wrapStream(iterable) {
6969
+ async *normalizeProviderStream(iterable) {
6435
6970
  const stream2 = iterable;
6436
6971
  let inputTokens = 0;
6437
6972
  let cachedInputTokens = 0;
@@ -6508,7 +7043,7 @@ var init_anthropic = __esm({
6508
7043
  async countTokens(messages, descriptor, _spec) {
6509
7044
  const client = this.client;
6510
7045
  const systemMessages = messages.filter((message) => message.role === "system");
6511
- const system = systemMessages.length > 0 ? systemMessages.map((m) => extractText(m.content)).join("\n\n") : void 0;
7046
+ const system = systemMessages.length > 0 ? systemMessages.map((m) => extractMessageText(m.content)).join("\n\n") : void 0;
6512
7047
  const conversation = messages.filter(
6513
7048
  (message) => message.role !== "system"
6514
7049
  ).map((message) => ({
@@ -6530,7 +7065,7 @@ var init_anthropic = __esm({
6530
7065
  let totalChars = 0;
6531
7066
  let imageCount = 0;
6532
7067
  for (const msg of messages) {
6533
- const parts = normalizeContent(msg.content);
7068
+ const parts = normalizeMessageContent(msg.content);
6534
7069
  for (const part of parts) {
6535
7070
  if (part.type === "text") {
6536
7071
  totalChars += part.text.length;
@@ -7221,7 +7756,7 @@ var init_gemini = __esm({
7221
7756
  format: spec?.defaultFormat ?? "wav"
7222
7757
  };
7223
7758
  }
7224
- buildRequestPayload(options, descriptor, _spec, messages) {
7759
+ buildApiRequest(options, descriptor, _spec, messages) {
7225
7760
  const contents = this.convertMessagesToContents(messages);
7226
7761
  const generationConfig = this.buildGenerationConfig(options);
7227
7762
  const config = {
@@ -7273,7 +7808,7 @@ var init_gemini = __esm({
7273
7808
  if (message.role === "system") {
7274
7809
  expandedMessages.push({
7275
7810
  role: "user",
7276
- content: extractText(message.content)
7811
+ content: extractMessageText(message.content)
7277
7812
  });
7278
7813
  expandedMessages.push({
7279
7814
  role: "assistant",
@@ -7323,7 +7858,7 @@ var init_gemini = __esm({
7323
7858
  * Handles text, images, and audio (Gemini supports all three).
7324
7859
  */
7325
7860
  convertToGeminiParts(content) {
7326
- const parts = normalizeContent(content);
7861
+ const parts = normalizeMessageContent(content);
7327
7862
  return parts.map((part) => {
7328
7863
  if (part.type === "text") {
7329
7864
  return { text: part.text };
@@ -7368,10 +7903,10 @@ var init_gemini = __esm({
7368
7903
  }
7369
7904
  return Object.keys(config).length > 0 ? config : null;
7370
7905
  }
7371
- async *wrapStream(iterable) {
7906
+ async *normalizeProviderStream(iterable) {
7372
7907
  const stream2 = iterable;
7373
7908
  for await (const chunk of stream2) {
7374
- const text3 = this.extractText(chunk);
7909
+ const text3 = this.extractMessageText(chunk);
7375
7910
  if (text3) {
7376
7911
  yield { text: text3, rawEvent: chunk };
7377
7912
  }
@@ -7382,7 +7917,7 @@ var init_gemini = __esm({
7382
7917
  }
7383
7918
  }
7384
7919
  }
7385
- extractText(chunk) {
7920
+ extractMessageText(chunk) {
7386
7921
  if (!chunk?.candidates) {
7387
7922
  return "";
7388
7923
  }
@@ -7447,7 +7982,7 @@ var init_gemini = __esm({
7447
7982
  let totalChars = 0;
7448
7983
  let mediaCount = 0;
7449
7984
  for (const msg of messages) {
7450
- const parts = normalizeContent(msg.content);
7985
+ const parts = normalizeMessageContent(msg.content);
7451
7986
  for (const part of parts) {
7452
7987
  if (part.type === "text") {
7453
7988
  totalChars += part.text.length;
@@ -8193,7 +8728,7 @@ var init_openai = __esm({
8193
8728
  format
8194
8729
  };
8195
8730
  }
8196
- buildRequestPayload(options, descriptor, spec, messages) {
8731
+ buildApiRequest(options, descriptor, spec, messages) {
8197
8732
  const { maxTokens, temperature, topP, stopSequences, extra } = options;
8198
8733
  const supportsTemperature = spec?.metadata?.supportsTemperature !== false;
8199
8734
  const shouldIncludeTemperature = typeof temperature === "number" && supportsTemperature;
@@ -8228,7 +8763,7 @@ var init_openai = __esm({
8228
8763
  ...message.name ? { name: message.name } : {}
8229
8764
  };
8230
8765
  }
8231
- const textContent = typeof message.content === "string" ? message.content : extractText(message.content);
8766
+ const textContent = typeof message.content === "string" ? message.content : extractMessageText(message.content);
8232
8767
  if (role === "system") {
8233
8768
  return {
8234
8769
  role: "system",
@@ -8288,7 +8823,7 @@ var init_openai = __esm({
8288
8823
  const stream2 = await client.chat.completions.create(payload, signal ? { signal } : void 0);
8289
8824
  return stream2;
8290
8825
  }
8291
- async *wrapStream(iterable) {
8826
+ async *normalizeProviderStream(iterable) {
8292
8827
  const stream2 = iterable;
8293
8828
  for await (const chunk of stream2) {
8294
8829
  const text3 = chunk.choices.map((choice) => choice.delta?.content ?? "").join("");
@@ -8346,9 +8881,9 @@ var init_openai = __esm({
8346
8881
  tokenCount += OPENAI_MESSAGE_OVERHEAD_TOKENS;
8347
8882
  const roleText = ROLE_MAP[message.role];
8348
8883
  tokenCount += encoding.encode(roleText).length;
8349
- const textContent = extractText(message.content);
8884
+ const textContent = extractMessageText(message.content);
8350
8885
  tokenCount += encoding.encode(textContent).length;
8351
- const parts = normalizeContent(message.content);
8886
+ const parts = normalizeMessageContent(message.content);
8352
8887
  for (const part of parts) {
8353
8888
  if (part.type === "image") {
8354
8889
  imageCount++;
@@ -8373,7 +8908,7 @@ var init_openai = __esm({
8373
8908
  let totalChars = 0;
8374
8909
  let imageCount = 0;
8375
8910
  for (const msg of messages) {
8376
- const parts = normalizeContent(msg.content);
8911
+ const parts = normalizeMessageContent(msg.content);
8377
8912
  for (const part of parts) {
8378
8913
  if (part.type === "text") {
8379
8914
  totalChars += part.text.length;
@@ -10106,7 +10641,7 @@ var MockBuilder = class {
10106
10641
  whenMessageContains(text3) {
10107
10642
  this.matchers.push(
10108
10643
  (ctx) => ctx.messages.some(
10109
- (msg) => extractText(msg.content).toLowerCase().includes(text3.toLowerCase())
10644
+ (msg) => extractMessageText(msg.content).toLowerCase().includes(text3.toLowerCase())
10110
10645
  )
10111
10646
  );
10112
10647
  return this;
@@ -10121,7 +10656,7 @@ var MockBuilder = class {
10121
10656
  this.matchers.push((ctx) => {
10122
10657
  const lastMsg = ctx.messages[ctx.messages.length - 1];
10123
10658
  if (!lastMsg) return false;
10124
- return extractText(lastMsg.content).toLowerCase().includes(text3.toLowerCase());
10659
+ return extractMessageText(lastMsg.content).toLowerCase().includes(text3.toLowerCase());
10125
10660
  });
10126
10661
  return this;
10127
10662
  }
@@ -10132,7 +10667,7 @@ var MockBuilder = class {
10132
10667
  * mockLLM().whenMessageMatches(/calculate \d+/)
10133
10668
  */
10134
10669
  whenMessageMatches(regex) {
10135
- this.matchers.push((ctx) => ctx.messages.some((msg) => regex.test(extractText(msg.content))));
10670
+ this.matchers.push((ctx) => ctx.messages.some((msg) => regex.test(extractMessageText(msg.content))));
10136
10671
  return this;
10137
10672
  }
10138
10673
  /**
@@ -10144,7 +10679,7 @@ var MockBuilder = class {
10144
10679
  whenRoleContains(role, text3) {
10145
10680
  this.matchers.push(
10146
10681
  (ctx) => ctx.messages.some(
10147
- (msg) => msg.role === role && extractText(msg.content).toLowerCase().includes(text3.toLowerCase())
10682
+ (msg) => msg.role === role && extractMessageText(msg.content).toLowerCase().includes(text3.toLowerCase())
10148
10683
  )
10149
10684
  );
10150
10685
  return this;
@@ -10566,7 +11101,7 @@ var MockConversationManager = class {
10566
11101
  this.history.push(msg);
10567
11102
  this.addedMessages.push(msg);
10568
11103
  }
10569
- addGadgetCall(gadgetName, parameters, result) {
11104
+ addGadgetCallResult(gadgetName, parameters, result) {
10570
11105
  const assistantMsg = {
10571
11106
  role: "assistant",
10572
11107
  content: `!!!GADGET_START:${gadgetName}
@@ -10676,7 +11211,7 @@ function createMockConversationManager(turnCount, baseMessages = []) {
10676
11211
 
10677
11212
  // src/testing/mock-gadget.ts
10678
11213
  init_gadget();
10679
- var MockGadgetImpl = class extends BaseGadget {
11214
+ var MockGadgetImpl = class extends AbstractGadget {
10680
11215
  name;
10681
11216
  description;
10682
11217
  parameterSchema;