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.
package/dist/index.cjs CHANGED
@@ -566,13 +566,13 @@ var init_prompt_config = __esm({
566
566
  });
567
567
 
568
568
  // src/core/messages.ts
569
- function normalizeContent(content) {
569
+ function normalizeMessageContent(content) {
570
570
  if (typeof content === "string") {
571
571
  return [{ type: "text", text: content }];
572
572
  }
573
573
  return content;
574
574
  }
575
- function extractText(content) {
575
+ function extractMessageText(content) {
576
576
  if (typeof content === "string") {
577
577
  return content;
578
578
  }
@@ -932,7 +932,17 @@ Produces: { "items": ["first", "second"] }`);
932
932
  this.messages.push({ role: "user", content: parts });
933
933
  return this;
934
934
  }
935
- addGadgetCall(gadget, parameters, result, media, mediaIds) {
935
+ /**
936
+ * Record a gadget execution result in the message history.
937
+ * Creates an assistant message with the gadget invocation and a user message with the result.
938
+ *
939
+ * @param gadget - Name of the gadget that was executed
940
+ * @param parameters - Parameters that were passed to the gadget
941
+ * @param result - Text result from the gadget execution
942
+ * @param media - Optional media outputs from the gadget
943
+ * @param mediaIds - Optional IDs for the media outputs
944
+ */
945
+ addGadgetCallResult(gadget, parameters, result, media, mediaIds) {
936
946
  const paramStr = this.formatBlockParameters(parameters, "");
937
947
  this.messages.push({
938
948
  role: "assistant",
@@ -1200,21 +1210,21 @@ var init_media_store = __esm({
1200
1210
  });
1201
1211
 
1202
1212
  // src/gadgets/exceptions.ts
1203
- var BreakLoopException, HumanInputException, TimeoutException, AbortError;
1213
+ var TaskCompletionSignal, HumanInputRequiredException, TimeoutException, AbortException;
1204
1214
  var init_exceptions = __esm({
1205
1215
  "src/gadgets/exceptions.ts"() {
1206
1216
  "use strict";
1207
- BreakLoopException = class extends Error {
1217
+ TaskCompletionSignal = class extends Error {
1208
1218
  constructor(message) {
1209
1219
  super(message ?? "Agent loop terminated by gadget");
1210
- this.name = "BreakLoopException";
1220
+ this.name = "TaskCompletionSignal";
1211
1221
  }
1212
1222
  };
1213
- HumanInputException = class extends Error {
1223
+ HumanInputRequiredException = class extends Error {
1214
1224
  question;
1215
1225
  constructor(question) {
1216
1226
  super(`Human input required: ${question}`);
1217
- this.name = "HumanInputException";
1227
+ this.name = "HumanInputRequiredException";
1218
1228
  this.question = question;
1219
1229
  }
1220
1230
  };
@@ -1228,10 +1238,10 @@ var init_exceptions = __esm({
1228
1238
  this.timeoutMs = timeoutMs;
1229
1239
  }
1230
1240
  };
1231
- AbortError = class extends Error {
1241
+ AbortException = class extends Error {
1232
1242
  constructor(message) {
1233
1243
  super(message || "Gadget execution was aborted");
1234
- this.name = "AbortError";
1244
+ this.name = "AbortException";
1235
1245
  }
1236
1246
  };
1237
1247
  }
@@ -1400,7 +1410,7 @@ var init_schema_to_json = __esm({
1400
1410
  });
1401
1411
 
1402
1412
  // src/gadgets/gadget.ts
1403
- function formatParamsAsBlock(params, prefix = "", argPrefix = GADGET_ARG_PREFIX) {
1413
+ function formatParamsForBlockExample(params, prefix = "", argPrefix = GADGET_ARG_PREFIX) {
1404
1414
  const lines = [];
1405
1415
  for (const [key, value] of Object.entries(params)) {
1406
1416
  const fullPath = prefix ? `${prefix}/${key}` : key;
@@ -1408,14 +1418,14 @@ function formatParamsAsBlock(params, prefix = "", argPrefix = GADGET_ARG_PREFIX)
1408
1418
  value.forEach((item, index) => {
1409
1419
  const itemPath = `${fullPath}/${index}`;
1410
1420
  if (typeof item === "object" && item !== null) {
1411
- lines.push(formatParamsAsBlock(item, itemPath, argPrefix));
1421
+ lines.push(formatParamsForBlockExample(item, itemPath, argPrefix));
1412
1422
  } else {
1413
1423
  lines.push(`${argPrefix}${itemPath}`);
1414
1424
  lines.push(String(item));
1415
1425
  }
1416
1426
  });
1417
1427
  } else if (typeof value === "object" && value !== null) {
1418
- lines.push(formatParamsAsBlock(value, fullPath, argPrefix));
1428
+ lines.push(formatParamsForBlockExample(value, fullPath, argPrefix));
1419
1429
  } else {
1420
1430
  lines.push(`${argPrefix}${fullPath}`);
1421
1431
  lines.push(String(value));
@@ -1504,7 +1514,7 @@ function formatSchemaAsPlainText(schema, indent = "", atRoot = true) {
1504
1514
  }
1505
1515
  return lines.join("\n");
1506
1516
  }
1507
- var BaseGadget;
1517
+ var AbstractGadget;
1508
1518
  var init_gadget = __esm({
1509
1519
  "src/gadgets/gadget.ts"() {
1510
1520
  "use strict";
@@ -1512,7 +1522,7 @@ var init_gadget = __esm({
1512
1522
  init_exceptions();
1513
1523
  init_schema_to_json();
1514
1524
  init_schema_validator();
1515
- BaseGadget = class {
1525
+ AbstractGadget = class {
1516
1526
  /**
1517
1527
  * The name of the gadget. Used for identification when LLM calls it.
1518
1528
  * If not provided, defaults to the class name.
@@ -1540,14 +1550,14 @@ var init_gadget = __esm({
1540
1550
  */
1541
1551
  examples;
1542
1552
  /**
1543
- * Throws an AbortError if the execution has been aborted.
1553
+ * Throws an AbortException if the execution has been aborted.
1544
1554
  *
1545
1555
  * Call this at key checkpoints in long-running gadgets to allow early exit
1546
1556
  * when the gadget has been cancelled (e.g., due to timeout). This enables
1547
1557
  * resource cleanup and prevents unnecessary work after cancellation.
1548
1558
  *
1549
1559
  * @param ctx - The execution context containing the abort signal
1550
- * @throws AbortError if ctx.signal.aborted is true
1560
+ * @throws AbortException if ctx.signal.aborted is true
1551
1561
  *
1552
1562
  * @example
1553
1563
  * ```typescript
@@ -1572,7 +1582,7 @@ var init_gadget = __esm({
1572
1582
  */
1573
1583
  throwIfAborted(ctx) {
1574
1584
  if (ctx?.signal?.aborted) {
1575
- throw new AbortError();
1585
+ throw new AbortException();
1576
1586
  }
1577
1587
  }
1578
1588
  /**
@@ -1714,7 +1724,7 @@ var init_gadget = __esm({
1714
1724
  }
1715
1725
  parts.push(`${effectiveStartPrefix}${gadgetName}`);
1716
1726
  parts.push(
1717
- formatParamsAsBlock(example.params, "", effectiveArgPrefix)
1727
+ formatParamsForBlockExample(example.params, "", effectiveArgPrefix)
1718
1728
  );
1719
1729
  parts.push(effectiveEndPrefix);
1720
1730
  if (example.output !== void 0) {
@@ -1732,7 +1742,7 @@ var init_gadget = __esm({
1732
1742
 
1733
1743
  // src/gadgets/create-gadget.ts
1734
1744
  function createGadget(config) {
1735
- class DynamicGadget extends BaseGadget {
1745
+ class DynamicGadget extends AbstractGadget {
1736
1746
  name = config.name;
1737
1747
  description = config.description;
1738
1748
  parameterSchema = config.schema;
@@ -2370,8 +2380,8 @@ var init_conversation_manager = __esm({
2370
2380
  addAssistantMessage(content) {
2371
2381
  this.historyBuilder.addAssistant(content);
2372
2382
  }
2373
- addGadgetCall(gadgetName, parameters, result, media, mediaIds) {
2374
- this.historyBuilder.addGadgetCall(gadgetName, parameters, result, media, mediaIds);
2383
+ addGadgetCallResult(gadgetName, parameters, result, media, mediaIds) {
2384
+ this.historyBuilder.addGadgetCallResult(gadgetName, parameters, result, media, mediaIds);
2375
2385
  }
2376
2386
  getMessages() {
2377
2387
  return [...this.baseMessages, ...this.initialMessages, ...this.historyBuilder.build()];
@@ -2391,7 +2401,7 @@ var init_conversation_manager = __esm({
2391
2401
  if (msg.role === "user") {
2392
2402
  this.historyBuilder.addUser(msg.content);
2393
2403
  } else if (msg.role === "assistant") {
2394
- this.historyBuilder.addAssistant(extractText(msg.content));
2404
+ this.historyBuilder.addAssistant(extractMessageText(msg.content));
2395
2405
  }
2396
2406
  }
2397
2407
  }
@@ -3271,12 +3281,12 @@ var init_cost_reporting_client = __esm({
3271
3281
  });
3272
3282
 
3273
3283
  // src/gadgets/error-formatter.ts
3274
- var GadgetErrorFormatter;
3284
+ var GadgetExecutionErrorFormatter;
3275
3285
  var init_error_formatter = __esm({
3276
3286
  "src/gadgets/error-formatter.ts"() {
3277
3287
  "use strict";
3278
3288
  init_constants();
3279
- GadgetErrorFormatter = class {
3289
+ GadgetExecutionErrorFormatter = class {
3280
3290
  argPrefix;
3281
3291
  startPrefix;
3282
3292
  endPrefix;
@@ -3362,16 +3372,16 @@ function stripMarkdownFences(content) {
3362
3372
  cleaned = cleaned.replace(closingFence, "");
3363
3373
  return cleaned.trim();
3364
3374
  }
3365
- var globalInvocationCounter, StreamParser;
3375
+ var globalInvocationCounter, GadgetCallParser;
3366
3376
  var init_parser = __esm({
3367
3377
  "src/gadgets/parser.ts"() {
3368
3378
  "use strict";
3369
3379
  init_constants();
3370
3380
  init_block_params();
3371
3381
  globalInvocationCounter = 0;
3372
- StreamParser = class {
3382
+ GadgetCallParser = class {
3373
3383
  buffer = "";
3374
- lastReportedTextLength = 0;
3384
+ lastEmittedTextOffset = 0;
3375
3385
  startPrefix;
3376
3386
  endPrefix;
3377
3387
  argPrefix;
@@ -3380,16 +3390,20 @@ var init_parser = __esm({
3380
3390
  this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
3381
3391
  this.argPrefix = options.argPrefix ?? GADGET_ARG_PREFIX;
3382
3392
  }
3383
- takeTextUntil(index) {
3384
- if (index <= this.lastReportedTextLength) {
3393
+ /**
3394
+ * Extract and consume text up to the given index.
3395
+ * Returns undefined if no meaningful text to emit.
3396
+ */
3397
+ extractTextSegment(index) {
3398
+ if (index <= this.lastEmittedTextOffset) {
3385
3399
  return void 0;
3386
3400
  }
3387
- const segment = this.buffer.slice(this.lastReportedTextLength, index);
3388
- this.lastReportedTextLength = index;
3401
+ const segment = this.buffer.slice(this.lastEmittedTextOffset, index);
3402
+ this.lastEmittedTextOffset = index;
3389
3403
  return segment.trim().length > 0 ? segment : void 0;
3390
3404
  }
3391
3405
  /**
3392
- * Parse gadget name with optional invocation ID and dependencies.
3406
+ * Parse gadget invocation metadata from the header line.
3393
3407
  *
3394
3408
  * Supported formats:
3395
3409
  * - `GadgetName` - Auto-generate ID, no dependencies
@@ -3398,24 +3412,24 @@ var init_parser = __esm({
3398
3412
  *
3399
3413
  * Dependencies must be comma-separated invocation IDs.
3400
3414
  */
3401
- parseGadgetName(gadgetName) {
3402
- const parts = gadgetName.split(":");
3415
+ parseInvocationMetadata(headerLine) {
3416
+ const parts = headerLine.split(":");
3403
3417
  if (parts.length === 1) {
3404
3418
  return {
3405
- actualName: parts[0],
3419
+ gadgetName: parts[0],
3406
3420
  invocationId: `gadget_${++globalInvocationCounter}`,
3407
3421
  dependencies: []
3408
3422
  };
3409
3423
  } else if (parts.length === 2) {
3410
3424
  return {
3411
- actualName: parts[0],
3425
+ gadgetName: parts[0],
3412
3426
  invocationId: parts[1].trim(),
3413
3427
  dependencies: []
3414
3428
  };
3415
3429
  } else {
3416
3430
  const deps = parts[2].split(",").map((d) => d.trim()).filter((d) => d.length > 0);
3417
3431
  return {
3418
- actualName: parts[0],
3432
+ gadgetName: parts[0],
3419
3433
  invocationId: parts[1].trim(),
3420
3434
  dependencies: deps
3421
3435
  };
@@ -3447,19 +3461,15 @@ var init_parser = __esm({
3447
3461
  while (true) {
3448
3462
  const partStartIndex = this.buffer.indexOf(this.startPrefix, startIndex);
3449
3463
  if (partStartIndex === -1) break;
3450
- const textBefore = this.takeTextUntil(partStartIndex);
3464
+ const textBefore = this.extractTextSegment(partStartIndex);
3451
3465
  if (textBefore !== void 0) {
3452
3466
  yield { type: "text", content: textBefore };
3453
3467
  }
3454
3468
  const metadataStartIndex = partStartIndex + this.startPrefix.length;
3455
3469
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
3456
3470
  if (metadataEndIndex === -1) break;
3457
- const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3458
- const {
3459
- actualName: actualGadgetName,
3460
- invocationId,
3461
- dependencies
3462
- } = this.parseGadgetName(gadgetName);
3471
+ const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3472
+ const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
3463
3473
  const contentStartIndex = metadataEndIndex + 1;
3464
3474
  let partEndIndex;
3465
3475
  let endMarkerLength = 0;
@@ -3479,7 +3489,7 @@ var init_parser = __esm({
3479
3489
  yield {
3480
3490
  type: "gadget_call",
3481
3491
  call: {
3482
- gadgetName: actualGadgetName,
3492
+ gadgetName,
3483
3493
  invocationId,
3484
3494
  parametersRaw,
3485
3495
  parameters,
@@ -3488,37 +3498,33 @@ var init_parser = __esm({
3488
3498
  }
3489
3499
  };
3490
3500
  startIndex = partEndIndex + endMarkerLength;
3491
- this.lastReportedTextLength = startIndex;
3501
+ this.lastEmittedTextOffset = startIndex;
3492
3502
  }
3493
3503
  if (startIndex > 0) {
3494
3504
  this.buffer = this.buffer.substring(startIndex);
3495
- this.lastReportedTextLength = 0;
3505
+ this.lastEmittedTextOffset = 0;
3496
3506
  }
3497
3507
  }
3498
3508
  // Finalize parsing and return remaining text or incomplete gadgets
3499
3509
  *finalize() {
3500
- const startIndex = this.buffer.indexOf(this.startPrefix, this.lastReportedTextLength);
3510
+ const startIndex = this.buffer.indexOf(this.startPrefix, this.lastEmittedTextOffset);
3501
3511
  if (startIndex !== -1) {
3502
- const textBefore = this.takeTextUntil(startIndex);
3512
+ const textBefore = this.extractTextSegment(startIndex);
3503
3513
  if (textBefore !== void 0) {
3504
3514
  yield { type: "text", content: textBefore };
3505
3515
  }
3506
3516
  const metadataStartIndex = startIndex + this.startPrefix.length;
3507
3517
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
3508
3518
  if (metadataEndIndex !== -1) {
3509
- const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3510
- const {
3511
- actualName: actualGadgetName,
3512
- invocationId,
3513
- dependencies
3514
- } = this.parseGadgetName(gadgetName);
3519
+ const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3520
+ const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
3515
3521
  const contentStartIndex = metadataEndIndex + 1;
3516
3522
  const parametersRaw = this.buffer.substring(contentStartIndex).trim();
3517
3523
  const { parameters, parseError } = this.parseParameters(parametersRaw);
3518
3524
  yield {
3519
3525
  type: "gadget_call",
3520
3526
  call: {
3521
- gadgetName: actualGadgetName,
3527
+ gadgetName,
3522
3528
  invocationId,
3523
3529
  parametersRaw,
3524
3530
  parameters,
@@ -3529,7 +3535,7 @@ var init_parser = __esm({
3529
3535
  return;
3530
3536
  }
3531
3537
  }
3532
- const remainingText = this.takeTextUntil(this.buffer.length);
3538
+ const remainingText = this.extractTextSegment(this.buffer.length);
3533
3539
  if (remainingText !== void 0) {
3534
3540
  yield { type: "text", content: remainingText };
3535
3541
  }
@@ -3537,7 +3543,7 @@ var init_parser = __esm({
3537
3543
  // Reset parser state (note: global invocation counter is NOT reset to ensure unique IDs)
3538
3544
  reset() {
3539
3545
  this.buffer = "";
3540
- this.lastReportedTextLength = 0;
3546
+ this.lastEmittedTextOffset = 0;
3541
3547
  }
3542
3548
  };
3543
3549
  }
@@ -3556,14 +3562,17 @@ var init_executor = __esm({
3556
3562
  init_exceptions();
3557
3563
  init_parser();
3558
3564
  GadgetExecutor = class {
3559
- constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore) {
3565
+ constructor(registry, requestHumanInput, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig, onNestedEvent) {
3560
3566
  this.registry = registry;
3561
- this.onHumanInputRequired = onHumanInputRequired;
3567
+ this.requestHumanInput = requestHumanInput;
3562
3568
  this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
3563
3569
  this.client = client;
3564
3570
  this.mediaStore = mediaStore;
3571
+ this.agentConfig = agentConfig;
3572
+ this.subagentConfig = subagentConfig;
3573
+ this.onNestedEvent = onNestedEvent;
3565
3574
  this.logger = logger ?? createLogger({ name: "llmist:executor" });
3566
- this.errorFormatter = new GadgetErrorFormatter(errorFormatterOptions);
3575
+ this.errorFormatter = new GadgetExecutionErrorFormatter(errorFormatterOptions);
3567
3576
  this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
3568
3577
  }
3569
3578
  logger;
@@ -3583,11 +3592,11 @@ var init_executor = __esm({
3583
3592
  });
3584
3593
  }
3585
3594
  /**
3586
- * Normalizes gadget execute result to consistent format.
3595
+ * Unify gadget execute result to consistent internal format.
3587
3596
  * Handles string returns (backwards compat), object returns with cost,
3588
3597
  * and object returns with media.
3589
3598
  */
3590
- normalizeExecuteResult(raw) {
3599
+ unifyExecuteResult(raw) {
3591
3600
  if (typeof raw === "string") {
3592
3601
  return { result: raw, cost: 0 };
3593
3602
  }
@@ -3705,7 +3714,11 @@ var init_executor = __esm({
3705
3714
  const ctx = {
3706
3715
  reportCost,
3707
3716
  llmist: this.client ? new CostReportingLLMistWrapper(this.client, reportCost) : void 0,
3708
- signal: abortController.signal
3717
+ signal: abortController.signal,
3718
+ agentConfig: this.agentConfig,
3719
+ subagentConfig: this.subagentConfig,
3720
+ invocationId: call.invocationId,
3721
+ onNestedEvent: this.onNestedEvent
3709
3722
  };
3710
3723
  let rawResult;
3711
3724
  if (timeoutMs && timeoutMs > 0) {
@@ -3720,7 +3733,7 @@ var init_executor = __esm({
3720
3733
  } else {
3721
3734
  rawResult = await Promise.resolve(gadget.execute(validatedParameters, ctx));
3722
3735
  }
3723
- const { result, media, cost: returnCost } = this.normalizeExecuteResult(rawResult);
3736
+ const { result, media, cost: returnCost } = this.unifyExecuteResult(rawResult);
3724
3737
  const totalCost = callbackCost + returnCost;
3725
3738
  let mediaIds;
3726
3739
  let storedMedia;
@@ -3766,7 +3779,7 @@ var init_executor = __esm({
3766
3779
  storedMedia
3767
3780
  };
3768
3781
  } catch (error) {
3769
- if (error instanceof BreakLoopException) {
3782
+ if (error instanceof TaskCompletionSignal) {
3770
3783
  this.logger.info("Gadget requested loop termination", {
3771
3784
  gadgetName: call.gadgetName,
3772
3785
  message: error.message
@@ -3794,7 +3807,7 @@ var init_executor = __esm({
3794
3807
  executionTimeMs: Date.now() - startTime
3795
3808
  };
3796
3809
  }
3797
- if (error instanceof AbortError) {
3810
+ if (error instanceof AbortException) {
3798
3811
  this.logger.info("Gadget execution was aborted", {
3799
3812
  gadgetName: call.gadgetName,
3800
3813
  executionTimeMs: Date.now() - startTime
@@ -3807,14 +3820,14 @@ var init_executor = __esm({
3807
3820
  executionTimeMs: Date.now() - startTime
3808
3821
  };
3809
3822
  }
3810
- if (error instanceof HumanInputException) {
3823
+ if (error instanceof HumanInputRequiredException) {
3811
3824
  this.logger.info("Gadget requested human input", {
3812
3825
  gadgetName: call.gadgetName,
3813
3826
  question: error.question
3814
3827
  });
3815
- if (this.onHumanInputRequired) {
3828
+ if (this.requestHumanInput) {
3816
3829
  try {
3817
- const answer = await this.onHumanInputRequired(error.question);
3830
+ const answer = await this.requestHumanInput(error.question);
3818
3831
  this.logger.debug("Human input received", {
3819
3832
  gadgetName: call.gadgetName,
3820
3833
  answerLength: answer.length
@@ -3912,13 +3925,13 @@ var init_stream_processor = __esm({
3912
3925
  parser;
3913
3926
  executor;
3914
3927
  stopOnGadgetError;
3915
- shouldContinueAfterError;
3916
- accumulatedText = "";
3917
- shouldStopExecution = false;
3928
+ canRecoverFromGadgetError;
3929
+ responseText = "";
3930
+ executionHalted = false;
3918
3931
  observerFailureCount = 0;
3919
3932
  // Dependency tracking for gadget execution DAG
3920
3933
  /** Gadgets waiting for their dependencies to complete */
3921
- pendingGadgets = /* @__PURE__ */ new Map();
3934
+ gadgetsAwaitingDependencies = /* @__PURE__ */ new Map();
3922
3935
  /** Completed gadget results, keyed by invocation ID */
3923
3936
  completedResults = /* @__PURE__ */ new Map();
3924
3937
  /** Invocation IDs of gadgets that have failed (error or skipped due to dependency) */
@@ -3929,27 +3942,36 @@ var init_stream_processor = __esm({
3929
3942
  this.hooks = options.hooks ?? {};
3930
3943
  this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
3931
3944
  this.stopOnGadgetError = options.stopOnGadgetError ?? true;
3932
- this.shouldContinueAfterError = options.shouldContinueAfterError;
3933
- this.parser = new StreamParser({
3945
+ this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
3946
+ this.parser = new GadgetCallParser({
3934
3947
  startPrefix: options.gadgetStartPrefix,
3935
3948
  endPrefix: options.gadgetEndPrefix,
3936
3949
  argPrefix: options.gadgetArgPrefix
3937
3950
  });
3938
3951
  this.executor = new GadgetExecutor(
3939
3952
  options.registry,
3940
- options.onHumanInputRequired,
3953
+ options.requestHumanInput,
3941
3954
  this.logger.getSubLogger({ name: "executor" }),
3942
3955
  options.defaultGadgetTimeoutMs,
3943
3956
  { argPrefix: options.gadgetArgPrefix },
3944
3957
  options.client,
3945
- options.mediaStore
3958
+ options.mediaStore,
3959
+ options.agentConfig,
3960
+ options.subagentConfig,
3961
+ options.onNestedEvent
3946
3962
  );
3947
3963
  }
3948
3964
  /**
3949
- * Process an LLM stream and return structured results.
3965
+ * Process an LLM stream and yield events in real-time.
3966
+ *
3967
+ * This is an async generator that yields events immediately as they occur:
3968
+ * - Text events are yielded as text is streamed from the LLM
3969
+ * - gadget_call events are yielded immediately when a gadget call is parsed
3970
+ * - gadget_result events are yielded when gadget execution completes
3971
+ *
3972
+ * The final event is always a StreamCompletionEvent containing metadata.
3950
3973
  */
3951
- async process(stream2) {
3952
- const outputs = [];
3974
+ async *process(stream2) {
3953
3975
  let finishReason = null;
3954
3976
  let usage;
3955
3977
  let didExecuteGadgets = false;
@@ -3963,7 +3985,7 @@ var init_stream_processor = __esm({
3963
3985
  if (this.hooks.interceptors?.interceptRawChunk) {
3964
3986
  const context = {
3965
3987
  iteration: this.iteration,
3966
- accumulatedText: this.accumulatedText,
3988
+ accumulatedText: this.responseText,
3967
3989
  logger: this.logger
3968
3990
  };
3969
3991
  const intercepted = this.hooks.interceptors.interceptRawChunk(processedChunk, context);
@@ -3974,7 +3996,7 @@ var init_stream_processor = __esm({
3974
3996
  }
3975
3997
  }
3976
3998
  if (processedChunk) {
3977
- this.accumulatedText += processedChunk;
3999
+ this.responseText += processedChunk;
3978
4000
  }
3979
4001
  }
3980
4002
  if (this.hooks.observers?.onStreamChunk && (processedChunk || chunk.usage)) {
@@ -3983,7 +4005,7 @@ var init_stream_processor = __esm({
3983
4005
  const context = {
3984
4006
  iteration: this.iteration,
3985
4007
  rawChunk: processedChunk,
3986
- accumulatedText: this.accumulatedText,
4008
+ accumulatedText: this.responseText,
3987
4009
  usage,
3988
4010
  logger: this.logger
3989
4011
  };
@@ -3995,67 +4017,66 @@ var init_stream_processor = __esm({
3995
4017
  continue;
3996
4018
  }
3997
4019
  for (const event of this.parser.feed(processedChunk)) {
3998
- const processedEvents = await this.processEvent(event);
3999
- outputs.push(...processedEvents);
4000
- if (processedEvents.some((e) => e.type === "gadget_result")) {
4001
- didExecuteGadgets = true;
4002
- }
4003
- for (const evt of processedEvents) {
4004
- if (evt.type === "gadget_result" && evt.result.breaksLoop) {
4005
- shouldBreakLoop = true;
4020
+ for await (const processedEvent of this.processEventGenerator(event)) {
4021
+ yield processedEvent;
4022
+ if (processedEvent.type === "gadget_result") {
4023
+ didExecuteGadgets = true;
4024
+ if (processedEvent.result.breaksLoop) {
4025
+ shouldBreakLoop = true;
4026
+ }
4006
4027
  }
4007
4028
  }
4008
4029
  }
4009
- if (this.shouldStopExecution) {
4030
+ if (this.executionHalted) {
4010
4031
  this.logger.info("Breaking from LLM stream due to gadget error");
4011
4032
  break;
4012
4033
  }
4013
4034
  }
4014
- if (!this.shouldStopExecution) {
4035
+ if (!this.executionHalted) {
4015
4036
  for (const event of this.parser.finalize()) {
4016
- const processedEvents = await this.processEvent(event);
4017
- outputs.push(...processedEvents);
4018
- if (processedEvents.some((e) => e.type === "gadget_result")) {
4019
- didExecuteGadgets = true;
4020
- }
4021
- for (const evt of processedEvents) {
4022
- if (evt.type === "gadget_result" && evt.result.breaksLoop) {
4023
- shouldBreakLoop = true;
4037
+ for await (const processedEvent of this.processEventGenerator(event)) {
4038
+ yield processedEvent;
4039
+ if (processedEvent.type === "gadget_result") {
4040
+ didExecuteGadgets = true;
4041
+ if (processedEvent.result.breaksLoop) {
4042
+ shouldBreakLoop = true;
4043
+ }
4024
4044
  }
4025
4045
  }
4026
4046
  }
4027
- const finalPendingEvents = await this.processPendingGadgets();
4028
- outputs.push(...finalPendingEvents);
4029
- if (finalPendingEvents.some((e) => e.type === "gadget_result")) {
4030
- didExecuteGadgets = true;
4031
- }
4032
- for (const evt of finalPendingEvents) {
4033
- if (evt.type === "gadget_result" && evt.result.breaksLoop) {
4034
- shouldBreakLoop = true;
4047
+ for await (const evt of this.processPendingGadgetsGenerator()) {
4048
+ yield evt;
4049
+ if (evt.type === "gadget_result") {
4050
+ didExecuteGadgets = true;
4051
+ if (evt.result.breaksLoop) {
4052
+ shouldBreakLoop = true;
4053
+ }
4035
4054
  }
4036
4055
  }
4037
4056
  }
4038
- let finalMessage = this.accumulatedText;
4057
+ let finalMessage = this.responseText;
4039
4058
  if (this.hooks.interceptors?.interceptAssistantMessage) {
4040
4059
  const context = {
4041
4060
  iteration: this.iteration,
4042
- rawResponse: this.accumulatedText,
4061
+ rawResponse: this.responseText,
4043
4062
  logger: this.logger
4044
4063
  };
4045
4064
  finalMessage = this.hooks.interceptors.interceptAssistantMessage(finalMessage, context);
4046
4065
  }
4047
- return {
4048
- outputs,
4066
+ const completionEvent = {
4067
+ type: "stream_complete",
4049
4068
  shouldBreakLoop,
4050
4069
  didExecuteGadgets,
4051
4070
  finishReason,
4052
4071
  usage,
4053
- rawResponse: this.accumulatedText,
4072
+ rawResponse: this.responseText,
4054
4073
  finalMessage
4055
4074
  };
4075
+ yield completionEvent;
4056
4076
  }
4057
4077
  /**
4058
4078
  * Process a single parsed event (text or gadget call).
4079
+ * @deprecated Use processEventGenerator for real-time streaming
4059
4080
  */
4060
4081
  async processEvent(event) {
4061
4082
  if (event.type === "text") {
@@ -4065,6 +4086,23 @@ var init_stream_processor = __esm({
4065
4086
  }
4066
4087
  return [event];
4067
4088
  }
4089
+ /**
4090
+ * Process a single parsed event, yielding events in real-time.
4091
+ * Generator version of processEvent for streaming support.
4092
+ */
4093
+ async *processEventGenerator(event) {
4094
+ if (event.type === "text") {
4095
+ for (const e of await this.processTextEvent(event)) {
4096
+ yield e;
4097
+ }
4098
+ } else if (event.type === "gadget_call") {
4099
+ for await (const e of this.processGadgetCallGenerator(event.call)) {
4100
+ yield e;
4101
+ }
4102
+ } else {
4103
+ yield event;
4104
+ }
4105
+ }
4068
4106
  /**
4069
4107
  * Process a text event through interceptors.
4070
4108
  */
@@ -4073,7 +4111,7 @@ var init_stream_processor = __esm({
4073
4111
  if (this.hooks.interceptors?.interceptTextChunk) {
4074
4112
  const context = {
4075
4113
  iteration: this.iteration,
4076
- accumulatedText: this.accumulatedText,
4114
+ accumulatedText: this.responseText,
4077
4115
  logger: this.logger
4078
4116
  };
4079
4117
  const intercepted = this.hooks.interceptors.interceptTextChunk(content, context);
@@ -4092,7 +4130,7 @@ var init_stream_processor = __esm({
4092
4130
  * After each execution, pending gadgets are checked to see if they can now run.
4093
4131
  */
4094
4132
  async processGadgetCall(call) {
4095
- if (this.shouldStopExecution) {
4133
+ if (this.executionHalted) {
4096
4134
  this.logger.debug("Skipping gadget execution due to previous error", {
4097
4135
  gadgetName: call.gadgetName
4098
4136
  });
@@ -4131,7 +4169,7 @@ var init_stream_processor = __esm({
4131
4169
  invocationId: call.invocationId,
4132
4170
  waitingOn: unsatisfied
4133
4171
  });
4134
- this.pendingGadgets.set(call.invocationId, call);
4172
+ this.gadgetsAwaitingDependencies.set(call.invocationId, call);
4135
4173
  return events;
4136
4174
  }
4137
4175
  }
@@ -4141,9 +4179,68 @@ var init_stream_processor = __esm({
4141
4179
  events.push(...triggeredEvents);
4142
4180
  return events;
4143
4181
  }
4182
+ /**
4183
+ * Process a gadget call, yielding events in real-time.
4184
+ *
4185
+ * Key difference from processGadgetCall: yields gadget_call event IMMEDIATELY
4186
+ * when parsed (before execution), enabling real-time UI feedback.
4187
+ */
4188
+ async *processGadgetCallGenerator(call) {
4189
+ if (this.executionHalted) {
4190
+ this.logger.debug("Skipping gadget execution due to previous error", {
4191
+ gadgetName: call.gadgetName
4192
+ });
4193
+ return;
4194
+ }
4195
+ yield { type: "gadget_call", call };
4196
+ if (call.dependencies.length > 0) {
4197
+ if (call.dependencies.includes(call.invocationId)) {
4198
+ this.logger.warn("Gadget has self-referential dependency (depends on itself)", {
4199
+ gadgetName: call.gadgetName,
4200
+ invocationId: call.invocationId
4201
+ });
4202
+ this.failedInvocations.add(call.invocationId);
4203
+ const skipEvent = {
4204
+ type: "gadget_skipped",
4205
+ gadgetName: call.gadgetName,
4206
+ invocationId: call.invocationId,
4207
+ parameters: call.parameters ?? {},
4208
+ failedDependency: call.invocationId,
4209
+ failedDependencyError: `Gadget "${call.invocationId}" cannot depend on itself (self-referential dependency)`
4210
+ };
4211
+ yield skipEvent;
4212
+ return;
4213
+ }
4214
+ const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
4215
+ if (failedDep) {
4216
+ const skipEvents = await this.handleFailedDependency(call, failedDep);
4217
+ for (const evt of skipEvents) {
4218
+ yield evt;
4219
+ }
4220
+ return;
4221
+ }
4222
+ const unsatisfied = call.dependencies.filter((dep) => !this.completedResults.has(dep));
4223
+ if (unsatisfied.length > 0) {
4224
+ this.logger.debug("Queueing gadget for later - waiting on dependencies", {
4225
+ gadgetName: call.gadgetName,
4226
+ invocationId: call.invocationId,
4227
+ waitingOn: unsatisfied
4228
+ });
4229
+ this.gadgetsAwaitingDependencies.set(call.invocationId, call);
4230
+ return;
4231
+ }
4232
+ }
4233
+ for await (const evt of this.executeGadgetGenerator(call)) {
4234
+ yield evt;
4235
+ }
4236
+ for await (const evt of this.processPendingGadgetsGenerator()) {
4237
+ yield evt;
4238
+ }
4239
+ }
4144
4240
  /**
4145
4241
  * Execute a gadget through the full hook lifecycle.
4146
4242
  * This is the core execution logic, extracted from processGadgetCall.
4243
+ * @deprecated Use executeGadgetGenerator for real-time streaming
4147
4244
  */
4148
4245
  async executeGadgetWithHooks(call) {
4149
4246
  const events = [];
@@ -4153,14 +4250,14 @@ var init_stream_processor = __esm({
4153
4250
  error: call.parseError,
4154
4251
  rawParameters: call.parametersRaw
4155
4252
  });
4156
- const shouldContinue = await this.checkContinueAfterError(
4253
+ const shouldContinue = await this.checkCanRecoverFromError(
4157
4254
  call.parseError,
4158
4255
  call.gadgetName,
4159
4256
  "parse",
4160
4257
  call.parameters
4161
4258
  );
4162
4259
  if (!shouldContinue) {
4163
- this.shouldStopExecution = true;
4260
+ this.executionHalted = true;
4164
4261
  }
4165
4262
  }
4166
4263
  let parameters = call.parameters ?? {};
@@ -4284,18 +4381,171 @@ var init_stream_processor = __esm({
4284
4381
  events.push({ type: "gadget_result", result });
4285
4382
  if (result.error) {
4286
4383
  const errorType = this.determineErrorType(call, result);
4287
- const shouldContinue = await this.checkContinueAfterError(
4384
+ const shouldContinue = await this.checkCanRecoverFromError(
4288
4385
  result.error,
4289
4386
  result.gadgetName,
4290
4387
  errorType,
4291
4388
  result.parameters
4292
4389
  );
4293
4390
  if (!shouldContinue) {
4294
- this.shouldStopExecution = true;
4391
+ this.executionHalted = true;
4295
4392
  }
4296
4393
  }
4297
4394
  return events;
4298
4395
  }
4396
+ /**
4397
+ * Execute a gadget and yield the result event.
4398
+ * Generator version that yields gadget_result immediately when execution completes.
4399
+ */
4400
+ async *executeGadgetGenerator(call) {
4401
+ if (call.parseError) {
4402
+ this.logger.warn("Gadget has parse error", {
4403
+ gadgetName: call.gadgetName,
4404
+ error: call.parseError,
4405
+ rawParameters: call.parametersRaw
4406
+ });
4407
+ const shouldContinue = await this.checkCanRecoverFromError(
4408
+ call.parseError,
4409
+ call.gadgetName,
4410
+ "parse",
4411
+ call.parameters
4412
+ );
4413
+ if (!shouldContinue) {
4414
+ this.executionHalted = true;
4415
+ }
4416
+ }
4417
+ let parameters = call.parameters ?? {};
4418
+ if (this.hooks.interceptors?.interceptGadgetParameters) {
4419
+ const context = {
4420
+ iteration: this.iteration,
4421
+ gadgetName: call.gadgetName,
4422
+ invocationId: call.invocationId,
4423
+ logger: this.logger
4424
+ };
4425
+ parameters = this.hooks.interceptors.interceptGadgetParameters(parameters, context);
4426
+ }
4427
+ call.parameters = parameters;
4428
+ let shouldSkip = false;
4429
+ let syntheticResult;
4430
+ if (this.hooks.controllers?.beforeGadgetExecution) {
4431
+ const context = {
4432
+ iteration: this.iteration,
4433
+ gadgetName: call.gadgetName,
4434
+ invocationId: call.invocationId,
4435
+ parameters,
4436
+ logger: this.logger
4437
+ };
4438
+ const action = await this.hooks.controllers.beforeGadgetExecution(context);
4439
+ validateBeforeGadgetExecutionAction(action);
4440
+ if (action.action === "skip") {
4441
+ shouldSkip = true;
4442
+ syntheticResult = action.syntheticResult;
4443
+ this.logger.info("Controller skipped gadget execution", {
4444
+ gadgetName: call.gadgetName
4445
+ });
4446
+ }
4447
+ }
4448
+ const startObservers = [];
4449
+ if (this.hooks.observers?.onGadgetExecutionStart) {
4450
+ startObservers.push(async () => {
4451
+ const context = {
4452
+ iteration: this.iteration,
4453
+ gadgetName: call.gadgetName,
4454
+ invocationId: call.invocationId,
4455
+ parameters,
4456
+ logger: this.logger
4457
+ };
4458
+ await this.hooks.observers.onGadgetExecutionStart(context);
4459
+ });
4460
+ }
4461
+ await this.runObserversInParallel(startObservers);
4462
+ let result;
4463
+ if (shouldSkip) {
4464
+ result = {
4465
+ gadgetName: call.gadgetName,
4466
+ invocationId: call.invocationId,
4467
+ parameters,
4468
+ result: syntheticResult ?? "Execution skipped",
4469
+ executionTimeMs: 0
4470
+ };
4471
+ } else {
4472
+ result = await this.executor.execute(call);
4473
+ }
4474
+ const originalResult = result.result;
4475
+ if (result.result && this.hooks.interceptors?.interceptGadgetResult) {
4476
+ const context = {
4477
+ iteration: this.iteration,
4478
+ gadgetName: result.gadgetName,
4479
+ invocationId: result.invocationId,
4480
+ parameters,
4481
+ executionTimeMs: result.executionTimeMs,
4482
+ logger: this.logger
4483
+ };
4484
+ result.result = this.hooks.interceptors.interceptGadgetResult(result.result, context);
4485
+ }
4486
+ if (this.hooks.controllers?.afterGadgetExecution) {
4487
+ const context = {
4488
+ iteration: this.iteration,
4489
+ gadgetName: result.gadgetName,
4490
+ invocationId: result.invocationId,
4491
+ parameters,
4492
+ result: result.result,
4493
+ error: result.error,
4494
+ executionTimeMs: result.executionTimeMs,
4495
+ logger: this.logger
4496
+ };
4497
+ const action = await this.hooks.controllers.afterGadgetExecution(context);
4498
+ validateAfterGadgetExecutionAction(action);
4499
+ if (action.action === "recover" && result.error) {
4500
+ this.logger.info("Controller recovered from gadget error", {
4501
+ gadgetName: result.gadgetName,
4502
+ originalError: result.error
4503
+ });
4504
+ result = {
4505
+ ...result,
4506
+ error: void 0,
4507
+ result: action.fallbackResult
4508
+ };
4509
+ }
4510
+ }
4511
+ const completeObservers = [];
4512
+ if (this.hooks.observers?.onGadgetExecutionComplete) {
4513
+ completeObservers.push(async () => {
4514
+ const context = {
4515
+ iteration: this.iteration,
4516
+ gadgetName: result.gadgetName,
4517
+ invocationId: result.invocationId,
4518
+ parameters,
4519
+ originalResult,
4520
+ finalResult: result.result,
4521
+ error: result.error,
4522
+ executionTimeMs: result.executionTimeMs,
4523
+ breaksLoop: result.breaksLoop,
4524
+ cost: result.cost,
4525
+ logger: this.logger
4526
+ };
4527
+ await this.hooks.observers.onGadgetExecutionComplete(context);
4528
+ });
4529
+ }
4530
+ await this.runObserversInParallel(completeObservers);
4531
+ this.completedResults.set(result.invocationId, result);
4532
+ if (result.error) {
4533
+ this.failedInvocations.add(result.invocationId);
4534
+ }
4535
+ yield { type: "gadget_result", result };
4536
+ if (result.error) {
4537
+ const errorType = this.determineErrorType(call, result);
4538
+ const shouldContinue = await this.checkCanRecoverFromError(
4539
+ result.error,
4540
+ result.gadgetName,
4541
+ errorType,
4542
+ result.parameters
4543
+ );
4544
+ if (!shouldContinue) {
4545
+ this.executionHalted = true;
4546
+ }
4547
+ }
4548
+ }
4299
4549
  /**
4300
4550
  * Handle a gadget that cannot execute because a dependency failed.
4301
4551
  * Calls the onDependencySkipped controller to allow customization.
@@ -4378,11 +4628,11 @@ var init_stream_processor = __esm({
4378
4628
  async processPendingGadgets() {
4379
4629
  const events = [];
4380
4630
  let progress = true;
4381
- while (progress && this.pendingGadgets.size > 0) {
4631
+ while (progress && this.gadgetsAwaitingDependencies.size > 0) {
4382
4632
  progress = false;
4383
4633
  const readyToExecute = [];
4384
4634
  const readyToSkip = [];
4385
- for (const [invocationId, call] of this.pendingGadgets) {
4635
+ for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
4386
4636
  const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
4387
4637
  if (failedDep) {
4388
4638
  readyToSkip.push({ call, failedDep });
@@ -4394,7 +4644,7 @@ var init_stream_processor = __esm({
4394
4644
  }
4395
4645
  }
4396
4646
  for (const { call, failedDep } of readyToSkip) {
4397
- this.pendingGadgets.delete(call.invocationId);
4647
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4398
4648
  const skipEvents = await this.handleFailedDependency(call, failedDep);
4399
4649
  events.push(...skipEvents);
4400
4650
  progress = true;
@@ -4405,7 +4655,7 @@ var init_stream_processor = __esm({
4405
4655
  invocationIds: readyToExecute.map((c) => c.invocationId)
4406
4656
  });
4407
4657
  for (const call of readyToExecute) {
4408
- this.pendingGadgets.delete(call.invocationId);
4658
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4409
4659
  }
4410
4660
  const executePromises = readyToExecute.map((call) => this.executeGadgetWithHooks(call));
4411
4661
  const results = await Promise.all(executePromises);
@@ -4415,9 +4665,9 @@ var init_stream_processor = __esm({
4415
4665
  progress = true;
4416
4666
  }
4417
4667
  }
4418
- if (this.pendingGadgets.size > 0) {
4419
- const pendingIds = new Set(this.pendingGadgets.keys());
4420
- for (const [invocationId, call] of this.pendingGadgets) {
4668
+ if (this.gadgetsAwaitingDependencies.size > 0) {
4669
+ const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
4670
+ for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
4421
4671
  const missingDeps = call.dependencies.filter((dep) => !this.completedResults.has(dep));
4422
4672
  const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
4423
4673
  const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
@@ -4448,10 +4698,103 @@ var init_stream_processor = __esm({
4448
4698
  };
4449
4699
  events.push(skipEvent);
4450
4700
  }
4451
- this.pendingGadgets.clear();
4701
+ this.gadgetsAwaitingDependencies.clear();
4452
4702
  }
4453
4703
  return events;
4454
4704
  }
4705
+ /**
4706
+ * Process pending gadgets, yielding events in real-time.
4707
+ * Generator version that yields events as gadgets complete.
4708
+ *
4709
+ * Note: Gadgets are still executed in parallel for efficiency,
4710
+ * but results are yielded as they become available.
4711
+ */
4712
+ async *processPendingGadgetsGenerator() {
4713
+ let progress = true;
4714
+ while (progress && this.gadgetsAwaitingDependencies.size > 0) {
4715
+ progress = false;
4716
+ const readyToExecute = [];
4717
+ const readyToSkip = [];
4718
+ for (const [_invocationId, call] of this.gadgetsAwaitingDependencies) {
4719
+ const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
4720
+ if (failedDep) {
4721
+ readyToSkip.push({ call, failedDep });
4722
+ continue;
4723
+ }
4724
+ const allSatisfied = call.dependencies.every((dep) => this.completedResults.has(dep));
4725
+ if (allSatisfied) {
4726
+ readyToExecute.push(call);
4727
+ }
4728
+ }
4729
+ for (const { call, failedDep } of readyToSkip) {
4730
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4731
+ const skipEvents = await this.handleFailedDependency(call, failedDep);
4732
+ for (const evt of skipEvents) {
4733
+ yield evt;
4734
+ }
4735
+ progress = true;
4736
+ }
4737
+ if (readyToExecute.length > 0) {
4738
+ this.logger.debug("Executing ready gadgets in parallel", {
4739
+ count: readyToExecute.length,
4740
+ invocationIds: readyToExecute.map((c) => c.invocationId)
4741
+ });
4742
+ for (const call of readyToExecute) {
4743
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4744
+ }
4745
+ const eventSets = await Promise.all(
4746
+ readyToExecute.map(async (call) => {
4747
+ const events = [];
4748
+ for await (const evt of this.executeGadgetGenerator(call)) {
4749
+ events.push(evt);
4750
+ }
4751
+ return events;
4752
+ })
4753
+ );
4754
+ for (const events of eventSets) {
4755
+ for (const evt of events) {
4756
+ yield evt;
4757
+ }
4758
+ }
4759
+ progress = true;
4760
+ }
4761
+ }
4762
+ if (this.gadgetsAwaitingDependencies.size > 0) {
4763
+ const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
4764
+ for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
4765
+ const missingDeps = call.dependencies.filter((dep) => !this.completedResults.has(dep));
4766
+ const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
4767
+ const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
4768
+ let errorMessage;
4769
+ let logLevel = "warn";
4770
+ if (circularDeps.length > 0 && trulyMissingDeps.length > 0) {
4771
+ errorMessage = `Dependencies unresolvable: circular=[${circularDeps.join(", ")}], missing=[${trulyMissingDeps.join(", ")}]`;
4772
+ logLevel = "error";
4773
+ } else if (circularDeps.length > 0) {
4774
+ errorMessage = `Circular dependency detected: "${invocationId}" depends on "${circularDeps[0]}" which also depends on "${invocationId}" (directly or indirectly)`;
4775
+ } else {
4776
+ errorMessage = `Dependency "${missingDeps[0]}" was never executed - check that the invocation ID exists and is spelled correctly`;
4777
+ }
4778
+ this.logger[logLevel]("Gadget has unresolvable dependencies", {
4779
+ gadgetName: call.gadgetName,
4780
+ invocationId,
4781
+ circularDependencies: circularDeps,
4782
+ missingDependencies: trulyMissingDeps
4783
+ });
4784
+ this.failedInvocations.add(invocationId);
4785
+ const skipEvent = {
4786
+ type: "gadget_skipped",
4787
+ gadgetName: call.gadgetName,
4788
+ invocationId,
4789
+ parameters: call.parameters ?? {},
4790
+ failedDependency: missingDeps[0],
4791
+ failedDependencyError: errorMessage
4792
+ };
4793
+ yield skipEvent;
4794
+ }
4795
+ this.gadgetsAwaitingDependencies.clear();
4796
+ }
4797
+ }
4455
4798
  /**
4456
4799
  * Safely execute an observer, catching and logging any errors.
4457
4800
  * Observers are non-critical, so errors are logged but don't crash the system.
@@ -4478,19 +4821,19 @@ var init_stream_processor = __esm({
4478
4821
  );
4479
4822
  }
4480
4823
  /**
4481
- * Check if execution should continue after an error.
4824
+ * Check if execution can recover from an error.
4482
4825
  *
4483
4826
  * Returns true if we should continue processing subsequent gadgets, false if we should stop.
4484
4827
  *
4485
4828
  * Logic:
4486
- * - If custom shouldContinueAfterError is provided, use it
4829
+ * - If custom canRecoverFromGadgetError is provided, use it
4487
4830
  * - Otherwise, use stopOnGadgetError config:
4488
4831
  * - stopOnGadgetError=true → return false (stop execution)
4489
4832
  * - stopOnGadgetError=false → return true (continue execution)
4490
4833
  */
4491
- async checkContinueAfterError(error, gadgetName, errorType, parameters) {
4492
- if (this.shouldContinueAfterError) {
4493
- return await this.shouldContinueAfterError({
4834
+ async checkCanRecoverFromError(error, gadgetName, errorType, parameters) {
4835
+ if (this.canRecoverFromGadgetError) {
4836
+ return await this.canRecoverFromGadgetError({
4494
4837
  error,
4495
4838
  gadgetName,
4496
4839
  errorType,
@@ -4553,14 +4896,14 @@ var init_agent = __esm({
4553
4896
  gadgetStartPrefix;
4554
4897
  gadgetEndPrefix;
4555
4898
  gadgetArgPrefix;
4556
- onHumanInputRequired;
4899
+ requestHumanInput;
4557
4900
  textOnlyHandler;
4558
4901
  textWithGadgetsHandler;
4559
4902
  stopOnGadgetError;
4560
- shouldContinueAfterError;
4903
+ canRecoverFromGadgetError;
4561
4904
  defaultGadgetTimeoutMs;
4562
4905
  defaultMaxTokens;
4563
- userPromptProvided;
4906
+ hasUserPrompt;
4564
4907
  // Gadget output limiting
4565
4908
  outputStore;
4566
4909
  outputLimitEnabled;
@@ -4571,6 +4914,11 @@ var init_agent = __esm({
4571
4914
  mediaStore;
4572
4915
  // Cancellation
4573
4916
  signal;
4917
+ // Subagent configuration
4918
+ agentContextConfig;
4919
+ subagentConfig;
4920
+ // Nested event callback for subagent gadgets
4921
+ onNestedEvent;
4574
4922
  /**
4575
4923
  * Creates a new Agent instance.
4576
4924
  * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
@@ -4590,11 +4938,11 @@ var init_agent = __esm({
4590
4938
  this.gadgetStartPrefix = options.gadgetStartPrefix;
4591
4939
  this.gadgetEndPrefix = options.gadgetEndPrefix;
4592
4940
  this.gadgetArgPrefix = options.gadgetArgPrefix;
4593
- this.onHumanInputRequired = options.onHumanInputRequired;
4941
+ this.requestHumanInput = options.requestHumanInput;
4594
4942
  this.textOnlyHandler = options.textOnlyHandler ?? "terminate";
4595
4943
  this.textWithGadgetsHandler = options.textWithGadgetsHandler;
4596
4944
  this.stopOnGadgetError = options.stopOnGadgetError ?? true;
4597
- this.shouldContinueAfterError = options.shouldContinueAfterError;
4945
+ this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
4598
4946
  this.defaultGadgetTimeoutMs = options.defaultGadgetTimeoutMs;
4599
4947
  this.defaultMaxTokens = this.resolveMaxTokensFromCatalog(options.model);
4600
4948
  this.outputLimitEnabled = options.gadgetOutputLimit ?? DEFAULT_GADGET_OUTPUT_LIMIT;
@@ -4610,7 +4958,7 @@ var init_agent = __esm({
4610
4958
  createGadgetOutputViewer(this.outputStore, this.outputLimitCharLimit)
4611
4959
  );
4612
4960
  }
4613
- this.hooks = this.mergeOutputLimiterHook(options.hooks);
4961
+ this.hooks = this.chainOutputLimiterWithUserHooks(options.hooks);
4614
4962
  const baseBuilder = new LLMMessageBuilder(options.promptConfig);
4615
4963
  if (options.systemPrompt) {
4616
4964
  baseBuilder.addSystem(options.systemPrompt);
@@ -4630,7 +4978,7 @@ var init_agent = __esm({
4630
4978
  endPrefix: options.gadgetEndPrefix,
4631
4979
  argPrefix: options.gadgetArgPrefix
4632
4980
  });
4633
- this.userPromptProvided = !!options.userPrompt;
4981
+ this.hasUserPrompt = !!options.userPrompt;
4634
4982
  if (options.userPrompt) {
4635
4983
  this.conversation.addUserMessage(options.userPrompt);
4636
4984
  }
@@ -4643,6 +4991,12 @@ var init_agent = __esm({
4643
4991
  );
4644
4992
  }
4645
4993
  this.signal = options.signal;
4994
+ this.agentContextConfig = {
4995
+ model: this.model,
4996
+ temperature: this.temperature
4997
+ };
4998
+ this.subagentConfig = options.subagentConfig;
4999
+ this.onNestedEvent = options.onNestedEvent;
4646
5000
  }
4647
5001
  /**
4648
5002
  * Get the gadget registry for this agent.
@@ -4749,7 +5103,7 @@ var init_agent = __esm({
4749
5103
  * @throws {Error} If no user prompt was provided (when using build() without ask())
4750
5104
  */
4751
5105
  async *run() {
4752
- if (!this.userPromptProvided) {
5106
+ if (!this.hasUserPrompt) {
4753
5107
  throw new Error(
4754
5108
  "No user prompt provided. Use .ask(prompt) instead of .build(), or call agent.run() after providing a prompt."
4755
5109
  );
@@ -4866,17 +5220,37 @@ var init_agent = __esm({
4866
5220
  gadgetArgPrefix: this.gadgetArgPrefix,
4867
5221
  hooks: this.hooks,
4868
5222
  logger: this.logger.getSubLogger({ name: "stream-processor" }),
4869
- onHumanInputRequired: this.onHumanInputRequired,
5223
+ requestHumanInput: this.requestHumanInput,
4870
5224
  stopOnGadgetError: this.stopOnGadgetError,
4871
- shouldContinueAfterError: this.shouldContinueAfterError,
5225
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
4872
5226
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
4873
5227
  client: this.client,
4874
- mediaStore: this.mediaStore
5228
+ mediaStore: this.mediaStore,
5229
+ agentConfig: this.agentContextConfig,
5230
+ subagentConfig: this.subagentConfig,
5231
+ onNestedEvent: this.onNestedEvent
4875
5232
  });
4876
- const result = await processor.process(stream2);
4877
- for (const output of result.outputs) {
4878
- yield output;
5233
+ let streamMetadata = null;
5234
+ let gadgetCallCount = 0;
5235
+ const textOutputs = [];
5236
+ const gadgetResults = [];
5237
+ for await (const event of processor.process(stream2)) {
5238
+ if (event.type === "stream_complete") {
5239
+ streamMetadata = event;
5240
+ continue;
5241
+ }
5242
+ if (event.type === "text") {
5243
+ textOutputs.push(event.content);
5244
+ } else if (event.type === "gadget_result") {
5245
+ gadgetCallCount++;
5246
+ gadgetResults.push(event);
5247
+ }
5248
+ yield event;
4879
5249
  }
5250
+ if (!streamMetadata) {
5251
+ throw new Error("Stream processing completed without metadata event");
5252
+ }
5253
+ const result = streamMetadata;
4880
5254
  this.logger.info("LLM response completed", {
4881
5255
  finishReason: result.finishReason,
4882
5256
  usage: result.usage,
@@ -4901,9 +5275,6 @@ var init_agent = __esm({
4901
5275
  });
4902
5276
  let finalMessage = result.finalMessage;
4903
5277
  if (this.hooks.controllers?.afterLLMCall) {
4904
- const gadgetCallCount = result.outputs.filter(
4905
- (output) => output.type === "gadget_result"
4906
- ).length;
4907
5278
  const context = {
4908
5279
  iteration: currentIteration,
4909
5280
  maxIterations: this.maxIterations,
@@ -4924,31 +5295,29 @@ var init_agent = __esm({
4924
5295
  if (msg.role === "user") {
4925
5296
  this.conversation.addUserMessage(msg.content);
4926
5297
  } else if (msg.role === "assistant") {
4927
- this.conversation.addAssistantMessage(extractText(msg.content));
5298
+ this.conversation.addAssistantMessage(extractMessageText(msg.content));
4928
5299
  } else if (msg.role === "system") {
4929
- this.conversation.addUserMessage(`[System] ${extractText(msg.content)}`);
5300
+ this.conversation.addUserMessage(`[System] ${extractMessageText(msg.content)}`);
4930
5301
  }
4931
5302
  }
4932
5303
  }
4933
5304
  }
4934
5305
  if (result.didExecuteGadgets) {
4935
5306
  if (this.textWithGadgetsHandler) {
4936
- const textContent = result.outputs.filter(
4937
- (output) => output.type === "text"
4938
- ).map((output) => output.content).join("");
5307
+ const textContent = textOutputs.join("");
4939
5308
  if (textContent.trim()) {
4940
5309
  const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
4941
- this.conversation.addGadgetCall(
5310
+ this.conversation.addGadgetCallResult(
4942
5311
  gadgetName,
4943
5312
  parameterMapping(textContent),
4944
5313
  resultMapping ? resultMapping(textContent) : textContent
4945
5314
  );
4946
5315
  }
4947
5316
  }
4948
- for (const output of result.outputs) {
5317
+ for (const output of gadgetResults) {
4949
5318
  if (output.type === "gadget_result") {
4950
5319
  const gadgetResult = output.result;
4951
- this.conversation.addGadgetCall(
5320
+ this.conversation.addGadgetCallResult(
4952
5321
  gadgetResult.gadgetName,
4953
5322
  gadgetResult.parameters,
4954
5323
  gadgetResult.error ?? gadgetResult.result ?? "",
@@ -4959,7 +5328,7 @@ var init_agent = __esm({
4959
5328
  }
4960
5329
  } else {
4961
5330
  if (finalMessage.trim()) {
4962
- this.conversation.addGadgetCall(
5331
+ this.conversation.addGadgetCallResult(
4963
5332
  "TellUser",
4964
5333
  { message: finalMessage, done: false, type: "info" },
4965
5334
  `\u2139\uFE0F ${finalMessage}`
@@ -5085,10 +5454,10 @@ var init_agent = __esm({
5085
5454
  return this.client.modelRegistry.getModelLimits(unprefixedModelId)?.maxOutputTokens;
5086
5455
  }
5087
5456
  /**
5088
- * Merge the output limiter interceptor into user-provided hooks.
5457
+ * Chain the output limiter interceptor with user-provided hooks.
5089
5458
  * The limiter runs first, then chains to any user interceptor.
5090
5459
  */
5091
- mergeOutputLimiterHook(userHooks) {
5460
+ chainOutputLimiterWithUserHooks(userHooks) {
5092
5461
  if (!this.outputLimitEnabled) {
5093
5462
  return userHooks ?? {};
5094
5463
  }
@@ -5442,9 +5811,9 @@ var init_base_provider = __esm({
5442
5811
  */
5443
5812
  async *stream(options, descriptor, spec) {
5444
5813
  const preparedMessages = this.prepareMessages(options.messages);
5445
- const payload = this.buildRequestPayload(options, descriptor, spec, preparedMessages);
5814
+ const payload = this.buildApiRequest(options, descriptor, spec, preparedMessages);
5446
5815
  const rawStream = await this.executeStreamRequest(payload, options.signal);
5447
- yield* this.wrapStream(rawStream);
5816
+ yield* this.normalizeProviderStream(rawStream);
5448
5817
  }
5449
5818
  /**
5450
5819
  * Prepare messages for the request.
@@ -5544,11 +5913,11 @@ var init_anthropic = __esm({
5544
5913
  "Anthropic does not support speech generation. Use OpenAI (TTS) or Google Gemini (TTS) instead."
5545
5914
  );
5546
5915
  }
5547
- buildRequestPayload(options, descriptor, spec, messages) {
5916
+ buildApiRequest(options, descriptor, spec, messages) {
5548
5917
  const systemMessages = messages.filter((message) => message.role === "system");
5549
5918
  const system = systemMessages.length > 0 ? systemMessages.map((m, index) => ({
5550
5919
  type: "text",
5551
- text: extractText(m.content),
5920
+ text: extractMessageText(m.content),
5552
5921
  // Add cache_control to the LAST system message block
5553
5922
  ...index === systemMessages.length - 1 ? { cache_control: { type: "ephemeral" } } : {}
5554
5923
  })) : void 0;
@@ -5585,7 +5954,7 @@ var init_anthropic = __esm({
5585
5954
  * Handles text, images (base64 only), and applies cache_control.
5586
5955
  */
5587
5956
  convertToAnthropicContent(content, addCacheControl) {
5588
- const parts = normalizeContent(content);
5957
+ const parts = normalizeMessageContent(content);
5589
5958
  return parts.map((part, index) => {
5590
5959
  const isLastPart = index === parts.length - 1;
5591
5960
  const cacheControl = addCacheControl && isLastPart ? { cache_control: { type: "ephemeral" } } : {};
@@ -5631,7 +6000,7 @@ var init_anthropic = __esm({
5631
6000
  const stream2 = await client.messages.create(payload, signal ? { signal } : void 0);
5632
6001
  return stream2;
5633
6002
  }
5634
- async *wrapStream(iterable) {
6003
+ async *normalizeProviderStream(iterable) {
5635
6004
  const stream2 = iterable;
5636
6005
  let inputTokens = 0;
5637
6006
  let cachedInputTokens = 0;
@@ -5708,7 +6077,7 @@ var init_anthropic = __esm({
5708
6077
  async countTokens(messages, descriptor, _spec) {
5709
6078
  const client = this.client;
5710
6079
  const systemMessages = messages.filter((message) => message.role === "system");
5711
- const system = systemMessages.length > 0 ? systemMessages.map((m) => extractText(m.content)).join("\n\n") : void 0;
6080
+ const system = systemMessages.length > 0 ? systemMessages.map((m) => extractMessageText(m.content)).join("\n\n") : void 0;
5712
6081
  const conversation = messages.filter(
5713
6082
  (message) => message.role !== "system"
5714
6083
  ).map((message) => ({
@@ -5730,7 +6099,7 @@ var init_anthropic = __esm({
5730
6099
  let totalChars = 0;
5731
6100
  let imageCount = 0;
5732
6101
  for (const msg of messages) {
5733
- const parts = normalizeContent(msg.content);
6102
+ const parts = normalizeMessageContent(msg.content);
5734
6103
  for (const part of parts) {
5735
6104
  if (part.type === "text") {
5736
6105
  totalChars += part.text.length;
@@ -6421,7 +6790,7 @@ var init_gemini = __esm({
6421
6790
  format: spec?.defaultFormat ?? "wav"
6422
6791
  };
6423
6792
  }
6424
- buildRequestPayload(options, descriptor, _spec, messages) {
6793
+ buildApiRequest(options, descriptor, _spec, messages) {
6425
6794
  const contents = this.convertMessagesToContents(messages);
6426
6795
  const generationConfig = this.buildGenerationConfig(options);
6427
6796
  const config = {
@@ -6473,7 +6842,7 @@ var init_gemini = __esm({
6473
6842
  if (message.role === "system") {
6474
6843
  expandedMessages.push({
6475
6844
  role: "user",
6476
- content: extractText(message.content)
6845
+ content: extractMessageText(message.content)
6477
6846
  });
6478
6847
  expandedMessages.push({
6479
6848
  role: "assistant",
@@ -6523,7 +6892,7 @@ var init_gemini = __esm({
6523
6892
  * Handles text, images, and audio (Gemini supports all three).
6524
6893
  */
6525
6894
  convertToGeminiParts(content) {
6526
- const parts = normalizeContent(content);
6895
+ const parts = normalizeMessageContent(content);
6527
6896
  return parts.map((part) => {
6528
6897
  if (part.type === "text") {
6529
6898
  return { text: part.text };
@@ -6568,10 +6937,10 @@ var init_gemini = __esm({
6568
6937
  }
6569
6938
  return Object.keys(config).length > 0 ? config : null;
6570
6939
  }
6571
- async *wrapStream(iterable) {
6940
+ async *normalizeProviderStream(iterable) {
6572
6941
  const stream2 = iterable;
6573
6942
  for await (const chunk of stream2) {
6574
- const text3 = this.extractText(chunk);
6943
+ const text3 = this.extractMessageText(chunk);
6575
6944
  if (text3) {
6576
6945
  yield { text: text3, rawEvent: chunk };
6577
6946
  }
@@ -6582,7 +6951,7 @@ var init_gemini = __esm({
6582
6951
  }
6583
6952
  }
6584
6953
  }
6585
- extractText(chunk) {
6954
+ extractMessageText(chunk) {
6586
6955
  if (!chunk?.candidates) {
6587
6956
  return "";
6588
6957
  }
@@ -6647,7 +7016,7 @@ var init_gemini = __esm({
6647
7016
  let totalChars = 0;
6648
7017
  let mediaCount = 0;
6649
7018
  for (const msg of messages) {
6650
- const parts = normalizeContent(msg.content);
7019
+ const parts = normalizeMessageContent(msg.content);
6651
7020
  for (const part of parts) {
6652
7021
  if (part.type === "text") {
6653
7022
  totalChars += part.text.length;
@@ -7393,7 +7762,7 @@ var init_openai = __esm({
7393
7762
  format
7394
7763
  };
7395
7764
  }
7396
- buildRequestPayload(options, descriptor, spec, messages) {
7765
+ buildApiRequest(options, descriptor, spec, messages) {
7397
7766
  const { maxTokens, temperature, topP, stopSequences, extra } = options;
7398
7767
  const supportsTemperature = spec?.metadata?.supportsTemperature !== false;
7399
7768
  const shouldIncludeTemperature = typeof temperature === "number" && supportsTemperature;
@@ -7428,7 +7797,7 @@ var init_openai = __esm({
7428
7797
  ...message.name ? { name: message.name } : {}
7429
7798
  };
7430
7799
  }
7431
- const textContent = typeof message.content === "string" ? message.content : extractText(message.content);
7800
+ const textContent = typeof message.content === "string" ? message.content : extractMessageText(message.content);
7432
7801
  if (role === "system") {
7433
7802
  return {
7434
7803
  role: "system",
@@ -7488,7 +7857,7 @@ var init_openai = __esm({
7488
7857
  const stream2 = await client.chat.completions.create(payload, signal ? { signal } : void 0);
7489
7858
  return stream2;
7490
7859
  }
7491
- async *wrapStream(iterable) {
7860
+ async *normalizeProviderStream(iterable) {
7492
7861
  const stream2 = iterable;
7493
7862
  for await (const chunk of stream2) {
7494
7863
  const text3 = chunk.choices.map((choice) => choice.delta?.content ?? "").join("");
@@ -7546,9 +7915,9 @@ var init_openai = __esm({
7546
7915
  tokenCount += OPENAI_MESSAGE_OVERHEAD_TOKENS;
7547
7916
  const roleText = ROLE_MAP[message.role];
7548
7917
  tokenCount += encoding.encode(roleText).length;
7549
- const textContent = extractText(message.content);
7918
+ const textContent = extractMessageText(message.content);
7550
7919
  tokenCount += encoding.encode(textContent).length;
7551
- const parts = normalizeContent(message.content);
7920
+ const parts = normalizeMessageContent(message.content);
7552
7921
  for (const part of parts) {
7553
7922
  if (part.type === "image") {
7554
7923
  imageCount++;
@@ -7573,7 +7942,7 @@ var init_openai = __esm({
7573
7942
  let totalChars = 0;
7574
7943
  let imageCount = 0;
7575
7944
  for (const msg of messages) {
7576
- const parts = normalizeContent(msg.content);
7945
+ const parts = normalizeMessageContent(msg.content);
7577
7946
  for (const part of parts) {
7578
7947
  if (part.type === "text") {
7579
7948
  totalChars += part.text.length;
@@ -8438,20 +8807,23 @@ var init_builder = __esm({
8438
8807
  promptConfig;
8439
8808
  gadgets = [];
8440
8809
  initialMessages = [];
8441
- onHumanInputRequired;
8810
+ requestHumanInput;
8442
8811
  gadgetStartPrefix;
8443
8812
  gadgetEndPrefix;
8444
8813
  gadgetArgPrefix;
8445
8814
  textOnlyHandler;
8446
8815
  textWithGadgetsHandler;
8447
8816
  stopOnGadgetError;
8448
- shouldContinueAfterError;
8817
+ canRecoverFromGadgetError;
8449
8818
  defaultGadgetTimeoutMs;
8450
8819
  gadgetOutputLimit;
8451
8820
  gadgetOutputLimitPercent;
8452
8821
  compactionConfig;
8453
8822
  signal;
8454
8823
  trailingMessage;
8824
+ subagentConfig;
8825
+ nestedEventCallback;
8826
+ parentContext;
8455
8827
  constructor(client) {
8456
8828
  this.client = client;
8457
8829
  }
@@ -8542,13 +8914,13 @@ var init_builder = __esm({
8542
8914
  *
8543
8915
  * @example
8544
8916
  * ```typescript
8545
- * .withPromptConfig({
8917
+ * .withPromptTemplateConfig({
8546
8918
  * mainInstruction: "Use the gadget markers below:",
8547
8919
  * rules: ["Always use markers", "Never use function calling"]
8548
8920
  * })
8549
8921
  * ```
8550
8922
  */
8551
- withPromptConfig(config) {
8923
+ withPromptTemplateConfig(config) {
8552
8924
  this.promptConfig = config;
8553
8925
  return this;
8554
8926
  }
@@ -8628,7 +9000,7 @@ var init_builder = __esm({
8628
9000
  * ```
8629
9001
  */
8630
9002
  onHumanInput(handler) {
8631
- this.onHumanInputRequired = handler;
9003
+ this.requestHumanInput = handler;
8632
9004
  return this;
8633
9005
  }
8634
9006
  /**
@@ -8763,9 +9135,9 @@ var init_builder = __esm({
8763
9135
  * Provides fine-grained control over whether to continue after different types of errors.
8764
9136
  * Overrides `stopOnGadgetError` when provided.
8765
9137
  *
8766
- * **Note:** This builder method configures the underlying `shouldContinueAfterError` option
9138
+ * **Note:** This builder method configures the underlying `canRecoverFromGadgetError` option
8767
9139
  * in `AgentOptions`. The method is named `withErrorHandler` for better developer experience,
8768
- * but maps to the `shouldContinueAfterError` property internally.
9140
+ * but maps to the `canRecoverFromGadgetError` property internally.
8769
9141
  *
8770
9142
  * @param handler - Function that decides whether to continue after an error.
8771
9143
  * Return `true` to continue execution, `false` to stop.
@@ -8786,7 +9158,7 @@ var init_builder = __esm({
8786
9158
  * ```
8787
9159
  */
8788
9160
  withErrorHandler(handler) {
8789
- this.shouldContinueAfterError = handler;
9161
+ this.canRecoverFromGadgetError = handler;
8790
9162
  return this;
8791
9163
  }
8792
9164
  /**
@@ -8927,6 +9299,95 @@ var init_builder = __esm({
8927
9299
  this.signal = signal;
8928
9300
  return this;
8929
9301
  }
9302
+ /**
9303
+ * Set subagent configuration overrides.
9304
+ *
9305
+ * Subagent gadgets (like BrowseWeb) can read these settings from ExecutionContext
9306
+ * to inherit model and other options from the CLI configuration.
9307
+ *
9308
+ * @param config - Subagent configuration map keyed by gadget name
9309
+ * @returns This builder for chaining
9310
+ *
9311
+ * @example
9312
+ * ```typescript
9313
+ * .withSubagentConfig({
9314
+ * BrowseWeb: { model: "inherit", maxIterations: 20, headless: true },
9315
+ * CodeAnalyzer: { model: "sonnet", maxIterations: 10 }
9316
+ * })
9317
+ * ```
9318
+ */
9319
+ withSubagentConfig(config) {
9320
+ this.subagentConfig = config;
9321
+ return this;
9322
+ }
9323
+ /**
9324
+ * Set the callback for nested subagent events.
9325
+ *
9326
+ * Subagent gadgets (like BrowseWeb) can use ExecutionContext.onNestedEvent
9327
+ * to report their internal LLM calls and gadget executions in real-time.
9328
+ * This callback receives those events, enabling hierarchical progress display.
9329
+ *
9330
+ * @param callback - Function to handle nested agent events
9331
+ * @returns This builder for chaining
9332
+ *
9333
+ * @example
9334
+ * ```typescript
9335
+ * .withNestedEventCallback((event) => {
9336
+ * if (event.type === "llm_call_start") {
9337
+ * console.log(` Nested LLM #${event.event.iteration} starting...`);
9338
+ * } else if (event.type === "gadget_call") {
9339
+ * console.log(` ⏵ ${event.event.call.gadgetName}...`);
9340
+ * }
9341
+ * })
9342
+ * ```
9343
+ */
9344
+ withNestedEventCallback(callback) {
9345
+ this.nestedEventCallback = callback;
9346
+ return this;
9347
+ }
9348
+ /**
9349
+ * Enable automatic nested event forwarding to parent agent.
9350
+ *
9351
+ * When building a subagent inside a gadget, call this method to automatically
9352
+ * forward all LLM calls and gadget events to the parent agent. This enables
9353
+ * hierarchical progress display without any manual event handling.
9354
+ *
9355
+ * The method extracts `invocationId` and `onNestedEvent` from the execution
9356
+ * context and sets up automatic forwarding via hooks and event wrapping.
9357
+ *
9358
+ * @param ctx - ExecutionContext passed to the gadget's execute() method
9359
+ * @param depth - Nesting depth (default: 1 for direct child)
9360
+ * @returns This builder for chaining
9361
+ *
9362
+ * @example
9363
+ * ```typescript
9364
+ * // In a subagent gadget like BrowseWeb - ONE LINE enables auto-forwarding:
9365
+ * execute: async (params, ctx) => {
9366
+ * const agent = new AgentBuilder(client)
9367
+ * .withModel(model)
9368
+ * .withGadgets(Navigate, Click, Screenshot)
9369
+ * .withParentContext(ctx) // <-- This is all you need!
9370
+ * .ask(params.task);
9371
+ *
9372
+ * for await (const event of agent.run()) {
9373
+ * // Events automatically forwarded - just process normally
9374
+ * if (event.type === "text") {
9375
+ * result = event.content;
9376
+ * }
9377
+ * }
9378
+ * }
9379
+ * ```
9380
+ */
9381
+ withParentContext(ctx, depth = 1) {
9382
+ if (ctx.onNestedEvent && ctx.invocationId) {
9383
+ this.parentContext = {
9384
+ invocationId: ctx.invocationId,
9385
+ onNestedEvent: ctx.onNestedEvent,
9386
+ depth
9387
+ };
9388
+ }
9389
+ return this;
9390
+ }
8930
9391
  /**
8931
9392
  * Add an ephemeral trailing message that appears at the end of each LLM request.
8932
9393
  *
@@ -8994,14 +9455,58 @@ ${endPrefix}`
8994
9455
  return this;
8995
9456
  }
8996
9457
  /**
8997
- * Compose the final hooks, including trailing message if configured.
9458
+ * Compose the final hooks, including:
9459
+ * - Trailing message injection (if configured)
9460
+ * - Nested event forwarding for LLM calls (if parentContext is set)
8998
9461
  */
8999
9462
  composeHooks() {
9463
+ let hooks = this.hooks;
9464
+ if (this.parentContext) {
9465
+ const { invocationId, onNestedEvent, depth } = this.parentContext;
9466
+ const existingOnLLMCallStart = hooks?.observers?.onLLMCallStart;
9467
+ const existingOnLLMCallComplete = hooks?.observers?.onLLMCallComplete;
9468
+ hooks = {
9469
+ ...hooks,
9470
+ observers: {
9471
+ ...hooks?.observers,
9472
+ onLLMCallStart: async (context) => {
9473
+ onNestedEvent({
9474
+ type: "llm_call_start",
9475
+ gadgetInvocationId: invocationId,
9476
+ depth,
9477
+ event: {
9478
+ iteration: context.iteration,
9479
+ model: context.options.model
9480
+ }
9481
+ });
9482
+ if (existingOnLLMCallStart) {
9483
+ await existingOnLLMCallStart(context);
9484
+ }
9485
+ },
9486
+ onLLMCallComplete: async (context) => {
9487
+ onNestedEvent({
9488
+ type: "llm_call_end",
9489
+ gadgetInvocationId: invocationId,
9490
+ depth,
9491
+ event: {
9492
+ iteration: context.iteration,
9493
+ model: context.options.model,
9494
+ outputTokens: context.usage?.outputTokens,
9495
+ finishReason: context.finishReason
9496
+ }
9497
+ });
9498
+ if (existingOnLLMCallComplete) {
9499
+ await existingOnLLMCallComplete(context);
9500
+ }
9501
+ }
9502
+ }
9503
+ };
9504
+ }
9000
9505
  if (!this.trailingMessage) {
9001
- return this.hooks;
9506
+ return hooks;
9002
9507
  }
9003
9508
  const trailingMsg = this.trailingMessage;
9004
- const existingBeforeLLMCall = this.hooks?.controllers?.beforeLLMCall;
9509
+ const existingBeforeLLMCall = hooks?.controllers?.beforeLLMCall;
9005
9510
  const trailingMessageController = async (ctx) => {
9006
9511
  const result = existingBeforeLLMCall ? await existingBeforeLLMCall(ctx) : { action: "proceed" };
9007
9512
  if (result.action === "skip") {
@@ -9016,9 +9521,9 @@ ${endPrefix}`
9016
9521
  };
9017
9522
  };
9018
9523
  return {
9019
- ...this.hooks,
9524
+ ...hooks,
9020
9525
  controllers: {
9021
- ...this.hooks?.controllers,
9526
+ ...hooks?.controllers,
9022
9527
  beforeLLMCall: trailingMessageController
9023
9528
  }
9024
9529
  };
@@ -9079,6 +9584,19 @@ ${endPrefix}`
9079
9584
  this.client = new LLMistClass();
9080
9585
  }
9081
9586
  const registry = GadgetRegistry.from(this.gadgets);
9587
+ let onNestedEvent = this.nestedEventCallback;
9588
+ if (this.parentContext) {
9589
+ const { invocationId, onNestedEvent: parentCallback, depth } = this.parentContext;
9590
+ const existingCallback = this.nestedEventCallback;
9591
+ onNestedEvent = (event) => {
9592
+ parentCallback({
9593
+ ...event,
9594
+ gadgetInvocationId: invocationId,
9595
+ depth: event.depth + depth
9596
+ });
9597
+ existingCallback?.(event);
9598
+ };
9599
+ }
9082
9600
  return {
9083
9601
  client: this.client,
9084
9602
  model: this.model ?? "openai:gpt-5-nano",
@@ -9091,19 +9609,21 @@ ${endPrefix}`
9091
9609
  hooks: this.composeHooks(),
9092
9610
  promptConfig: this.promptConfig,
9093
9611
  initialMessages: this.initialMessages,
9094
- onHumanInputRequired: this.onHumanInputRequired,
9612
+ requestHumanInput: this.requestHumanInput,
9095
9613
  gadgetStartPrefix: this.gadgetStartPrefix,
9096
9614
  gadgetEndPrefix: this.gadgetEndPrefix,
9097
9615
  gadgetArgPrefix: this.gadgetArgPrefix,
9098
9616
  textOnlyHandler: this.textOnlyHandler,
9099
9617
  textWithGadgetsHandler: this.textWithGadgetsHandler,
9100
9618
  stopOnGadgetError: this.stopOnGadgetError,
9101
- shouldContinueAfterError: this.shouldContinueAfterError,
9619
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
9102
9620
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
9103
9621
  gadgetOutputLimit: this.gadgetOutputLimit,
9104
9622
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
9105
9623
  compactionConfig: this.compactionConfig,
9106
- signal: this.signal
9624
+ signal: this.signal,
9625
+ subagentConfig: this.subagentConfig,
9626
+ onNestedEvent
9107
9627
  };
9108
9628
  }
9109
9629
  ask(userPrompt) {
@@ -9260,6 +9780,19 @@ ${endPrefix}`
9260
9780
  this.client = new LLMistClass();
9261
9781
  }
9262
9782
  const registry = GadgetRegistry.from(this.gadgets);
9783
+ let onNestedEvent = this.nestedEventCallback;
9784
+ if (this.parentContext) {
9785
+ const { invocationId, onNestedEvent: parentCallback, depth } = this.parentContext;
9786
+ const existingCallback = this.nestedEventCallback;
9787
+ onNestedEvent = (event) => {
9788
+ parentCallback({
9789
+ ...event,
9790
+ gadgetInvocationId: invocationId,
9791
+ depth: event.depth + depth
9792
+ });
9793
+ existingCallback?.(event);
9794
+ };
9795
+ }
9263
9796
  const options = {
9264
9797
  client: this.client,
9265
9798
  model: this.model ?? "openai:gpt-5-nano",
@@ -9272,19 +9805,21 @@ ${endPrefix}`
9272
9805
  hooks: this.composeHooks(),
9273
9806
  promptConfig: this.promptConfig,
9274
9807
  initialMessages: this.initialMessages,
9275
- onHumanInputRequired: this.onHumanInputRequired,
9808
+ requestHumanInput: this.requestHumanInput,
9276
9809
  gadgetStartPrefix: this.gadgetStartPrefix,
9277
9810
  gadgetEndPrefix: this.gadgetEndPrefix,
9278
9811
  gadgetArgPrefix: this.gadgetArgPrefix,
9279
9812
  textOnlyHandler: this.textOnlyHandler,
9280
9813
  textWithGadgetsHandler: this.textWithGadgetsHandler,
9281
9814
  stopOnGadgetError: this.stopOnGadgetError,
9282
- shouldContinueAfterError: this.shouldContinueAfterError,
9815
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
9283
9816
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
9284
9817
  gadgetOutputLimit: this.gadgetOutputLimit,
9285
9818
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
9286
9819
  compactionConfig: this.compactionConfig,
9287
- signal: this.signal
9820
+ signal: this.signal,
9821
+ subagentConfig: this.subagentConfig,
9822
+ onNestedEvent
9288
9823
  };
9289
9824
  return new Agent(AGENT_INTERNAL_KEY, options);
9290
9825
  }
@@ -9295,11 +9830,10 @@ ${endPrefix}`
9295
9830
  // src/index.ts
9296
9831
  var index_exports = {};
9297
9832
  __export(index_exports, {
9298
- AbortError: () => AbortError,
9833
+ AbortException: () => AbortException,
9834
+ AbstractGadget: () => AbstractGadget,
9299
9835
  AgentBuilder: () => AgentBuilder,
9300
9836
  AnthropicMessagesProvider: () => AnthropicMessagesProvider,
9301
- BaseGadget: () => BaseGadget,
9302
- BreakLoopException: () => BreakLoopException,
9303
9837
  CompactionManager: () => CompactionManager,
9304
9838
  ConversationManager: () => ConversationManager,
9305
9839
  DEFAULT_COMPACTION_CONFIG: () => DEFAULT_COMPACTION_CONFIG,
@@ -9307,12 +9841,13 @@ __export(index_exports, {
9307
9841
  DEFAULT_PROMPTS: () => DEFAULT_PROMPTS,
9308
9842
  DEFAULT_SUMMARIZATION_PROMPT: () => DEFAULT_SUMMARIZATION_PROMPT,
9309
9843
  Gadget: () => Gadget,
9844
+ GadgetCallParser: () => GadgetCallParser,
9310
9845
  GadgetExecutor: () => GadgetExecutor,
9311
9846
  GadgetOutputStore: () => GadgetOutputStore,
9312
9847
  GadgetRegistry: () => GadgetRegistry,
9313
9848
  GeminiGenerativeProvider: () => GeminiGenerativeProvider,
9314
9849
  HookPresets: () => HookPresets,
9315
- HumanInputException: () => HumanInputException,
9850
+ HumanInputRequiredException: () => HumanInputRequiredException,
9316
9851
  HybridStrategy: () => HybridStrategy,
9317
9852
  LLMMessageBuilder: () => LLMMessageBuilder,
9318
9853
  LLMist: () => LLMist,
@@ -9325,9 +9860,10 @@ __export(index_exports, {
9325
9860
  ModelRegistry: () => ModelRegistry,
9326
9861
  OpenAIChatProvider: () => OpenAIChatProvider,
9327
9862
  SlidingWindowStrategy: () => SlidingWindowStrategy,
9328
- StreamParser: () => StreamParser,
9329
9863
  StreamProcessor: () => StreamProcessor,
9330
9864
  SummarizationStrategy: () => SummarizationStrategy,
9865
+ TaskCompletionSignal: () => TaskCompletionSignal,
9866
+ TimeoutException: () => TimeoutException,
9331
9867
  audioFromBase64: () => audioFromBase64,
9332
9868
  audioFromBuffer: () => audioFromBuffer,
9333
9869
  collectEvents: () => collectEvents,
@@ -9339,7 +9875,7 @@ __export(index_exports, {
9339
9875
  createGeminiProviderFromEnv: () => createGeminiProviderFromEnv,
9340
9876
  createHints: () => createHints,
9341
9877
  createLogger: () => createLogger,
9342
- createMedia: () => createMedia,
9878
+ createMediaOutput: () => createMediaOutput,
9343
9879
  createMockAdapter: () => createMockAdapter,
9344
9880
  createMockClient: () => createMockClient,
9345
9881
  createMockStream: () => createMockStream,
@@ -9349,7 +9885,7 @@ __export(index_exports, {
9349
9885
  detectAudioMimeType: () => detectAudioMimeType,
9350
9886
  detectImageMimeType: () => detectImageMimeType,
9351
9887
  discoverProviderAdapters: () => discoverProviderAdapters,
9352
- extractText: () => extractText,
9888
+ extractMessageText: () => extractMessageText,
9353
9889
  getMockManager: () => getMockManager,
9354
9890
  getModelId: () => getModelId,
9355
9891
  getProvider: () => getProvider,
@@ -9363,7 +9899,7 @@ __export(index_exports, {
9363
9899
  isTextPart: () => isTextPart,
9364
9900
  iterationProgressHint: () => iterationProgressHint,
9365
9901
  mockLLM: () => mockLLM,
9366
- normalizeContent: () => normalizeContent,
9902
+ normalizeMessageContent: () => normalizeMessageContent,
9367
9903
  parallelGadgetHint: () => parallelGadgetHint,
9368
9904
  parseDataUrl: () => parseDataUrl,
9369
9905
  resolveHintTemplate: () => resolveHintTemplate,
@@ -10293,7 +10829,7 @@ init_registry();
10293
10829
  // src/gadgets/typed-gadget.ts
10294
10830
  init_gadget();
10295
10831
  function Gadget(config) {
10296
- class GadgetBase extends BaseGadget {
10832
+ class GadgetBase extends AbstractGadget {
10297
10833
  description = config.description;
10298
10834
  parameterSchema = config.schema;
10299
10835
  name = config.name;
@@ -10313,7 +10849,7 @@ function Gadget(config) {
10313
10849
 
10314
10850
  // src/gadgets/helpers.ts
10315
10851
  init_input_content();
10316
- function createMedia(kind, data, mimeType, options) {
10852
+ function createMediaOutput(kind, data, mimeType, options) {
10317
10853
  const buffer = data instanceof Buffer ? data : Buffer.from(data);
10318
10854
  return {
10319
10855
  kind,
@@ -11017,7 +11553,7 @@ var MockBuilder = class {
11017
11553
  whenMessageContains(text3) {
11018
11554
  this.matchers.push(
11019
11555
  (ctx) => ctx.messages.some(
11020
- (msg) => extractText(msg.content).toLowerCase().includes(text3.toLowerCase())
11556
+ (msg) => extractMessageText(msg.content).toLowerCase().includes(text3.toLowerCase())
11021
11557
  )
11022
11558
  );
11023
11559
  return this;
@@ -11032,7 +11568,7 @@ var MockBuilder = class {
11032
11568
  this.matchers.push((ctx) => {
11033
11569
  const lastMsg = ctx.messages[ctx.messages.length - 1];
11034
11570
  if (!lastMsg) return false;
11035
- return extractText(lastMsg.content).toLowerCase().includes(text3.toLowerCase());
11571
+ return extractMessageText(lastMsg.content).toLowerCase().includes(text3.toLowerCase());
11036
11572
  });
11037
11573
  return this;
11038
11574
  }
@@ -11043,7 +11579,7 @@ var MockBuilder = class {
11043
11579
  * mockLLM().whenMessageMatches(/calculate \d+/)
11044
11580
  */
11045
11581
  whenMessageMatches(regex) {
11046
- this.matchers.push((ctx) => ctx.messages.some((msg) => regex.test(extractText(msg.content))));
11582
+ this.matchers.push((ctx) => ctx.messages.some((msg) => regex.test(extractMessageText(msg.content))));
11047
11583
  return this;
11048
11584
  }
11049
11585
  /**
@@ -11055,7 +11591,7 @@ var MockBuilder = class {
11055
11591
  whenRoleContains(role, text3) {
11056
11592
  this.matchers.push(
11057
11593
  (ctx) => ctx.messages.some(
11058
- (msg) => msg.role === role && extractText(msg.content).toLowerCase().includes(text3.toLowerCase())
11594
+ (msg) => msg.role === role && extractMessageText(msg.content).toLowerCase().includes(text3.toLowerCase())
11059
11595
  )
11060
11596
  );
11061
11597
  return this;
@@ -11460,11 +11996,10 @@ function createMockClient(options) {
11460
11996
  init_gadget();
11461
11997
  // Annotate the CommonJS export names for ESM import in node:
11462
11998
  0 && (module.exports = {
11463
- AbortError,
11999
+ AbortException,
12000
+ AbstractGadget,
11464
12001
  AgentBuilder,
11465
12002
  AnthropicMessagesProvider,
11466
- BaseGadget,
11467
- BreakLoopException,
11468
12003
  CompactionManager,
11469
12004
  ConversationManager,
11470
12005
  DEFAULT_COMPACTION_CONFIG,
@@ -11472,12 +12007,13 @@ init_gadget();
11472
12007
  DEFAULT_PROMPTS,
11473
12008
  DEFAULT_SUMMARIZATION_PROMPT,
11474
12009
  Gadget,
12010
+ GadgetCallParser,
11475
12011
  GadgetExecutor,
11476
12012
  GadgetOutputStore,
11477
12013
  GadgetRegistry,
11478
12014
  GeminiGenerativeProvider,
11479
12015
  HookPresets,
11480
- HumanInputException,
12016
+ HumanInputRequiredException,
11481
12017
  HybridStrategy,
11482
12018
  LLMMessageBuilder,
11483
12019
  LLMist,
@@ -11490,9 +12026,10 @@ init_gadget();
11490
12026
  ModelRegistry,
11491
12027
  OpenAIChatProvider,
11492
12028
  SlidingWindowStrategy,
11493
- StreamParser,
11494
12029
  StreamProcessor,
11495
12030
  SummarizationStrategy,
12031
+ TaskCompletionSignal,
12032
+ TimeoutException,
11496
12033
  audioFromBase64,
11497
12034
  audioFromBuffer,
11498
12035
  collectEvents,
@@ -11504,7 +12041,7 @@ init_gadget();
11504
12041
  createGeminiProviderFromEnv,
11505
12042
  createHints,
11506
12043
  createLogger,
11507
- createMedia,
12044
+ createMediaOutput,
11508
12045
  createMockAdapter,
11509
12046
  createMockClient,
11510
12047
  createMockStream,
@@ -11514,7 +12051,7 @@ init_gadget();
11514
12051
  detectAudioMimeType,
11515
12052
  detectImageMimeType,
11516
12053
  discoverProviderAdapters,
11517
- extractText,
12054
+ extractMessageText,
11518
12055
  getMockManager,
11519
12056
  getModelId,
11520
12057
  getProvider,
@@ -11528,7 +12065,7 @@ init_gadget();
11528
12065
  isTextPart,
11529
12066
  iterationProgressHint,
11530
12067
  mockLLM,
11531
- normalizeContent,
12068
+ normalizeMessageContent,
11532
12069
  parallelGadgetHint,
11533
12070
  parseDataUrl,
11534
12071
  resolveHintTemplate,