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.
@@ -320,13 +320,13 @@ var init_prompt_config = __esm({
320
320
  });
321
321
 
322
322
  // src/core/messages.ts
323
- function normalizeContent(content) {
323
+ function normalizeMessageContent(content) {
324
324
  if (typeof content === "string") {
325
325
  return [{ type: "text", text: content }];
326
326
  }
327
327
  return content;
328
328
  }
329
- function extractText(content) {
329
+ function extractMessageText(content) {
330
330
  if (typeof content === "string") {
331
331
  return content;
332
332
  }
@@ -686,7 +686,17 @@ Produces: { "items": ["first", "second"] }`);
686
686
  this.messages.push({ role: "user", content: parts });
687
687
  return this;
688
688
  }
689
- addGadgetCall(gadget, parameters, result, media, mediaIds) {
689
+ /**
690
+ * Record a gadget execution result in the message history.
691
+ * Creates an assistant message with the gadget invocation and a user message with the result.
692
+ *
693
+ * @param gadget - Name of the gadget that was executed
694
+ * @param parameters - Parameters that were passed to the gadget
695
+ * @param result - Text result from the gadget execution
696
+ * @param media - Optional media outputs from the gadget
697
+ * @param mediaIds - Optional IDs for the media outputs
698
+ */
699
+ addGadgetCallResult(gadget, parameters, result, media, mediaIds) {
690
700
  const paramStr = this.formatBlockParameters(parameters, "");
691
701
  this.messages.push({
692
702
  role: "assistant",
@@ -1267,21 +1277,21 @@ var init_media_store = __esm({
1267
1277
  });
1268
1278
 
1269
1279
  // src/gadgets/exceptions.ts
1270
- var BreakLoopException, HumanInputException, TimeoutException, AbortError;
1280
+ var TaskCompletionSignal, HumanInputRequiredException, TimeoutException, AbortException;
1271
1281
  var init_exceptions = __esm({
1272
1282
  "src/gadgets/exceptions.ts"() {
1273
1283
  "use strict";
1274
- BreakLoopException = class extends Error {
1284
+ TaskCompletionSignal = class extends Error {
1275
1285
  constructor(message) {
1276
1286
  super(message ?? "Agent loop terminated by gadget");
1277
- this.name = "BreakLoopException";
1287
+ this.name = "TaskCompletionSignal";
1278
1288
  }
1279
1289
  };
1280
- HumanInputException = class extends Error {
1290
+ HumanInputRequiredException = class extends Error {
1281
1291
  question;
1282
1292
  constructor(question) {
1283
1293
  super(`Human input required: ${question}`);
1284
- this.name = "HumanInputException";
1294
+ this.name = "HumanInputRequiredException";
1285
1295
  this.question = question;
1286
1296
  }
1287
1297
  };
@@ -1295,10 +1305,10 @@ var init_exceptions = __esm({
1295
1305
  this.timeoutMs = timeoutMs;
1296
1306
  }
1297
1307
  };
1298
- AbortError = class extends Error {
1308
+ AbortException = class extends Error {
1299
1309
  constructor(message) {
1300
1310
  super(message || "Gadget execution was aborted");
1301
- this.name = "AbortError";
1311
+ this.name = "AbortException";
1302
1312
  }
1303
1313
  };
1304
1314
  }
@@ -1387,7 +1397,7 @@ var init_schema_to_json = __esm({
1387
1397
  });
1388
1398
 
1389
1399
  // src/gadgets/gadget.ts
1390
- function formatParamsAsBlock(params, prefix = "", argPrefix = GADGET_ARG_PREFIX) {
1400
+ function formatParamsForBlockExample(params, prefix = "", argPrefix = GADGET_ARG_PREFIX) {
1391
1401
  const lines = [];
1392
1402
  for (const [key, value] of Object.entries(params)) {
1393
1403
  const fullPath = prefix ? `${prefix}/${key}` : key;
@@ -1395,14 +1405,14 @@ function formatParamsAsBlock(params, prefix = "", argPrefix = GADGET_ARG_PREFIX)
1395
1405
  value.forEach((item, index) => {
1396
1406
  const itemPath = `${fullPath}/${index}`;
1397
1407
  if (typeof item === "object" && item !== null) {
1398
- lines.push(formatParamsAsBlock(item, itemPath, argPrefix));
1408
+ lines.push(formatParamsForBlockExample(item, itemPath, argPrefix));
1399
1409
  } else {
1400
1410
  lines.push(`${argPrefix}${itemPath}`);
1401
1411
  lines.push(String(item));
1402
1412
  }
1403
1413
  });
1404
1414
  } else if (typeof value === "object" && value !== null) {
1405
- lines.push(formatParamsAsBlock(value, fullPath, argPrefix));
1415
+ lines.push(formatParamsForBlockExample(value, fullPath, argPrefix));
1406
1416
  } else {
1407
1417
  lines.push(`${argPrefix}${fullPath}`);
1408
1418
  lines.push(String(value));
@@ -1491,7 +1501,7 @@ function formatSchemaAsPlainText(schema, indent = "", atRoot = true) {
1491
1501
  }
1492
1502
  return lines.join("\n");
1493
1503
  }
1494
- var BaseGadget;
1504
+ var AbstractGadget;
1495
1505
  var init_gadget = __esm({
1496
1506
  "src/gadgets/gadget.ts"() {
1497
1507
  "use strict";
@@ -1499,7 +1509,7 @@ var init_gadget = __esm({
1499
1509
  init_exceptions();
1500
1510
  init_schema_to_json();
1501
1511
  init_schema_validator();
1502
- BaseGadget = class {
1512
+ AbstractGadget = class {
1503
1513
  /**
1504
1514
  * The name of the gadget. Used for identification when LLM calls it.
1505
1515
  * If not provided, defaults to the class name.
@@ -1527,14 +1537,14 @@ var init_gadget = __esm({
1527
1537
  */
1528
1538
  examples;
1529
1539
  /**
1530
- * Throws an AbortError if the execution has been aborted.
1540
+ * Throws an AbortException if the execution has been aborted.
1531
1541
  *
1532
1542
  * Call this at key checkpoints in long-running gadgets to allow early exit
1533
1543
  * when the gadget has been cancelled (e.g., due to timeout). This enables
1534
1544
  * resource cleanup and prevents unnecessary work after cancellation.
1535
1545
  *
1536
1546
  * @param ctx - The execution context containing the abort signal
1537
- * @throws AbortError if ctx.signal.aborted is true
1547
+ * @throws AbortException if ctx.signal.aborted is true
1538
1548
  *
1539
1549
  * @example
1540
1550
  * ```typescript
@@ -1559,7 +1569,7 @@ var init_gadget = __esm({
1559
1569
  */
1560
1570
  throwIfAborted(ctx) {
1561
1571
  if (ctx?.signal?.aborted) {
1562
- throw new AbortError();
1572
+ throw new AbortException();
1563
1573
  }
1564
1574
  }
1565
1575
  /**
@@ -1701,7 +1711,7 @@ var init_gadget = __esm({
1701
1711
  }
1702
1712
  parts.push(`${effectiveStartPrefix}${gadgetName}`);
1703
1713
  parts.push(
1704
- formatParamsAsBlock(example.params, "", effectiveArgPrefix)
1714
+ formatParamsForBlockExample(example.params, "", effectiveArgPrefix)
1705
1715
  );
1706
1716
  parts.push(effectiveEndPrefix);
1707
1717
  if (example.output !== void 0) {
@@ -1719,7 +1729,7 @@ var init_gadget = __esm({
1719
1729
 
1720
1730
  // src/gadgets/create-gadget.ts
1721
1731
  function createGadget(config) {
1722
- class DynamicGadget extends BaseGadget {
1732
+ class DynamicGadget extends AbstractGadget {
1723
1733
  name = config.name;
1724
1734
  description = config.description;
1725
1735
  parameterSchema = config.schema;
@@ -2357,8 +2367,8 @@ var init_conversation_manager = __esm({
2357
2367
  addAssistantMessage(content) {
2358
2368
  this.historyBuilder.addAssistant(content);
2359
2369
  }
2360
- addGadgetCall(gadgetName, parameters, result, media, mediaIds) {
2361
- this.historyBuilder.addGadgetCall(gadgetName, parameters, result, media, mediaIds);
2370
+ addGadgetCallResult(gadgetName, parameters, result, media, mediaIds) {
2371
+ this.historyBuilder.addGadgetCallResult(gadgetName, parameters, result, media, mediaIds);
2362
2372
  }
2363
2373
  getMessages() {
2364
2374
  return [...this.baseMessages, ...this.initialMessages, ...this.historyBuilder.build()];
@@ -2378,7 +2388,7 @@ var init_conversation_manager = __esm({
2378
2388
  if (msg.role === "user") {
2379
2389
  this.historyBuilder.addUser(msg.content);
2380
2390
  } else if (msg.role === "assistant") {
2381
- this.historyBuilder.addAssistant(extractText(msg.content));
2391
+ this.historyBuilder.addAssistant(extractMessageText(msg.content));
2382
2392
  }
2383
2393
  }
2384
2394
  }
@@ -3258,12 +3268,12 @@ var init_cost_reporting_client = __esm({
3258
3268
  });
3259
3269
 
3260
3270
  // src/gadgets/error-formatter.ts
3261
- var GadgetErrorFormatter;
3271
+ var GadgetExecutionErrorFormatter;
3262
3272
  var init_error_formatter = __esm({
3263
3273
  "src/gadgets/error-formatter.ts"() {
3264
3274
  "use strict";
3265
3275
  init_constants();
3266
- GadgetErrorFormatter = class {
3276
+ GadgetExecutionErrorFormatter = class {
3267
3277
  argPrefix;
3268
3278
  startPrefix;
3269
3279
  endPrefix;
@@ -3349,16 +3359,16 @@ function stripMarkdownFences(content) {
3349
3359
  cleaned = cleaned.replace(closingFence, "");
3350
3360
  return cleaned.trim();
3351
3361
  }
3352
- var globalInvocationCounter, StreamParser;
3362
+ var globalInvocationCounter, GadgetCallParser;
3353
3363
  var init_parser = __esm({
3354
3364
  "src/gadgets/parser.ts"() {
3355
3365
  "use strict";
3356
3366
  init_constants();
3357
3367
  init_block_params();
3358
3368
  globalInvocationCounter = 0;
3359
- StreamParser = class {
3369
+ GadgetCallParser = class {
3360
3370
  buffer = "";
3361
- lastReportedTextLength = 0;
3371
+ lastEmittedTextOffset = 0;
3362
3372
  startPrefix;
3363
3373
  endPrefix;
3364
3374
  argPrefix;
@@ -3367,16 +3377,20 @@ var init_parser = __esm({
3367
3377
  this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
3368
3378
  this.argPrefix = options.argPrefix ?? GADGET_ARG_PREFIX;
3369
3379
  }
3370
- takeTextUntil(index) {
3371
- if (index <= this.lastReportedTextLength) {
3380
+ /**
3381
+ * Extract and consume text up to the given index.
3382
+ * Returns undefined if no meaningful text to emit.
3383
+ */
3384
+ extractTextSegment(index) {
3385
+ if (index <= this.lastEmittedTextOffset) {
3372
3386
  return void 0;
3373
3387
  }
3374
- const segment = this.buffer.slice(this.lastReportedTextLength, index);
3375
- this.lastReportedTextLength = index;
3388
+ const segment = this.buffer.slice(this.lastEmittedTextOffset, index);
3389
+ this.lastEmittedTextOffset = index;
3376
3390
  return segment.trim().length > 0 ? segment : void 0;
3377
3391
  }
3378
3392
  /**
3379
- * Parse gadget name with optional invocation ID and dependencies.
3393
+ * Parse gadget invocation metadata from the header line.
3380
3394
  *
3381
3395
  * Supported formats:
3382
3396
  * - `GadgetName` - Auto-generate ID, no dependencies
@@ -3385,24 +3399,24 @@ var init_parser = __esm({
3385
3399
  *
3386
3400
  * Dependencies must be comma-separated invocation IDs.
3387
3401
  */
3388
- parseGadgetName(gadgetName) {
3389
- const parts = gadgetName.split(":");
3402
+ parseInvocationMetadata(headerLine) {
3403
+ const parts = headerLine.split(":");
3390
3404
  if (parts.length === 1) {
3391
3405
  return {
3392
- actualName: parts[0],
3406
+ gadgetName: parts[0],
3393
3407
  invocationId: `gadget_${++globalInvocationCounter}`,
3394
3408
  dependencies: []
3395
3409
  };
3396
3410
  } else if (parts.length === 2) {
3397
3411
  return {
3398
- actualName: parts[0],
3412
+ gadgetName: parts[0],
3399
3413
  invocationId: parts[1].trim(),
3400
3414
  dependencies: []
3401
3415
  };
3402
3416
  } else {
3403
3417
  const deps = parts[2].split(",").map((d) => d.trim()).filter((d) => d.length > 0);
3404
3418
  return {
3405
- actualName: parts[0],
3419
+ gadgetName: parts[0],
3406
3420
  invocationId: parts[1].trim(),
3407
3421
  dependencies: deps
3408
3422
  };
@@ -3434,19 +3448,15 @@ var init_parser = __esm({
3434
3448
  while (true) {
3435
3449
  const partStartIndex = this.buffer.indexOf(this.startPrefix, startIndex);
3436
3450
  if (partStartIndex === -1) break;
3437
- const textBefore = this.takeTextUntil(partStartIndex);
3451
+ const textBefore = this.extractTextSegment(partStartIndex);
3438
3452
  if (textBefore !== void 0) {
3439
3453
  yield { type: "text", content: textBefore };
3440
3454
  }
3441
3455
  const metadataStartIndex = partStartIndex + this.startPrefix.length;
3442
3456
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
3443
3457
  if (metadataEndIndex === -1) break;
3444
- const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3445
- const {
3446
- actualName: actualGadgetName,
3447
- invocationId,
3448
- dependencies
3449
- } = this.parseGadgetName(gadgetName);
3458
+ const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3459
+ const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
3450
3460
  const contentStartIndex = metadataEndIndex + 1;
3451
3461
  let partEndIndex;
3452
3462
  let endMarkerLength = 0;
@@ -3466,7 +3476,7 @@ var init_parser = __esm({
3466
3476
  yield {
3467
3477
  type: "gadget_call",
3468
3478
  call: {
3469
- gadgetName: actualGadgetName,
3479
+ gadgetName,
3470
3480
  invocationId,
3471
3481
  parametersRaw,
3472
3482
  parameters,
@@ -3475,37 +3485,33 @@ var init_parser = __esm({
3475
3485
  }
3476
3486
  };
3477
3487
  startIndex = partEndIndex + endMarkerLength;
3478
- this.lastReportedTextLength = startIndex;
3488
+ this.lastEmittedTextOffset = startIndex;
3479
3489
  }
3480
3490
  if (startIndex > 0) {
3481
3491
  this.buffer = this.buffer.substring(startIndex);
3482
- this.lastReportedTextLength = 0;
3492
+ this.lastEmittedTextOffset = 0;
3483
3493
  }
3484
3494
  }
3485
3495
  // Finalize parsing and return remaining text or incomplete gadgets
3486
3496
  *finalize() {
3487
- const startIndex = this.buffer.indexOf(this.startPrefix, this.lastReportedTextLength);
3497
+ const startIndex = this.buffer.indexOf(this.startPrefix, this.lastEmittedTextOffset);
3488
3498
  if (startIndex !== -1) {
3489
- const textBefore = this.takeTextUntil(startIndex);
3499
+ const textBefore = this.extractTextSegment(startIndex);
3490
3500
  if (textBefore !== void 0) {
3491
3501
  yield { type: "text", content: textBefore };
3492
3502
  }
3493
3503
  const metadataStartIndex = startIndex + this.startPrefix.length;
3494
3504
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
3495
3505
  if (metadataEndIndex !== -1) {
3496
- const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3497
- const {
3498
- actualName: actualGadgetName,
3499
- invocationId,
3500
- dependencies
3501
- } = this.parseGadgetName(gadgetName);
3506
+ const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3507
+ const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
3502
3508
  const contentStartIndex = metadataEndIndex + 1;
3503
3509
  const parametersRaw = this.buffer.substring(contentStartIndex).trim();
3504
3510
  const { parameters, parseError } = this.parseParameters(parametersRaw);
3505
3511
  yield {
3506
3512
  type: "gadget_call",
3507
3513
  call: {
3508
- gadgetName: actualGadgetName,
3514
+ gadgetName,
3509
3515
  invocationId,
3510
3516
  parametersRaw,
3511
3517
  parameters,
@@ -3516,7 +3522,7 @@ var init_parser = __esm({
3516
3522
  return;
3517
3523
  }
3518
3524
  }
3519
- const remainingText = this.takeTextUntil(this.buffer.length);
3525
+ const remainingText = this.extractTextSegment(this.buffer.length);
3520
3526
  if (remainingText !== void 0) {
3521
3527
  yield { type: "text", content: remainingText };
3522
3528
  }
@@ -3524,7 +3530,7 @@ var init_parser = __esm({
3524
3530
  // Reset parser state (note: global invocation counter is NOT reset to ensure unique IDs)
3525
3531
  reset() {
3526
3532
  this.buffer = "";
3527
- this.lastReportedTextLength = 0;
3533
+ this.lastEmittedTextOffset = 0;
3528
3534
  }
3529
3535
  };
3530
3536
  }
@@ -3543,14 +3549,17 @@ var init_executor = __esm({
3543
3549
  init_exceptions();
3544
3550
  init_parser();
3545
3551
  GadgetExecutor = class {
3546
- constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore) {
3552
+ constructor(registry, requestHumanInput, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig, onNestedEvent) {
3547
3553
  this.registry = registry;
3548
- this.onHumanInputRequired = onHumanInputRequired;
3554
+ this.requestHumanInput = requestHumanInput;
3549
3555
  this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
3550
3556
  this.client = client;
3551
3557
  this.mediaStore = mediaStore;
3558
+ this.agentConfig = agentConfig;
3559
+ this.subagentConfig = subagentConfig;
3560
+ this.onNestedEvent = onNestedEvent;
3552
3561
  this.logger = logger ?? createLogger({ name: "llmist:executor" });
3553
- this.errorFormatter = new GadgetErrorFormatter(errorFormatterOptions);
3562
+ this.errorFormatter = new GadgetExecutionErrorFormatter(errorFormatterOptions);
3554
3563
  this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
3555
3564
  }
3556
3565
  logger;
@@ -3570,11 +3579,11 @@ var init_executor = __esm({
3570
3579
  });
3571
3580
  }
3572
3581
  /**
3573
- * Normalizes gadget execute result to consistent format.
3582
+ * Unify gadget execute result to consistent internal format.
3574
3583
  * Handles string returns (backwards compat), object returns with cost,
3575
3584
  * and object returns with media.
3576
3585
  */
3577
- normalizeExecuteResult(raw) {
3586
+ unifyExecuteResult(raw) {
3578
3587
  if (typeof raw === "string") {
3579
3588
  return { result: raw, cost: 0 };
3580
3589
  }
@@ -3692,7 +3701,11 @@ var init_executor = __esm({
3692
3701
  const ctx = {
3693
3702
  reportCost,
3694
3703
  llmist: this.client ? new CostReportingLLMistWrapper(this.client, reportCost) : void 0,
3695
- signal: abortController.signal
3704
+ signal: abortController.signal,
3705
+ agentConfig: this.agentConfig,
3706
+ subagentConfig: this.subagentConfig,
3707
+ invocationId: call.invocationId,
3708
+ onNestedEvent: this.onNestedEvent
3696
3709
  };
3697
3710
  let rawResult;
3698
3711
  if (timeoutMs && timeoutMs > 0) {
@@ -3707,7 +3720,7 @@ var init_executor = __esm({
3707
3720
  } else {
3708
3721
  rawResult = await Promise.resolve(gadget.execute(validatedParameters, ctx));
3709
3722
  }
3710
- const { result, media, cost: returnCost } = this.normalizeExecuteResult(rawResult);
3723
+ const { result, media, cost: returnCost } = this.unifyExecuteResult(rawResult);
3711
3724
  const totalCost = callbackCost + returnCost;
3712
3725
  let mediaIds;
3713
3726
  let storedMedia;
@@ -3753,7 +3766,7 @@ var init_executor = __esm({
3753
3766
  storedMedia
3754
3767
  };
3755
3768
  } catch (error) {
3756
- if (error instanceof BreakLoopException) {
3769
+ if (error instanceof TaskCompletionSignal) {
3757
3770
  this.logger.info("Gadget requested loop termination", {
3758
3771
  gadgetName: call.gadgetName,
3759
3772
  message: error.message
@@ -3781,7 +3794,7 @@ var init_executor = __esm({
3781
3794
  executionTimeMs: Date.now() - startTime
3782
3795
  };
3783
3796
  }
3784
- if (error instanceof AbortError) {
3797
+ if (error instanceof AbortException) {
3785
3798
  this.logger.info("Gadget execution was aborted", {
3786
3799
  gadgetName: call.gadgetName,
3787
3800
  executionTimeMs: Date.now() - startTime
@@ -3794,14 +3807,14 @@ var init_executor = __esm({
3794
3807
  executionTimeMs: Date.now() - startTime
3795
3808
  };
3796
3809
  }
3797
- if (error instanceof HumanInputException) {
3810
+ if (error instanceof HumanInputRequiredException) {
3798
3811
  this.logger.info("Gadget requested human input", {
3799
3812
  gadgetName: call.gadgetName,
3800
3813
  question: error.question
3801
3814
  });
3802
- if (this.onHumanInputRequired) {
3815
+ if (this.requestHumanInput) {
3803
3816
  try {
3804
- const answer = await this.onHumanInputRequired(error.question);
3817
+ const answer = await this.requestHumanInput(error.question);
3805
3818
  this.logger.debug("Human input received", {
3806
3819
  gadgetName: call.gadgetName,
3807
3820
  answerLength: answer.length
@@ -3899,13 +3912,13 @@ var init_stream_processor = __esm({
3899
3912
  parser;
3900
3913
  executor;
3901
3914
  stopOnGadgetError;
3902
- shouldContinueAfterError;
3903
- accumulatedText = "";
3904
- shouldStopExecution = false;
3915
+ canRecoverFromGadgetError;
3916
+ responseText = "";
3917
+ executionHalted = false;
3905
3918
  observerFailureCount = 0;
3906
3919
  // Dependency tracking for gadget execution DAG
3907
3920
  /** Gadgets waiting for their dependencies to complete */
3908
- pendingGadgets = /* @__PURE__ */ new Map();
3921
+ gadgetsAwaitingDependencies = /* @__PURE__ */ new Map();
3909
3922
  /** Completed gadget results, keyed by invocation ID */
3910
3923
  completedResults = /* @__PURE__ */ new Map();
3911
3924
  /** Invocation IDs of gadgets that have failed (error or skipped due to dependency) */
@@ -3916,27 +3929,36 @@ var init_stream_processor = __esm({
3916
3929
  this.hooks = options.hooks ?? {};
3917
3930
  this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
3918
3931
  this.stopOnGadgetError = options.stopOnGadgetError ?? true;
3919
- this.shouldContinueAfterError = options.shouldContinueAfterError;
3920
- this.parser = new StreamParser({
3932
+ this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
3933
+ this.parser = new GadgetCallParser({
3921
3934
  startPrefix: options.gadgetStartPrefix,
3922
3935
  endPrefix: options.gadgetEndPrefix,
3923
3936
  argPrefix: options.gadgetArgPrefix
3924
3937
  });
3925
3938
  this.executor = new GadgetExecutor(
3926
3939
  options.registry,
3927
- options.onHumanInputRequired,
3940
+ options.requestHumanInput,
3928
3941
  this.logger.getSubLogger({ name: "executor" }),
3929
3942
  options.defaultGadgetTimeoutMs,
3930
3943
  { argPrefix: options.gadgetArgPrefix },
3931
3944
  options.client,
3932
- options.mediaStore
3945
+ options.mediaStore,
3946
+ options.agentConfig,
3947
+ options.subagentConfig,
3948
+ options.onNestedEvent
3933
3949
  );
3934
3950
  }
3935
3951
  /**
3936
- * Process an LLM stream and return structured results.
3952
+ * Process an LLM stream and yield events in real-time.
3953
+ *
3954
+ * This is an async generator that yields events immediately as they occur:
3955
+ * - Text events are yielded as text is streamed from the LLM
3956
+ * - gadget_call events are yielded immediately when a gadget call is parsed
3957
+ * - gadget_result events are yielded when gadget execution completes
3958
+ *
3959
+ * The final event is always a StreamCompletionEvent containing metadata.
3937
3960
  */
3938
- async process(stream2) {
3939
- const outputs = [];
3961
+ async *process(stream2) {
3940
3962
  let finishReason = null;
3941
3963
  let usage;
3942
3964
  let didExecuteGadgets = false;
@@ -3950,7 +3972,7 @@ var init_stream_processor = __esm({
3950
3972
  if (this.hooks.interceptors?.interceptRawChunk) {
3951
3973
  const context = {
3952
3974
  iteration: this.iteration,
3953
- accumulatedText: this.accumulatedText,
3975
+ accumulatedText: this.responseText,
3954
3976
  logger: this.logger
3955
3977
  };
3956
3978
  const intercepted = this.hooks.interceptors.interceptRawChunk(processedChunk, context);
@@ -3961,7 +3983,7 @@ var init_stream_processor = __esm({
3961
3983
  }
3962
3984
  }
3963
3985
  if (processedChunk) {
3964
- this.accumulatedText += processedChunk;
3986
+ this.responseText += processedChunk;
3965
3987
  }
3966
3988
  }
3967
3989
  if (this.hooks.observers?.onStreamChunk && (processedChunk || chunk.usage)) {
@@ -3970,7 +3992,7 @@ var init_stream_processor = __esm({
3970
3992
  const context = {
3971
3993
  iteration: this.iteration,
3972
3994
  rawChunk: processedChunk,
3973
- accumulatedText: this.accumulatedText,
3995
+ accumulatedText: this.responseText,
3974
3996
  usage,
3975
3997
  logger: this.logger
3976
3998
  };
@@ -3982,67 +4004,66 @@ var init_stream_processor = __esm({
3982
4004
  continue;
3983
4005
  }
3984
4006
  for (const event of this.parser.feed(processedChunk)) {
3985
- const processedEvents = await this.processEvent(event);
3986
- outputs.push(...processedEvents);
3987
- if (processedEvents.some((e) => e.type === "gadget_result")) {
3988
- didExecuteGadgets = true;
3989
- }
3990
- for (const evt of processedEvents) {
3991
- if (evt.type === "gadget_result" && evt.result.breaksLoop) {
3992
- shouldBreakLoop = true;
4007
+ for await (const processedEvent of this.processEventGenerator(event)) {
4008
+ yield processedEvent;
4009
+ if (processedEvent.type === "gadget_result") {
4010
+ didExecuteGadgets = true;
4011
+ if (processedEvent.result.breaksLoop) {
4012
+ shouldBreakLoop = true;
4013
+ }
3993
4014
  }
3994
4015
  }
3995
4016
  }
3996
- if (this.shouldStopExecution) {
4017
+ if (this.executionHalted) {
3997
4018
  this.logger.info("Breaking from LLM stream due to gadget error");
3998
4019
  break;
3999
4020
  }
4000
4021
  }
4001
- if (!this.shouldStopExecution) {
4022
+ if (!this.executionHalted) {
4002
4023
  for (const event of this.parser.finalize()) {
4003
- const processedEvents = await this.processEvent(event);
4004
- outputs.push(...processedEvents);
4005
- if (processedEvents.some((e) => e.type === "gadget_result")) {
4006
- didExecuteGadgets = true;
4007
- }
4008
- for (const evt of processedEvents) {
4009
- if (evt.type === "gadget_result" && evt.result.breaksLoop) {
4010
- shouldBreakLoop = true;
4024
+ for await (const processedEvent of this.processEventGenerator(event)) {
4025
+ yield processedEvent;
4026
+ if (processedEvent.type === "gadget_result") {
4027
+ didExecuteGadgets = true;
4028
+ if (processedEvent.result.breaksLoop) {
4029
+ shouldBreakLoop = true;
4030
+ }
4011
4031
  }
4012
4032
  }
4013
4033
  }
4014
- const finalPendingEvents = await this.processPendingGadgets();
4015
- outputs.push(...finalPendingEvents);
4016
- if (finalPendingEvents.some((e) => e.type === "gadget_result")) {
4017
- didExecuteGadgets = true;
4018
- }
4019
- for (const evt of finalPendingEvents) {
4020
- if (evt.type === "gadget_result" && evt.result.breaksLoop) {
4021
- shouldBreakLoop = true;
4034
+ for await (const evt of this.processPendingGadgetsGenerator()) {
4035
+ yield evt;
4036
+ if (evt.type === "gadget_result") {
4037
+ didExecuteGadgets = true;
4038
+ if (evt.result.breaksLoop) {
4039
+ shouldBreakLoop = true;
4040
+ }
4022
4041
  }
4023
4042
  }
4024
4043
  }
4025
- let finalMessage = this.accumulatedText;
4044
+ let finalMessage = this.responseText;
4026
4045
  if (this.hooks.interceptors?.interceptAssistantMessage) {
4027
4046
  const context = {
4028
4047
  iteration: this.iteration,
4029
- rawResponse: this.accumulatedText,
4048
+ rawResponse: this.responseText,
4030
4049
  logger: this.logger
4031
4050
  };
4032
4051
  finalMessage = this.hooks.interceptors.interceptAssistantMessage(finalMessage, context);
4033
4052
  }
4034
- return {
4035
- outputs,
4053
+ const completionEvent = {
4054
+ type: "stream_complete",
4036
4055
  shouldBreakLoop,
4037
4056
  didExecuteGadgets,
4038
4057
  finishReason,
4039
4058
  usage,
4040
- rawResponse: this.accumulatedText,
4059
+ rawResponse: this.responseText,
4041
4060
  finalMessage
4042
4061
  };
4062
+ yield completionEvent;
4043
4063
  }
4044
4064
  /**
4045
4065
  * Process a single parsed event (text or gadget call).
4066
+ * @deprecated Use processEventGenerator for real-time streaming
4046
4067
  */
4047
4068
  async processEvent(event) {
4048
4069
  if (event.type === "text") {
@@ -4052,6 +4073,23 @@ var init_stream_processor = __esm({
4052
4073
  }
4053
4074
  return [event];
4054
4075
  }
4076
+ /**
4077
+ * Process a single parsed event, yielding events in real-time.
4078
+ * Generator version of processEvent for streaming support.
4079
+ */
4080
+ async *processEventGenerator(event) {
4081
+ if (event.type === "text") {
4082
+ for (const e of await this.processTextEvent(event)) {
4083
+ yield e;
4084
+ }
4085
+ } else if (event.type === "gadget_call") {
4086
+ for await (const e of this.processGadgetCallGenerator(event.call)) {
4087
+ yield e;
4088
+ }
4089
+ } else {
4090
+ yield event;
4091
+ }
4092
+ }
4055
4093
  /**
4056
4094
  * Process a text event through interceptors.
4057
4095
  */
@@ -4060,7 +4098,7 @@ var init_stream_processor = __esm({
4060
4098
  if (this.hooks.interceptors?.interceptTextChunk) {
4061
4099
  const context = {
4062
4100
  iteration: this.iteration,
4063
- accumulatedText: this.accumulatedText,
4101
+ accumulatedText: this.responseText,
4064
4102
  logger: this.logger
4065
4103
  };
4066
4104
  const intercepted = this.hooks.interceptors.interceptTextChunk(content, context);
@@ -4079,7 +4117,7 @@ var init_stream_processor = __esm({
4079
4117
  * After each execution, pending gadgets are checked to see if they can now run.
4080
4118
  */
4081
4119
  async processGadgetCall(call) {
4082
- if (this.shouldStopExecution) {
4120
+ if (this.executionHalted) {
4083
4121
  this.logger.debug("Skipping gadget execution due to previous error", {
4084
4122
  gadgetName: call.gadgetName
4085
4123
  });
@@ -4118,7 +4156,7 @@ var init_stream_processor = __esm({
4118
4156
  invocationId: call.invocationId,
4119
4157
  waitingOn: unsatisfied
4120
4158
  });
4121
- this.pendingGadgets.set(call.invocationId, call);
4159
+ this.gadgetsAwaitingDependencies.set(call.invocationId, call);
4122
4160
  return events;
4123
4161
  }
4124
4162
  }
@@ -4128,9 +4166,68 @@ var init_stream_processor = __esm({
4128
4166
  events.push(...triggeredEvents);
4129
4167
  return events;
4130
4168
  }
4169
+ /**
4170
+ * Process a gadget call, yielding events in real-time.
4171
+ *
4172
+ * Key difference from processGadgetCall: yields gadget_call event IMMEDIATELY
4173
+ * when parsed (before execution), enabling real-time UI feedback.
4174
+ */
4175
+ async *processGadgetCallGenerator(call) {
4176
+ if (this.executionHalted) {
4177
+ this.logger.debug("Skipping gadget execution due to previous error", {
4178
+ gadgetName: call.gadgetName
4179
+ });
4180
+ return;
4181
+ }
4182
+ yield { type: "gadget_call", call };
4183
+ if (call.dependencies.length > 0) {
4184
+ if (call.dependencies.includes(call.invocationId)) {
4185
+ this.logger.warn("Gadget has self-referential dependency (depends on itself)", {
4186
+ gadgetName: call.gadgetName,
4187
+ invocationId: call.invocationId
4188
+ });
4189
+ this.failedInvocations.add(call.invocationId);
4190
+ const skipEvent = {
4191
+ type: "gadget_skipped",
4192
+ gadgetName: call.gadgetName,
4193
+ invocationId: call.invocationId,
4194
+ parameters: call.parameters ?? {},
4195
+ failedDependency: call.invocationId,
4196
+ failedDependencyError: `Gadget "${call.invocationId}" cannot depend on itself (self-referential dependency)`
4197
+ };
4198
+ yield skipEvent;
4199
+ return;
4200
+ }
4201
+ const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
4202
+ if (failedDep) {
4203
+ const skipEvents = await this.handleFailedDependency(call, failedDep);
4204
+ for (const evt of skipEvents) {
4205
+ yield evt;
4206
+ }
4207
+ return;
4208
+ }
4209
+ const unsatisfied = call.dependencies.filter((dep) => !this.completedResults.has(dep));
4210
+ if (unsatisfied.length > 0) {
4211
+ this.logger.debug("Queueing gadget for later - waiting on dependencies", {
4212
+ gadgetName: call.gadgetName,
4213
+ invocationId: call.invocationId,
4214
+ waitingOn: unsatisfied
4215
+ });
4216
+ this.gadgetsAwaitingDependencies.set(call.invocationId, call);
4217
+ return;
4218
+ }
4219
+ }
4220
+ for await (const evt of this.executeGadgetGenerator(call)) {
4221
+ yield evt;
4222
+ }
4223
+ for await (const evt of this.processPendingGadgetsGenerator()) {
4224
+ yield evt;
4225
+ }
4226
+ }
4131
4227
  /**
4132
4228
  * Execute a gadget through the full hook lifecycle.
4133
4229
  * This is the core execution logic, extracted from processGadgetCall.
4230
+ * @deprecated Use executeGadgetGenerator for real-time streaming
4134
4231
  */
4135
4232
  async executeGadgetWithHooks(call) {
4136
4233
  const events = [];
@@ -4140,14 +4237,14 @@ var init_stream_processor = __esm({
4140
4237
  error: call.parseError,
4141
4238
  rawParameters: call.parametersRaw
4142
4239
  });
4143
- const shouldContinue = await this.checkContinueAfterError(
4240
+ const shouldContinue = await this.checkCanRecoverFromError(
4144
4241
  call.parseError,
4145
4242
  call.gadgetName,
4146
4243
  "parse",
4147
4244
  call.parameters
4148
4245
  );
4149
4246
  if (!shouldContinue) {
4150
- this.shouldStopExecution = true;
4247
+ this.executionHalted = true;
4151
4248
  }
4152
4249
  }
4153
4250
  let parameters = call.parameters ?? {};
@@ -4271,18 +4368,171 @@ var init_stream_processor = __esm({
4271
4368
  events.push({ type: "gadget_result", result });
4272
4369
  if (result.error) {
4273
4370
  const errorType = this.determineErrorType(call, result);
4274
- const shouldContinue = await this.checkContinueAfterError(
4371
+ const shouldContinue = await this.checkCanRecoverFromError(
4275
4372
  result.error,
4276
4373
  result.gadgetName,
4277
4374
  errorType,
4278
4375
  result.parameters
4279
4376
  );
4280
4377
  if (!shouldContinue) {
4281
- this.shouldStopExecution = true;
4378
+ this.executionHalted = true;
4282
4379
  }
4283
4380
  }
4284
4381
  return events;
4285
4382
  }
4383
+ /**
4384
+ * Execute a gadget and yield the result event.
4385
+ * Generator version that yields gadget_result immediately when execution completes.
4386
+ */
4387
+ async *executeGadgetGenerator(call) {
4388
+ if (call.parseError) {
4389
+ this.logger.warn("Gadget has parse error", {
4390
+ gadgetName: call.gadgetName,
4391
+ error: call.parseError,
4392
+ rawParameters: call.parametersRaw
4393
+ });
4394
+ const shouldContinue = await this.checkCanRecoverFromError(
4395
+ call.parseError,
4396
+ call.gadgetName,
4397
+ "parse",
4398
+ call.parameters
4399
+ );
4400
+ if (!shouldContinue) {
4401
+ this.executionHalted = true;
4402
+ }
4403
+ }
4404
+ let parameters = call.parameters ?? {};
4405
+ if (this.hooks.interceptors?.interceptGadgetParameters) {
4406
+ const context = {
4407
+ iteration: this.iteration,
4408
+ gadgetName: call.gadgetName,
4409
+ invocationId: call.invocationId,
4410
+ logger: this.logger
4411
+ };
4412
+ parameters = this.hooks.interceptors.interceptGadgetParameters(parameters, context);
4413
+ }
4414
+ call.parameters = parameters;
4415
+ let shouldSkip = false;
4416
+ let syntheticResult;
4417
+ if (this.hooks.controllers?.beforeGadgetExecution) {
4418
+ const context = {
4419
+ iteration: this.iteration,
4420
+ gadgetName: call.gadgetName,
4421
+ invocationId: call.invocationId,
4422
+ parameters,
4423
+ logger: this.logger
4424
+ };
4425
+ const action = await this.hooks.controllers.beforeGadgetExecution(context);
4426
+ validateBeforeGadgetExecutionAction(action);
4427
+ if (action.action === "skip") {
4428
+ shouldSkip = true;
4429
+ syntheticResult = action.syntheticResult;
4430
+ this.logger.info("Controller skipped gadget execution", {
4431
+ gadgetName: call.gadgetName
4432
+ });
4433
+ }
4434
+ }
4435
+ const startObservers = [];
4436
+ if (this.hooks.observers?.onGadgetExecutionStart) {
4437
+ startObservers.push(async () => {
4438
+ const context = {
4439
+ iteration: this.iteration,
4440
+ gadgetName: call.gadgetName,
4441
+ invocationId: call.invocationId,
4442
+ parameters,
4443
+ logger: this.logger
4444
+ };
4445
+ await this.hooks.observers.onGadgetExecutionStart(context);
4446
+ });
4447
+ }
4448
+ await this.runObserversInParallel(startObservers);
4449
+ let result;
4450
+ if (shouldSkip) {
4451
+ result = {
4452
+ gadgetName: call.gadgetName,
4453
+ invocationId: call.invocationId,
4454
+ parameters,
4455
+ result: syntheticResult ?? "Execution skipped",
4456
+ executionTimeMs: 0
4457
+ };
4458
+ } else {
4459
+ result = await this.executor.execute(call);
4460
+ }
4461
+ const originalResult = result.result;
4462
+ if (result.result && this.hooks.interceptors?.interceptGadgetResult) {
4463
+ const context = {
4464
+ iteration: this.iteration,
4465
+ gadgetName: result.gadgetName,
4466
+ invocationId: result.invocationId,
4467
+ parameters,
4468
+ executionTimeMs: result.executionTimeMs,
4469
+ logger: this.logger
4470
+ };
4471
+ result.result = this.hooks.interceptors.interceptGadgetResult(result.result, context);
4472
+ }
4473
+ if (this.hooks.controllers?.afterGadgetExecution) {
4474
+ const context = {
4475
+ iteration: this.iteration,
4476
+ gadgetName: result.gadgetName,
4477
+ invocationId: result.invocationId,
4478
+ parameters,
4479
+ result: result.result,
4480
+ error: result.error,
4481
+ executionTimeMs: result.executionTimeMs,
4482
+ logger: this.logger
4483
+ };
4484
+ const action = await this.hooks.controllers.afterGadgetExecution(context);
4485
+ validateAfterGadgetExecutionAction(action);
4486
+ if (action.action === "recover" && result.error) {
4487
+ this.logger.info("Controller recovered from gadget error", {
4488
+ gadgetName: result.gadgetName,
4489
+ originalError: result.error
4490
+ });
4491
+ result = {
4492
+ ...result,
4493
+ error: void 0,
4494
+ result: action.fallbackResult
4495
+ };
4496
+ }
4497
+ }
4498
+ const completeObservers = [];
4499
+ if (this.hooks.observers?.onGadgetExecutionComplete) {
4500
+ completeObservers.push(async () => {
4501
+ const context = {
4502
+ iteration: this.iteration,
4503
+ gadgetName: result.gadgetName,
4504
+ invocationId: result.invocationId,
4505
+ parameters,
4506
+ originalResult,
4507
+ finalResult: result.result,
4508
+ error: result.error,
4509
+ executionTimeMs: result.executionTimeMs,
4510
+ breaksLoop: result.breaksLoop,
4511
+ cost: result.cost,
4512
+ logger: this.logger
4513
+ };
4514
+ await this.hooks.observers.onGadgetExecutionComplete(context);
4515
+ });
4516
+ }
4517
+ await this.runObserversInParallel(completeObservers);
4518
+ this.completedResults.set(result.invocationId, result);
4519
+ if (result.error) {
4520
+ this.failedInvocations.add(result.invocationId);
4521
+ }
4522
+ yield { type: "gadget_result", result };
4523
+ if (result.error) {
4524
+ const errorType = this.determineErrorType(call, result);
4525
+ const shouldContinue = await this.checkCanRecoverFromError(
4526
+ result.error,
4527
+ result.gadgetName,
4528
+ errorType,
4529
+ result.parameters
4530
+ );
4531
+ if (!shouldContinue) {
4532
+ this.executionHalted = true;
4533
+ }
4534
+ }
4535
+ }
4286
4536
  /**
4287
4537
  * Handle a gadget that cannot execute because a dependency failed.
4288
4538
  * Calls the onDependencySkipped controller to allow customization.
@@ -4365,11 +4615,11 @@ var init_stream_processor = __esm({
4365
4615
  async processPendingGadgets() {
4366
4616
  const events = [];
4367
4617
  let progress = true;
4368
- while (progress && this.pendingGadgets.size > 0) {
4618
+ while (progress && this.gadgetsAwaitingDependencies.size > 0) {
4369
4619
  progress = false;
4370
4620
  const readyToExecute = [];
4371
4621
  const readyToSkip = [];
4372
- for (const [invocationId, call] of this.pendingGadgets) {
4622
+ for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
4373
4623
  const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
4374
4624
  if (failedDep) {
4375
4625
  readyToSkip.push({ call, failedDep });
@@ -4381,7 +4631,7 @@ var init_stream_processor = __esm({
4381
4631
  }
4382
4632
  }
4383
4633
  for (const { call, failedDep } of readyToSkip) {
4384
- this.pendingGadgets.delete(call.invocationId);
4634
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4385
4635
  const skipEvents = await this.handleFailedDependency(call, failedDep);
4386
4636
  events.push(...skipEvents);
4387
4637
  progress = true;
@@ -4392,7 +4642,7 @@ var init_stream_processor = __esm({
4392
4642
  invocationIds: readyToExecute.map((c) => c.invocationId)
4393
4643
  });
4394
4644
  for (const call of readyToExecute) {
4395
- this.pendingGadgets.delete(call.invocationId);
4645
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4396
4646
  }
4397
4647
  const executePromises = readyToExecute.map((call) => this.executeGadgetWithHooks(call));
4398
4648
  const results = await Promise.all(executePromises);
@@ -4402,9 +4652,9 @@ var init_stream_processor = __esm({
4402
4652
  progress = true;
4403
4653
  }
4404
4654
  }
4405
- if (this.pendingGadgets.size > 0) {
4406
- const pendingIds = new Set(this.pendingGadgets.keys());
4407
- for (const [invocationId, call] of this.pendingGadgets) {
4655
+ if (this.gadgetsAwaitingDependencies.size > 0) {
4656
+ const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
4657
+ for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
4408
4658
  const missingDeps = call.dependencies.filter((dep) => !this.completedResults.has(dep));
4409
4659
  const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
4410
4660
  const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
@@ -4435,10 +4685,103 @@ var init_stream_processor = __esm({
4435
4685
  };
4436
4686
  events.push(skipEvent);
4437
4687
  }
4438
- this.pendingGadgets.clear();
4688
+ this.gadgetsAwaitingDependencies.clear();
4439
4689
  }
4440
4690
  return events;
4441
4691
  }
4692
+ /**
4693
+ * Process pending gadgets, yielding events in real-time.
4694
+ * Generator version that yields events as gadgets complete.
4695
+ *
4696
+ * Note: Gadgets are still executed in parallel for efficiency,
4697
+ * but results are yielded as they become available.
4698
+ */
4699
+ async *processPendingGadgetsGenerator() {
4700
+ let progress = true;
4701
+ while (progress && this.gadgetsAwaitingDependencies.size > 0) {
4702
+ progress = false;
4703
+ const readyToExecute = [];
4704
+ const readyToSkip = [];
4705
+ for (const [_invocationId, call] of this.gadgetsAwaitingDependencies) {
4706
+ const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
4707
+ if (failedDep) {
4708
+ readyToSkip.push({ call, failedDep });
4709
+ continue;
4710
+ }
4711
+ const allSatisfied = call.dependencies.every((dep) => this.completedResults.has(dep));
4712
+ if (allSatisfied) {
4713
+ readyToExecute.push(call);
4714
+ }
4715
+ }
4716
+ for (const { call, failedDep } of readyToSkip) {
4717
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4718
+ const skipEvents = await this.handleFailedDependency(call, failedDep);
4719
+ for (const evt of skipEvents) {
4720
+ yield evt;
4721
+ }
4722
+ progress = true;
4723
+ }
4724
+ if (readyToExecute.length > 0) {
4725
+ this.logger.debug("Executing ready gadgets in parallel", {
4726
+ count: readyToExecute.length,
4727
+ invocationIds: readyToExecute.map((c) => c.invocationId)
4728
+ });
4729
+ for (const call of readyToExecute) {
4730
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4731
+ }
4732
+ const eventSets = await Promise.all(
4733
+ readyToExecute.map(async (call) => {
4734
+ const events = [];
4735
+ for await (const evt of this.executeGadgetGenerator(call)) {
4736
+ events.push(evt);
4737
+ }
4738
+ return events;
4739
+ })
4740
+ );
4741
+ for (const events of eventSets) {
4742
+ for (const evt of events) {
4743
+ yield evt;
4744
+ }
4745
+ }
4746
+ progress = true;
4747
+ }
4748
+ }
4749
+ if (this.gadgetsAwaitingDependencies.size > 0) {
4750
+ const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
4751
+ for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
4752
+ const missingDeps = call.dependencies.filter((dep) => !this.completedResults.has(dep));
4753
+ const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
4754
+ const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
4755
+ let errorMessage;
4756
+ let logLevel = "warn";
4757
+ if (circularDeps.length > 0 && trulyMissingDeps.length > 0) {
4758
+ errorMessage = `Dependencies unresolvable: circular=[${circularDeps.join(", ")}], missing=[${trulyMissingDeps.join(", ")}]`;
4759
+ logLevel = "error";
4760
+ } else if (circularDeps.length > 0) {
4761
+ errorMessage = `Circular dependency detected: "${invocationId}" depends on "${circularDeps[0]}" which also depends on "${invocationId}" (directly or indirectly)`;
4762
+ } else {
4763
+ errorMessage = `Dependency "${missingDeps[0]}" was never executed - check that the invocation ID exists and is spelled correctly`;
4764
+ }
4765
+ this.logger[logLevel]("Gadget has unresolvable dependencies", {
4766
+ gadgetName: call.gadgetName,
4767
+ invocationId,
4768
+ circularDependencies: circularDeps,
4769
+ missingDependencies: trulyMissingDeps
4770
+ });
4771
+ this.failedInvocations.add(invocationId);
4772
+ const skipEvent = {
4773
+ type: "gadget_skipped",
4774
+ gadgetName: call.gadgetName,
4775
+ invocationId,
4776
+ parameters: call.parameters ?? {},
4777
+ failedDependency: missingDeps[0],
4778
+ failedDependencyError: errorMessage
4779
+ };
4780
+ yield skipEvent;
4781
+ }
4782
+ this.gadgetsAwaitingDependencies.clear();
4783
+ }
4784
+ }
4442
4785
  /**
4443
4786
  * Safely execute an observer, catching and logging any errors.
4444
4787
  * Observers are non-critical, so errors are logged but don't crash the system.
@@ -4465,19 +4808,19 @@ var init_stream_processor = __esm({
4465
4808
  );
4466
4809
  }
4467
4810
  /**
4468
- * Check if execution should continue after an error.
4811
+ * Check if execution can recover from an error.
4469
4812
  *
4470
4813
  * Returns true if we should continue processing subsequent gadgets, false if we should stop.
4471
4814
  *
4472
4815
  * Logic:
4473
- * - If custom shouldContinueAfterError is provided, use it
4816
+ * - If custom canRecoverFromGadgetError is provided, use it
4474
4817
  * - Otherwise, use stopOnGadgetError config:
4475
4818
  * - stopOnGadgetError=true → return false (stop execution)
4476
4819
  * - stopOnGadgetError=false → return true (continue execution)
4477
4820
  */
4478
- async checkContinueAfterError(error, gadgetName, errorType, parameters) {
4479
- if (this.shouldContinueAfterError) {
4480
- return await this.shouldContinueAfterError({
4821
+ async checkCanRecoverFromError(error, gadgetName, errorType, parameters) {
4822
+ if (this.canRecoverFromGadgetError) {
4823
+ return await this.canRecoverFromGadgetError({
4481
4824
  error,
4482
4825
  gadgetName,
4483
4826
  errorType,
@@ -4540,14 +4883,14 @@ var init_agent = __esm({
4540
4883
  gadgetStartPrefix;
4541
4884
  gadgetEndPrefix;
4542
4885
  gadgetArgPrefix;
4543
- onHumanInputRequired;
4886
+ requestHumanInput;
4544
4887
  textOnlyHandler;
4545
4888
  textWithGadgetsHandler;
4546
4889
  stopOnGadgetError;
4547
- shouldContinueAfterError;
4890
+ canRecoverFromGadgetError;
4548
4891
  defaultGadgetTimeoutMs;
4549
4892
  defaultMaxTokens;
4550
- userPromptProvided;
4893
+ hasUserPrompt;
4551
4894
  // Gadget output limiting
4552
4895
  outputStore;
4553
4896
  outputLimitEnabled;
@@ -4558,6 +4901,11 @@ var init_agent = __esm({
4558
4901
  mediaStore;
4559
4902
  // Cancellation
4560
4903
  signal;
4904
+ // Subagent configuration
4905
+ agentContextConfig;
4906
+ subagentConfig;
4907
+ // Nested event callback for subagent gadgets
4908
+ onNestedEvent;
4561
4909
  /**
4562
4910
  * Creates a new Agent instance.
4563
4911
  * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
@@ -4577,11 +4925,11 @@ var init_agent = __esm({
4577
4925
  this.gadgetStartPrefix = options.gadgetStartPrefix;
4578
4926
  this.gadgetEndPrefix = options.gadgetEndPrefix;
4579
4927
  this.gadgetArgPrefix = options.gadgetArgPrefix;
4580
- this.onHumanInputRequired = options.onHumanInputRequired;
4928
+ this.requestHumanInput = options.requestHumanInput;
4581
4929
  this.textOnlyHandler = options.textOnlyHandler ?? "terminate";
4582
4930
  this.textWithGadgetsHandler = options.textWithGadgetsHandler;
4583
4931
  this.stopOnGadgetError = options.stopOnGadgetError ?? true;
4584
- this.shouldContinueAfterError = options.shouldContinueAfterError;
4932
+ this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
4585
4933
  this.defaultGadgetTimeoutMs = options.defaultGadgetTimeoutMs;
4586
4934
  this.defaultMaxTokens = this.resolveMaxTokensFromCatalog(options.model);
4587
4935
  this.outputLimitEnabled = options.gadgetOutputLimit ?? DEFAULT_GADGET_OUTPUT_LIMIT;
@@ -4597,7 +4945,7 @@ var init_agent = __esm({
4597
4945
  createGadgetOutputViewer(this.outputStore, this.outputLimitCharLimit)
4598
4946
  );
4599
4947
  }
4600
- this.hooks = this.mergeOutputLimiterHook(options.hooks);
4948
+ this.hooks = this.chainOutputLimiterWithUserHooks(options.hooks);
4601
4949
  const baseBuilder = new LLMMessageBuilder(options.promptConfig);
4602
4950
  if (options.systemPrompt) {
4603
4951
  baseBuilder.addSystem(options.systemPrompt);
@@ -4617,7 +4965,7 @@ var init_agent = __esm({
4617
4965
  endPrefix: options.gadgetEndPrefix,
4618
4966
  argPrefix: options.gadgetArgPrefix
4619
4967
  });
4620
- this.userPromptProvided = !!options.userPrompt;
4968
+ this.hasUserPrompt = !!options.userPrompt;
4621
4969
  if (options.userPrompt) {
4622
4970
  this.conversation.addUserMessage(options.userPrompt);
4623
4971
  }
@@ -4630,6 +4978,12 @@ var init_agent = __esm({
4630
4978
  );
4631
4979
  }
4632
4980
  this.signal = options.signal;
4981
+ this.agentContextConfig = {
4982
+ model: this.model,
4983
+ temperature: this.temperature
4984
+ };
4985
+ this.subagentConfig = options.subagentConfig;
4986
+ this.onNestedEvent = options.onNestedEvent;
4633
4987
  }
4634
4988
  /**
4635
4989
  * Get the gadget registry for this agent.
@@ -4736,7 +5090,7 @@ var init_agent = __esm({
4736
5090
  * @throws {Error} If no user prompt was provided (when using build() without ask())
4737
5091
  */
4738
5092
  async *run() {
4739
- if (!this.userPromptProvided) {
5093
+ if (!this.hasUserPrompt) {
4740
5094
  throw new Error(
4741
5095
  "No user prompt provided. Use .ask(prompt) instead of .build(), or call agent.run() after providing a prompt."
4742
5096
  );
@@ -4853,17 +5207,37 @@ var init_agent = __esm({
4853
5207
  gadgetArgPrefix: this.gadgetArgPrefix,
4854
5208
  hooks: this.hooks,
4855
5209
  logger: this.logger.getSubLogger({ name: "stream-processor" }),
4856
- onHumanInputRequired: this.onHumanInputRequired,
5210
+ requestHumanInput: this.requestHumanInput,
4857
5211
  stopOnGadgetError: this.stopOnGadgetError,
4858
- shouldContinueAfterError: this.shouldContinueAfterError,
5212
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
4859
5213
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
4860
5214
  client: this.client,
4861
- mediaStore: this.mediaStore
5215
+ mediaStore: this.mediaStore,
5216
+ agentConfig: this.agentContextConfig,
5217
+ subagentConfig: this.subagentConfig,
5218
+ onNestedEvent: this.onNestedEvent
4862
5219
  });
4863
- const result = await processor.process(stream2);
4864
- for (const output of result.outputs) {
4865
- yield output;
5220
+ let streamMetadata = null;
5221
+ let gadgetCallCount = 0;
5222
+ const textOutputs = [];
5223
+ const gadgetResults = [];
5224
+ for await (const event of processor.process(stream2)) {
5225
+ if (event.type === "stream_complete") {
5226
+ streamMetadata = event;
5227
+ continue;
5228
+ }
5229
+ if (event.type === "text") {
5230
+ textOutputs.push(event.content);
5231
+ } else if (event.type === "gadget_result") {
5232
+ gadgetCallCount++;
5233
+ gadgetResults.push(event);
5234
+ }
5235
+ yield event;
4866
5236
  }
5237
+ if (!streamMetadata) {
5238
+ throw new Error("Stream processing completed without metadata event");
5239
+ }
5240
+ const result = streamMetadata;
4867
5241
  this.logger.info("LLM response completed", {
4868
5242
  finishReason: result.finishReason,
4869
5243
  usage: result.usage,
@@ -4888,9 +5262,6 @@ var init_agent = __esm({
4888
5262
  });
4889
5263
  let finalMessage = result.finalMessage;
4890
5264
  if (this.hooks.controllers?.afterLLMCall) {
4891
- const gadgetCallCount = result.outputs.filter(
4892
- (output) => output.type === "gadget_result"
4893
- ).length;
4894
5265
  const context = {
4895
5266
  iteration: currentIteration,
4896
5267
  maxIterations: this.maxIterations,
@@ -4911,31 +5282,29 @@ var init_agent = __esm({
4911
5282
  if (msg.role === "user") {
4912
5283
  this.conversation.addUserMessage(msg.content);
4913
5284
  } else if (msg.role === "assistant") {
4914
- this.conversation.addAssistantMessage(extractText(msg.content));
5285
+ this.conversation.addAssistantMessage(extractMessageText(msg.content));
4915
5286
  } else if (msg.role === "system") {
4916
- this.conversation.addUserMessage(`[System] ${extractText(msg.content)}`);
5287
+ this.conversation.addUserMessage(`[System] ${extractMessageText(msg.content)}`);
4917
5288
  }
4918
5289
  }
4919
5290
  }
4920
5291
  }
4921
5292
  if (result.didExecuteGadgets) {
4922
5293
  if (this.textWithGadgetsHandler) {
4923
- const textContent = result.outputs.filter(
4924
- (output) => output.type === "text"
4925
- ).map((output) => output.content).join("");
5294
+ const textContent = textOutputs.join("");
4926
5295
  if (textContent.trim()) {
4927
5296
  const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
4928
- this.conversation.addGadgetCall(
5297
+ this.conversation.addGadgetCallResult(
4929
5298
  gadgetName,
4930
5299
  parameterMapping(textContent),
4931
5300
  resultMapping ? resultMapping(textContent) : textContent
4932
5301
  );
4933
5302
  }
4934
5303
  }
4935
- for (const output of result.outputs) {
5304
+ for (const output of gadgetResults) {
4936
5305
  if (output.type === "gadget_result") {
4937
5306
  const gadgetResult = output.result;
4938
- this.conversation.addGadgetCall(
5307
+ this.conversation.addGadgetCallResult(
4939
5308
  gadgetResult.gadgetName,
4940
5309
  gadgetResult.parameters,
4941
5310
  gadgetResult.error ?? gadgetResult.result ?? "",
@@ -4946,7 +5315,7 @@ var init_agent = __esm({
4946
5315
  }
4947
5316
  } else {
4948
5317
  if (finalMessage.trim()) {
4949
- this.conversation.addGadgetCall(
5318
+ this.conversation.addGadgetCallResult(
4950
5319
  "TellUser",
4951
5320
  { message: finalMessage, done: false, type: "info" },
4952
5321
  `\u2139\uFE0F ${finalMessage}`
@@ -5072,10 +5441,10 @@ var init_agent = __esm({
5072
5441
  return this.client.modelRegistry.getModelLimits(unprefixedModelId)?.maxOutputTokens;
5073
5442
  }
5074
5443
  /**
5075
- * Merge the output limiter interceptor into user-provided hooks.
5444
+ * Chain the output limiter interceptor with user-provided hooks.
5076
5445
  * The limiter runs first, then chains to any user interceptor.
5077
5446
  */
5078
- mergeOutputLimiterHook(userHooks) {
5447
+ chainOutputLimiterWithUserHooks(userHooks) {
5079
5448
  if (!this.outputLimitEnabled) {
5080
5449
  return userHooks ?? {};
5081
5450
  }
@@ -5154,20 +5523,23 @@ var init_builder = __esm({
5154
5523
  promptConfig;
5155
5524
  gadgets = [];
5156
5525
  initialMessages = [];
5157
- onHumanInputRequired;
5526
+ requestHumanInput;
5158
5527
  gadgetStartPrefix;
5159
5528
  gadgetEndPrefix;
5160
5529
  gadgetArgPrefix;
5161
5530
  textOnlyHandler;
5162
5531
  textWithGadgetsHandler;
5163
5532
  stopOnGadgetError;
5164
- shouldContinueAfterError;
5533
+ canRecoverFromGadgetError;
5165
5534
  defaultGadgetTimeoutMs;
5166
5535
  gadgetOutputLimit;
5167
5536
  gadgetOutputLimitPercent;
5168
5537
  compactionConfig;
5169
5538
  signal;
5170
5539
  trailingMessage;
5540
+ subagentConfig;
5541
+ nestedEventCallback;
5542
+ parentContext;
5171
5543
  constructor(client) {
5172
5544
  this.client = client;
5173
5545
  }
@@ -5258,13 +5630,13 @@ var init_builder = __esm({
5258
5630
  *
5259
5631
  * @example
5260
5632
  * ```typescript
5261
- * .withPromptConfig({
5633
+ * .withPromptTemplateConfig({
5262
5634
  * mainInstruction: "Use the gadget markers below:",
5263
5635
  * rules: ["Always use markers", "Never use function calling"]
5264
5636
  * })
5265
5637
  * ```
5266
5638
  */
5267
- withPromptConfig(config) {
5639
+ withPromptTemplateConfig(config) {
5268
5640
  this.promptConfig = config;
5269
5641
  return this;
5270
5642
  }
@@ -5344,7 +5716,7 @@ var init_builder = __esm({
5344
5716
  * ```
5345
5717
  */
5346
5718
  onHumanInput(handler) {
5347
- this.onHumanInputRequired = handler;
5719
+ this.requestHumanInput = handler;
5348
5720
  return this;
5349
5721
  }
5350
5722
  /**
@@ -5479,9 +5851,9 @@ var init_builder = __esm({
5479
5851
  * Provides fine-grained control over whether to continue after different types of errors.
5480
5852
  * Overrides `stopOnGadgetError` when provided.
5481
5853
  *
5482
- * **Note:** This builder method configures the underlying `shouldContinueAfterError` option
5854
+ * **Note:** This builder method configures the underlying `canRecoverFromGadgetError` option
5483
5855
  * in `AgentOptions`. The method is named `withErrorHandler` for better developer experience,
5484
- * but maps to the `shouldContinueAfterError` property internally.
5856
+ * but maps to the `canRecoverFromGadgetError` property internally.
5485
5857
  *
5486
5858
  * @param handler - Function that decides whether to continue after an error.
5487
5859
  * Return `true` to continue execution, `false` to stop.
@@ -5502,7 +5874,7 @@ var init_builder = __esm({
5502
5874
  * ```
5503
5875
  */
5504
5876
  withErrorHandler(handler) {
5505
- this.shouldContinueAfterError = handler;
5877
+ this.canRecoverFromGadgetError = handler;
5506
5878
  return this;
5507
5879
  }
5508
5880
  /**
@@ -5643,6 +6015,95 @@ var init_builder = __esm({
5643
6015
  this.signal = signal;
5644
6016
  return this;
5645
6017
  }
6018
+ /**
6019
+ * Set subagent configuration overrides.
6020
+ *
6021
+ * Subagent gadgets (like BrowseWeb) can read these settings from ExecutionContext
6022
+ * to inherit model and other options from the CLI configuration.
6023
+ *
6024
+ * @param config - Subagent configuration map keyed by gadget name
6025
+ * @returns This builder for chaining
6026
+ *
6027
+ * @example
6028
+ * ```typescript
6029
+ * .withSubagentConfig({
6030
+ * BrowseWeb: { model: "inherit", maxIterations: 20, headless: true },
6031
+ * CodeAnalyzer: { model: "sonnet", maxIterations: 10 }
6032
+ * })
6033
+ * ```
6034
+ */
6035
+ withSubagentConfig(config) {
6036
+ this.subagentConfig = config;
6037
+ return this;
6038
+ }
6039
+ /**
6040
+ * Set the callback for nested subagent events.
6041
+ *
6042
+ * Subagent gadgets (like BrowseWeb) can use ExecutionContext.onNestedEvent
6043
+ * to report their internal LLM calls and gadget executions in real-time.
6044
+ * This callback receives those events, enabling hierarchical progress display.
6045
+ *
6046
+ * @param callback - Function to handle nested agent events
6047
+ * @returns This builder for chaining
6048
+ *
6049
+ * @example
6050
+ * ```typescript
6051
+ * .withNestedEventCallback((event) => {
6052
+ * if (event.type === "llm_call_start") {
6053
+ * console.log(` Nested LLM #${event.event.iteration} starting...`);
6054
+ * } else if (event.type === "gadget_call") {
6055
+ * console.log(` ⏵ ${event.event.call.gadgetName}...`);
6056
+ * }
6057
+ * })
6058
+ * ```
6059
+ */
6060
+ withNestedEventCallback(callback) {
6061
+ this.nestedEventCallback = callback;
6062
+ return this;
6063
+ }
6064
+ /**
6065
+ * Enable automatic nested event forwarding to parent agent.
6066
+ *
6067
+ * When building a subagent inside a gadget, call this method to automatically
6068
+ * forward all LLM calls and gadget events to the parent agent. This enables
6069
+ * hierarchical progress display without any manual event handling.
6070
+ *
6071
+ * The method extracts `invocationId` and `onNestedEvent` from the execution
6072
+ * context and sets up automatic forwarding via hooks and event wrapping.
6073
+ *
6074
+ * @param ctx - ExecutionContext passed to the gadget's execute() method
6075
+ * @param depth - Nesting depth (default: 1 for direct child)
6076
+ * @returns This builder for chaining
6077
+ *
6078
+ * @example
6079
+ * ```typescript
6080
+ * // In a subagent gadget like BrowseWeb - ONE LINE enables auto-forwarding:
6081
+ * execute: async (params, ctx) => {
6082
+ * const agent = new AgentBuilder(client)
6083
+ * .withModel(model)
6084
+ * .withGadgets(Navigate, Click, Screenshot)
6085
+ * .withParentContext(ctx) // <-- This is all you need!
6086
+ * .ask(params.task);
6087
+ *
6088
+ * for await (const event of agent.run()) {
6089
+ * // Events automatically forwarded - just process normally
6090
+ * if (event.type === "text") {
6091
+ * result = event.content;
6092
+ * }
6093
+ * }
6094
+ * }
6095
+ * ```
6096
+ */
6097
+ withParentContext(ctx, depth = 1) {
6098
+ if (ctx.onNestedEvent && ctx.invocationId) {
6099
+ this.parentContext = {
6100
+ invocationId: ctx.invocationId,
6101
+ onNestedEvent: ctx.onNestedEvent,
6102
+ depth
6103
+ };
6104
+ }
6105
+ return this;
6106
+ }
5646
6107
  /**
5647
6108
  * Add an ephemeral trailing message that appears at the end of each LLM request.
5648
6109
  *
@@ -5710,14 +6171,58 @@ ${endPrefix}`
5710
6171
  return this;
5711
6172
  }
5712
6173
  /**
5713
- * Compose the final hooks, including trailing message if configured.
6174
+ * Compose the final hooks, including:
6175
+ * - Trailing message injection (if configured)
6176
+ * - Nested event forwarding for LLM calls (if parentContext is set)
5714
6177
  */
5715
6178
  composeHooks() {
6179
+ let hooks = this.hooks;
6180
+ if (this.parentContext) {
6181
+ const { invocationId, onNestedEvent, depth } = this.parentContext;
6182
+ const existingOnLLMCallStart = hooks?.observers?.onLLMCallStart;
6183
+ const existingOnLLMCallComplete = hooks?.observers?.onLLMCallComplete;
6184
+ hooks = {
6185
+ ...hooks,
6186
+ observers: {
6187
+ ...hooks?.observers,
6188
+ onLLMCallStart: async (context) => {
6189
+ onNestedEvent({
6190
+ type: "llm_call_start",
6191
+ gadgetInvocationId: invocationId,
6192
+ depth,
6193
+ event: {
6194
+ iteration: context.iteration,
6195
+ model: context.options.model
6196
+ }
6197
+ });
6198
+ if (existingOnLLMCallStart) {
6199
+ await existingOnLLMCallStart(context);
6200
+ }
6201
+ },
6202
+ onLLMCallComplete: async (context) => {
6203
+ onNestedEvent({
6204
+ type: "llm_call_end",
6205
+ gadgetInvocationId: invocationId,
6206
+ depth,
6207
+ event: {
6208
+ iteration: context.iteration,
6209
+ model: context.options.model,
6210
+ outputTokens: context.usage?.outputTokens,
6211
+ finishReason: context.finishReason
6212
+ }
6213
+ });
6214
+ if (existingOnLLMCallComplete) {
6215
+ await existingOnLLMCallComplete(context);
6216
+ }
6217
+ }
6218
+ }
6219
+ };
6220
+ }
5716
6221
  if (!this.trailingMessage) {
5717
- return this.hooks;
6222
+ return hooks;
5718
6223
  }
5719
6224
  const trailingMsg = this.trailingMessage;
5720
- const existingBeforeLLMCall = this.hooks?.controllers?.beforeLLMCall;
6225
+ const existingBeforeLLMCall = hooks?.controllers?.beforeLLMCall;
5721
6226
  const trailingMessageController = async (ctx) => {
5722
6227
  const result = existingBeforeLLMCall ? await existingBeforeLLMCall(ctx) : { action: "proceed" };
5723
6228
  if (result.action === "skip") {
@@ -5732,9 +6237,9 @@ ${endPrefix}`
5732
6237
  };
5733
6238
  };
5734
6239
  return {
5735
- ...this.hooks,
6240
+ ...hooks,
5736
6241
  controllers: {
5737
- ...this.hooks?.controllers,
6242
+ ...hooks?.controllers,
5738
6243
  beforeLLMCall: trailingMessageController
5739
6244
  }
5740
6245
  };
@@ -5795,6 +6300,19 @@ ${endPrefix}`
5795
6300
  this.client = new LLMistClass();
5796
6301
  }
5797
6302
  const registry = GadgetRegistry.from(this.gadgets);
6303
+ let onNestedEvent = this.nestedEventCallback;
6304
+ if (this.parentContext) {
6305
+ const { invocationId, onNestedEvent: parentCallback, depth } = this.parentContext;
6306
+ const existingCallback = this.nestedEventCallback;
6307
+ onNestedEvent = (event) => {
6308
+ parentCallback({
6309
+ ...event,
6310
+ gadgetInvocationId: invocationId,
6311
+ depth: event.depth + depth
6312
+ });
6313
+ existingCallback?.(event);
6314
+ };
6315
+ }
5798
6316
  return {
5799
6317
  client: this.client,
5800
6318
  model: this.model ?? "openai:gpt-5-nano",
@@ -5807,19 +6325,21 @@ ${endPrefix}`
5807
6325
  hooks: this.composeHooks(),
5808
6326
  promptConfig: this.promptConfig,
5809
6327
  initialMessages: this.initialMessages,
5810
- onHumanInputRequired: this.onHumanInputRequired,
6328
+ requestHumanInput: this.requestHumanInput,
5811
6329
  gadgetStartPrefix: this.gadgetStartPrefix,
5812
6330
  gadgetEndPrefix: this.gadgetEndPrefix,
5813
6331
  gadgetArgPrefix: this.gadgetArgPrefix,
5814
6332
  textOnlyHandler: this.textOnlyHandler,
5815
6333
  textWithGadgetsHandler: this.textWithGadgetsHandler,
5816
6334
  stopOnGadgetError: this.stopOnGadgetError,
5817
- shouldContinueAfterError: this.shouldContinueAfterError,
6335
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
5818
6336
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
5819
6337
  gadgetOutputLimit: this.gadgetOutputLimit,
5820
6338
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
5821
6339
  compactionConfig: this.compactionConfig,
5822
- signal: this.signal
6340
+ signal: this.signal,
6341
+ subagentConfig: this.subagentConfig,
6342
+ onNestedEvent
5823
6343
  };
5824
6344
  }
5825
6345
  ask(userPrompt) {
@@ -5976,6 +6496,19 @@ ${endPrefix}`
5976
6496
  this.client = new LLMistClass();
5977
6497
  }
5978
6498
  const registry = GadgetRegistry.from(this.gadgets);
6499
+ let onNestedEvent = this.nestedEventCallback;
6500
+ if (this.parentContext) {
6501
+ const { invocationId, onNestedEvent: parentCallback, depth } = this.parentContext;
6502
+ const existingCallback = this.nestedEventCallback;
6503
+ onNestedEvent = (event) => {
6504
+ parentCallback({
6505
+ ...event,
6506
+ gadgetInvocationId: invocationId,
6507
+ depth: event.depth + depth
6508
+ });
6509
+ existingCallback?.(event);
6510
+ };
6511
+ }
5979
6512
  const options = {
5980
6513
  client: this.client,
5981
6514
  model: this.model ?? "openai:gpt-5-nano",
@@ -5988,19 +6521,21 @@ ${endPrefix}`
5988
6521
  hooks: this.composeHooks(),
5989
6522
  promptConfig: this.promptConfig,
5990
6523
  initialMessages: this.initialMessages,
5991
- onHumanInputRequired: this.onHumanInputRequired,
6524
+ requestHumanInput: this.requestHumanInput,
5992
6525
  gadgetStartPrefix: this.gadgetStartPrefix,
5993
6526
  gadgetEndPrefix: this.gadgetEndPrefix,
5994
6527
  gadgetArgPrefix: this.gadgetArgPrefix,
5995
6528
  textOnlyHandler: this.textOnlyHandler,
5996
6529
  textWithGadgetsHandler: this.textWithGadgetsHandler,
5997
6530
  stopOnGadgetError: this.stopOnGadgetError,
5998
- shouldContinueAfterError: this.shouldContinueAfterError,
6531
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
5999
6532
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
6000
6533
  gadgetOutputLimit: this.gadgetOutputLimit,
6001
6534
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
6002
6535
  compactionConfig: this.compactionConfig,
6003
- signal: this.signal
6536
+ signal: this.signal,
6537
+ subagentConfig: this.subagentConfig,
6538
+ onNestedEvent
6004
6539
  };
6005
6540
  return new Agent(AGENT_INTERNAL_KEY, options);
6006
6541
  }
@@ -6306,9 +6841,9 @@ var init_base_provider = __esm({
6306
6841
  */
6307
6842
  async *stream(options, descriptor, spec) {
6308
6843
  const preparedMessages = this.prepareMessages(options.messages);
6309
- const payload = this.buildRequestPayload(options, descriptor, spec, preparedMessages);
6844
+ const payload = this.buildApiRequest(options, descriptor, spec, preparedMessages);
6310
6845
  const rawStream = await this.executeStreamRequest(payload, options.signal);
6311
- yield* this.wrapStream(rawStream);
6846
+ yield* this.normalizeProviderStream(rawStream);
6312
6847
  }
6313
6848
  /**
6314
6849
  * Prepare messages for the request.
@@ -6408,11 +6943,11 @@ var init_anthropic = __esm({
6408
6943
  "Anthropic does not support speech generation. Use OpenAI (TTS) or Google Gemini (TTS) instead."
6409
6944
  );
6410
6945
  }
6411
- buildRequestPayload(options, descriptor, spec, messages) {
6946
+ buildApiRequest(options, descriptor, spec, messages) {
6412
6947
  const systemMessages = messages.filter((message) => message.role === "system");
6413
6948
  const system = systemMessages.length > 0 ? systemMessages.map((m, index) => ({
6414
6949
  type: "text",
6415
- text: extractText(m.content),
6950
+ text: extractMessageText(m.content),
6416
6951
  // Add cache_control to the LAST system message block
6417
6952
  ...index === systemMessages.length - 1 ? { cache_control: { type: "ephemeral" } } : {}
6418
6953
  })) : void 0;
@@ -6449,7 +6984,7 @@ var init_anthropic = __esm({
6449
6984
  * Handles text, images (base64 only), and applies cache_control.
6450
6985
  */
6451
6986
  convertToAnthropicContent(content, addCacheControl) {
6452
- const parts = normalizeContent(content);
6987
+ const parts = normalizeMessageContent(content);
6453
6988
  return parts.map((part, index) => {
6454
6989
  const isLastPart = index === parts.length - 1;
6455
6990
  const cacheControl = addCacheControl && isLastPart ? { cache_control: { type: "ephemeral" } } : {};
@@ -6495,7 +7030,7 @@ var init_anthropic = __esm({
6495
7030
  const stream2 = await client.messages.create(payload, signal ? { signal } : void 0);
6496
7031
  return stream2;
6497
7032
  }
6498
- async *wrapStream(iterable) {
7033
+ async *normalizeProviderStream(iterable) {
6499
7034
  const stream2 = iterable;
6500
7035
  let inputTokens = 0;
6501
7036
  let cachedInputTokens = 0;
@@ -6572,7 +7107,7 @@ var init_anthropic = __esm({
6572
7107
  async countTokens(messages, descriptor, _spec) {
6573
7108
  const client = this.client;
6574
7109
  const systemMessages = messages.filter((message) => message.role === "system");
6575
- const system = systemMessages.length > 0 ? systemMessages.map((m) => extractText(m.content)).join("\n\n") : void 0;
7110
+ const system = systemMessages.length > 0 ? systemMessages.map((m) => extractMessageText(m.content)).join("\n\n") : void 0;
6576
7111
  const conversation = messages.filter(
6577
7112
  (message) => message.role !== "system"
6578
7113
  ).map((message) => ({
@@ -6594,7 +7129,7 @@ var init_anthropic = __esm({
6594
7129
  let totalChars = 0;
6595
7130
  let imageCount = 0;
6596
7131
  for (const msg of messages) {
6597
- const parts = normalizeContent(msg.content);
7132
+ const parts = normalizeMessageContent(msg.content);
6598
7133
  for (const part of parts) {
6599
7134
  if (part.type === "text") {
6600
7135
  totalChars += part.text.length;
@@ -7285,7 +7820,7 @@ var init_gemini = __esm({
7285
7820
  format: spec?.defaultFormat ?? "wav"
7286
7821
  };
7287
7822
  }
7288
- buildRequestPayload(options, descriptor, _spec, messages) {
7823
+ buildApiRequest(options, descriptor, _spec, messages) {
7289
7824
  const contents = this.convertMessagesToContents(messages);
7290
7825
  const generationConfig = this.buildGenerationConfig(options);
7291
7826
  const config = {
@@ -7337,7 +7872,7 @@ var init_gemini = __esm({
7337
7872
  if (message.role === "system") {
7338
7873
  expandedMessages.push({
7339
7874
  role: "user",
7340
- content: extractText(message.content)
7875
+ content: extractMessageText(message.content)
7341
7876
  });
7342
7877
  expandedMessages.push({
7343
7878
  role: "assistant",
@@ -7387,7 +7922,7 @@ var init_gemini = __esm({
7387
7922
  * Handles text, images, and audio (Gemini supports all three).
7388
7923
  */
7389
7924
  convertToGeminiParts(content) {
7390
- const parts = normalizeContent(content);
7925
+ const parts = normalizeMessageContent(content);
7391
7926
  return parts.map((part) => {
7392
7927
  if (part.type === "text") {
7393
7928
  return { text: part.text };
@@ -7432,10 +7967,10 @@ var init_gemini = __esm({
7432
7967
  }
7433
7968
  return Object.keys(config).length > 0 ? config : null;
7434
7969
  }
7435
- async *wrapStream(iterable) {
7970
+ async *normalizeProviderStream(iterable) {
7436
7971
  const stream2 = iterable;
7437
7972
  for await (const chunk of stream2) {
7438
- const text3 = this.extractText(chunk);
7973
+ const text3 = this.extractMessageText(chunk);
7439
7974
  if (text3) {
7440
7975
  yield { text: text3, rawEvent: chunk };
7441
7976
  }
@@ -7446,7 +7981,7 @@ var init_gemini = __esm({
7446
7981
  }
7447
7982
  }
7448
7983
  }
7449
- extractText(chunk) {
7984
+ extractMessageText(chunk) {
7450
7985
  if (!chunk?.candidates) {
7451
7986
  return "";
7452
7987
  }
@@ -7511,7 +8046,7 @@ var init_gemini = __esm({
7511
8046
  let totalChars = 0;
7512
8047
  let mediaCount = 0;
7513
8048
  for (const msg of messages) {
7514
- const parts = normalizeContent(msg.content);
8049
+ const parts = normalizeMessageContent(msg.content);
7515
8050
  for (const part of parts) {
7516
8051
  if (part.type === "text") {
7517
8052
  totalChars += part.text.length;
@@ -8257,7 +8792,7 @@ var init_openai = __esm({
8257
8792
  format
8258
8793
  };
8259
8794
  }
8260
- buildRequestPayload(options, descriptor, spec, messages) {
8795
+ buildApiRequest(options, descriptor, spec, messages) {
8261
8796
  const { maxTokens, temperature, topP, stopSequences, extra } = options;
8262
8797
  const supportsTemperature = spec?.metadata?.supportsTemperature !== false;
8263
8798
  const shouldIncludeTemperature = typeof temperature === "number" && supportsTemperature;
@@ -8292,7 +8827,7 @@ var init_openai = __esm({
8292
8827
  ...message.name ? { name: message.name } : {}
8293
8828
  };
8294
8829
  }
8295
- const textContent = typeof message.content === "string" ? message.content : extractText(message.content);
8830
+ const textContent = typeof message.content === "string" ? message.content : extractMessageText(message.content);
8296
8831
  if (role === "system") {
8297
8832
  return {
8298
8833
  role: "system",
@@ -8352,7 +8887,7 @@ var init_openai = __esm({
8352
8887
  const stream2 = await client.chat.completions.create(payload, signal ? { signal } : void 0);
8353
8888
  return stream2;
8354
8889
  }
8355
- async *wrapStream(iterable) {
8890
+ async *normalizeProviderStream(iterable) {
8356
8891
  const stream2 = iterable;
8357
8892
  for await (const chunk of stream2) {
8358
8893
  const text3 = chunk.choices.map((choice) => choice.delta?.content ?? "").join("");
@@ -8410,9 +8945,9 @@ var init_openai = __esm({
8410
8945
  tokenCount += OPENAI_MESSAGE_OVERHEAD_TOKENS;
8411
8946
  const roleText = ROLE_MAP[message.role];
8412
8947
  tokenCount += encoding.encode(roleText).length;
8413
- const textContent = extractText(message.content);
8948
+ const textContent = extractMessageText(message.content);
8414
8949
  tokenCount += encoding.encode(textContent).length;
8415
- const parts = normalizeContent(message.content);
8950
+ const parts = normalizeMessageContent(message.content);
8416
8951
  for (const part of parts) {
8417
8952
  if (part.type === "image") {
8418
8953
  imageCount++;
@@ -8437,7 +8972,7 @@ var init_openai = __esm({
8437
8972
  let totalChars = 0;
8438
8973
  let imageCount = 0;
8439
8974
  for (const msg of messages) {
8440
- const parts = normalizeContent(msg.content);
8975
+ const parts = normalizeMessageContent(msg.content);
8441
8976
  for (const part of parts) {
8442
8977
  if (part.type === "text") {
8443
8978
  totalChars += part.text.length;
@@ -10125,7 +10660,7 @@ var MockBuilder = class {
10125
10660
  whenMessageContains(text3) {
10126
10661
  this.matchers.push(
10127
10662
  (ctx) => ctx.messages.some(
10128
- (msg) => extractText(msg.content).toLowerCase().includes(text3.toLowerCase())
10663
+ (msg) => extractMessageText(msg.content).toLowerCase().includes(text3.toLowerCase())
10129
10664
  )
10130
10665
  );
10131
10666
  return this;
@@ -10140,7 +10675,7 @@ var MockBuilder = class {
10140
10675
  this.matchers.push((ctx) => {
10141
10676
  const lastMsg = ctx.messages[ctx.messages.length - 1];
10142
10677
  if (!lastMsg) return false;
10143
- return extractText(lastMsg.content).toLowerCase().includes(text3.toLowerCase());
10678
+ return extractMessageText(lastMsg.content).toLowerCase().includes(text3.toLowerCase());
10144
10679
  });
10145
10680
  return this;
10146
10681
  }
@@ -10151,7 +10686,7 @@ var MockBuilder = class {
10151
10686
  * mockLLM().whenMessageMatches(/calculate \d+/)
10152
10687
  */
10153
10688
  whenMessageMatches(regex) {
10154
- this.matchers.push((ctx) => ctx.messages.some((msg) => regex.test(extractText(msg.content))));
10689
+ this.matchers.push((ctx) => ctx.messages.some((msg) => regex.test(extractMessageText(msg.content))));
10155
10690
  return this;
10156
10691
  }
10157
10692
  /**
@@ -10163,7 +10698,7 @@ var MockBuilder = class {
10163
10698
  whenRoleContains(role, text3) {
10164
10699
  this.matchers.push(
10165
10700
  (ctx) => ctx.messages.some(
10166
- (msg) => msg.role === role && extractText(msg.content).toLowerCase().includes(text3.toLowerCase())
10701
+ (msg) => msg.role === role && extractMessageText(msg.content).toLowerCase().includes(text3.toLowerCase())
10167
10702
  )
10168
10703
  );
10169
10704
  return this;
@@ -10585,7 +11120,7 @@ var MockConversationManager = class {
10585
11120
  this.history.push(msg);
10586
11121
  this.addedMessages.push(msg);
10587
11122
  }
10588
- addGadgetCall(gadgetName, parameters, result) {
11123
+ addGadgetCallResult(gadgetName, parameters, result) {
10589
11124
  const assistantMsg = {
10590
11125
  role: "assistant",
10591
11126
  content: `!!!GADGET_START:${gadgetName}
@@ -10695,7 +11230,7 @@ function createMockConversationManager(turnCount, baseMessages = []) {
10695
11230
 
10696
11231
  // src/testing/mock-gadget.ts
10697
11232
  init_gadget();
10698
- var MockGadgetImpl = class extends BaseGadget {
11233
+ var MockGadgetImpl = class extends AbstractGadget {
10699
11234
  name;
10700
11235
  description;
10701
11236
  parameterSchema;
@@ -10948,22 +11483,23 @@ export {
10948
11483
  resolveRulesTemplate,
10949
11484
  resolveHintTemplate,
10950
11485
  init_prompt_config,
10951
- normalizeContent,
10952
- extractText,
11486
+ normalizeMessageContent,
11487
+ extractMessageText,
10953
11488
  LLMMessageBuilder,
10954
11489
  init_messages,
10955
11490
  MediaStore,
10956
11491
  init_media_store,
10957
- BreakLoopException,
10958
- HumanInputException,
10959
- AbortError,
11492
+ TaskCompletionSignal,
11493
+ HumanInputRequiredException,
11494
+ TimeoutException,
11495
+ AbortException,
10960
11496
  init_exceptions,
10961
11497
  createLogger,
10962
11498
  defaultLogger,
10963
11499
  init_logger,
10964
11500
  schemaToJSONSchema,
10965
11501
  init_schema_to_json,
10966
- BaseGadget,
11502
+ AbstractGadget,
10967
11503
  init_gadget,
10968
11504
  createGadget,
10969
11505
  init_create_gadget,
@@ -10987,7 +11523,7 @@ export {
10987
11523
  init_event_handlers,
10988
11524
  GadgetOutputStore,
10989
11525
  init_gadget_output_store,
10990
- StreamParser,
11526
+ GadgetCallParser,
10991
11527
  init_parser,
10992
11528
  GadgetExecutor,
10993
11529
  init_executor,
@@ -11059,4 +11595,4 @@ export {
11059
11595
  createEmptyStream,
11060
11596
  createErrorStream
11061
11597
  };
11062
- //# sourceMappingURL=chunk-364PEMVT.js.map
11598
+ //# sourceMappingURL=chunk-JCFPJMRQ.js.map