llmist 3.0.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.
@@ -3549,7 +3549,7 @@ var init_executor = __esm({
3549
3549
  init_exceptions();
3550
3550
  init_parser();
3551
3551
  GadgetExecutor = class {
3552
- constructor(registry, requestHumanInput, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig) {
3552
+ constructor(registry, requestHumanInput, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig, onNestedEvent) {
3553
3553
  this.registry = registry;
3554
3554
  this.requestHumanInput = requestHumanInput;
3555
3555
  this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
@@ -3557,6 +3557,7 @@ var init_executor = __esm({
3557
3557
  this.mediaStore = mediaStore;
3558
3558
  this.agentConfig = agentConfig;
3559
3559
  this.subagentConfig = subagentConfig;
3560
+ this.onNestedEvent = onNestedEvent;
3560
3561
  this.logger = logger ?? createLogger({ name: "llmist:executor" });
3561
3562
  this.errorFormatter = new GadgetExecutionErrorFormatter(errorFormatterOptions);
3562
3563
  this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
@@ -3702,7 +3703,9 @@ var init_executor = __esm({
3702
3703
  llmist: this.client ? new CostReportingLLMistWrapper(this.client, reportCost) : void 0,
3703
3704
  signal: abortController.signal,
3704
3705
  agentConfig: this.agentConfig,
3705
- subagentConfig: this.subagentConfig
3706
+ subagentConfig: this.subagentConfig,
3707
+ invocationId: call.invocationId,
3708
+ onNestedEvent: this.onNestedEvent
3706
3709
  };
3707
3710
  let rawResult;
3708
3711
  if (timeoutMs && timeoutMs > 0) {
@@ -3941,14 +3944,21 @@ var init_stream_processor = __esm({
3941
3944
  options.client,
3942
3945
  options.mediaStore,
3943
3946
  options.agentConfig,
3944
- options.subagentConfig
3947
+ options.subagentConfig,
3948
+ options.onNestedEvent
3945
3949
  );
3946
3950
  }
3947
3951
  /**
3948
- * 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.
3949
3960
  */
3950
- async process(stream2) {
3951
- const outputs = [];
3961
+ async *process(stream2) {
3952
3962
  let finishReason = null;
3953
3963
  let usage;
3954
3964
  let didExecuteGadgets = false;
@@ -3994,14 +4004,13 @@ var init_stream_processor = __esm({
3994
4004
  continue;
3995
4005
  }
3996
4006
  for (const event of this.parser.feed(processedChunk)) {
3997
- const processedEvents = await this.processEvent(event);
3998
- outputs.push(...processedEvents);
3999
- if (processedEvents.some((e) => e.type === "gadget_result")) {
4000
- didExecuteGadgets = true;
4001
- }
4002
- for (const evt of processedEvents) {
4003
- if (evt.type === "gadget_result" && evt.result.breaksLoop) {
4004
- 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
+ }
4005
4014
  }
4006
4015
  }
4007
4016
  }
@@ -4012,25 +4021,23 @@ var init_stream_processor = __esm({
4012
4021
  }
4013
4022
  if (!this.executionHalted) {
4014
4023
  for (const event of this.parser.finalize()) {
4015
- const processedEvents = await this.processEvent(event);
4016
- outputs.push(...processedEvents);
4017
- if (processedEvents.some((e) => e.type === "gadget_result")) {
4018
- didExecuteGadgets = true;
4019
- }
4020
- for (const evt of processedEvents) {
4021
- if (evt.type === "gadget_result" && evt.result.breaksLoop) {
4022
- 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
+ }
4023
4031
  }
4024
4032
  }
4025
4033
  }
4026
- const finalPendingEvents = await this.processPendingGadgets();
4027
- outputs.push(...finalPendingEvents);
4028
- if (finalPendingEvents.some((e) => e.type === "gadget_result")) {
4029
- didExecuteGadgets = true;
4030
- }
4031
- for (const evt of finalPendingEvents) {
4032
- if (evt.type === "gadget_result" && evt.result.breaksLoop) {
4033
- 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
+ }
4034
4041
  }
4035
4042
  }
4036
4043
  }
@@ -4043,8 +4050,8 @@ var init_stream_processor = __esm({
4043
4050
  };
4044
4051
  finalMessage = this.hooks.interceptors.interceptAssistantMessage(finalMessage, context);
4045
4052
  }
4046
- return {
4047
- outputs,
4053
+ const completionEvent = {
4054
+ type: "stream_complete",
4048
4055
  shouldBreakLoop,
4049
4056
  didExecuteGadgets,
4050
4057
  finishReason,
@@ -4052,9 +4059,11 @@ var init_stream_processor = __esm({
4052
4059
  rawResponse: this.responseText,
4053
4060
  finalMessage
4054
4061
  };
4062
+ yield completionEvent;
4055
4063
  }
4056
4064
  /**
4057
4065
  * Process a single parsed event (text or gadget call).
4066
+ * @deprecated Use processEventGenerator for real-time streaming
4058
4067
  */
4059
4068
  async processEvent(event) {
4060
4069
  if (event.type === "text") {
@@ -4064,6 +4073,23 @@ var init_stream_processor = __esm({
4064
4073
  }
4065
4074
  return [event];
4066
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
+ }
4067
4093
  /**
4068
4094
  * Process a text event through interceptors.
4069
4095
  */
@@ -4140,9 +4166,68 @@ var init_stream_processor = __esm({
4140
4166
  events.push(...triggeredEvents);
4141
4167
  return events;
4142
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
+ }
4143
4227
  /**
4144
4228
  * Execute a gadget through the full hook lifecycle.
4145
4229
  * This is the core execution logic, extracted from processGadgetCall.
4230
+ * @deprecated Use executeGadgetGenerator for real-time streaming
4146
4231
  */
4147
4232
  async executeGadgetWithHooks(call) {
4148
4233
  const events = [];
@@ -4295,6 +4380,159 @@ var init_stream_processor = __esm({
4295
4380
  }
4296
4381
  return events;
4297
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
+ }
4298
4536
  /**
4299
4537
  * Handle a gadget that cannot execute because a dependency failed.
4300
4538
  * Calls the onDependencySkipped controller to allow customization.
@@ -4451,6 +4689,99 @@ var init_stream_processor = __esm({
4451
4689
  }
4452
4690
  return events;
4453
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
+ }
4454
4785
  /**
4455
4786
  * Safely execute an observer, catching and logging any errors.
4456
4787
  * Observers are non-critical, so errors are logged but don't crash the system.
@@ -4573,6 +4904,8 @@ var init_agent = __esm({
4573
4904
  // Subagent configuration
4574
4905
  agentContextConfig;
4575
4906
  subagentConfig;
4907
+ // Nested event callback for subagent gadgets
4908
+ onNestedEvent;
4576
4909
  /**
4577
4910
  * Creates a new Agent instance.
4578
4911
  * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
@@ -4650,6 +4983,7 @@ var init_agent = __esm({
4650
4983
  temperature: this.temperature
4651
4984
  };
4652
4985
  this.subagentConfig = options.subagentConfig;
4986
+ this.onNestedEvent = options.onNestedEvent;
4653
4987
  }
4654
4988
  /**
4655
4989
  * Get the gadget registry for this agent.
@@ -4880,12 +5214,30 @@ var init_agent = __esm({
4880
5214
  client: this.client,
4881
5215
  mediaStore: this.mediaStore,
4882
5216
  agentConfig: this.agentContextConfig,
4883
- subagentConfig: this.subagentConfig
5217
+ subagentConfig: this.subagentConfig,
5218
+ onNestedEvent: this.onNestedEvent
4884
5219
  });
4885
- const result = await processor.process(stream2);
4886
- for (const output of result.outputs) {
4887
- 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;
4888
5236
  }
5237
+ if (!streamMetadata) {
5238
+ throw new Error("Stream processing completed without metadata event");
5239
+ }
5240
+ const result = streamMetadata;
4889
5241
  this.logger.info("LLM response completed", {
4890
5242
  finishReason: result.finishReason,
4891
5243
  usage: result.usage,
@@ -4910,9 +5262,6 @@ var init_agent = __esm({
4910
5262
  });
4911
5263
  let finalMessage = result.finalMessage;
4912
5264
  if (this.hooks.controllers?.afterLLMCall) {
4913
- const gadgetCallCount = result.outputs.filter(
4914
- (output) => output.type === "gadget_result"
4915
- ).length;
4916
5265
  const context = {
4917
5266
  iteration: currentIteration,
4918
5267
  maxIterations: this.maxIterations,
@@ -4942,9 +5291,7 @@ var init_agent = __esm({
4942
5291
  }
4943
5292
  if (result.didExecuteGadgets) {
4944
5293
  if (this.textWithGadgetsHandler) {
4945
- const textContent = result.outputs.filter(
4946
- (output) => output.type === "text"
4947
- ).map((output) => output.content).join("");
5294
+ const textContent = textOutputs.join("");
4948
5295
  if (textContent.trim()) {
4949
5296
  const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
4950
5297
  this.conversation.addGadgetCallResult(
@@ -4954,7 +5301,7 @@ var init_agent = __esm({
4954
5301
  );
4955
5302
  }
4956
5303
  }
4957
- for (const output of result.outputs) {
5304
+ for (const output of gadgetResults) {
4958
5305
  if (output.type === "gadget_result") {
4959
5306
  const gadgetResult = output.result;
4960
5307
  this.conversation.addGadgetCallResult(
@@ -5191,6 +5538,8 @@ var init_builder = __esm({
5191
5538
  signal;
5192
5539
  trailingMessage;
5193
5540
  subagentConfig;
5541
+ nestedEventCallback;
5542
+ parentContext;
5194
5543
  constructor(client) {
5195
5544
  this.client = client;
5196
5545
  }
@@ -5687,6 +6036,74 @@ var init_builder = __esm({
5687
6036
  this.subagentConfig = config;
5688
6037
  return this;
5689
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
+ }
5690
6107
  /**
5691
6108
  * Add an ephemeral trailing message that appears at the end of each LLM request.
5692
6109
  *
@@ -5754,14 +6171,58 @@ ${endPrefix}`
5754
6171
  return this;
5755
6172
  }
5756
6173
  /**
5757
- * 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)
5758
6177
  */
5759
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
+ }
5760
6221
  if (!this.trailingMessage) {
5761
- return this.hooks;
6222
+ return hooks;
5762
6223
  }
5763
6224
  const trailingMsg = this.trailingMessage;
5764
- const existingBeforeLLMCall = this.hooks?.controllers?.beforeLLMCall;
6225
+ const existingBeforeLLMCall = hooks?.controllers?.beforeLLMCall;
5765
6226
  const trailingMessageController = async (ctx) => {
5766
6227
  const result = existingBeforeLLMCall ? await existingBeforeLLMCall(ctx) : { action: "proceed" };
5767
6228
  if (result.action === "skip") {
@@ -5776,9 +6237,9 @@ ${endPrefix}`
5776
6237
  };
5777
6238
  };
5778
6239
  return {
5779
- ...this.hooks,
6240
+ ...hooks,
5780
6241
  controllers: {
5781
- ...this.hooks?.controllers,
6242
+ ...hooks?.controllers,
5782
6243
  beforeLLMCall: trailingMessageController
5783
6244
  }
5784
6245
  };
@@ -5839,6 +6300,19 @@ ${endPrefix}`
5839
6300
  this.client = new LLMistClass();
5840
6301
  }
5841
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
+ }
5842
6316
  return {
5843
6317
  client: this.client,
5844
6318
  model: this.model ?? "openai:gpt-5-nano",
@@ -5864,7 +6338,8 @@ ${endPrefix}`
5864
6338
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
5865
6339
  compactionConfig: this.compactionConfig,
5866
6340
  signal: this.signal,
5867
- subagentConfig: this.subagentConfig
6341
+ subagentConfig: this.subagentConfig,
6342
+ onNestedEvent
5868
6343
  };
5869
6344
  }
5870
6345
  ask(userPrompt) {
@@ -6021,6 +6496,19 @@ ${endPrefix}`
6021
6496
  this.client = new LLMistClass();
6022
6497
  }
6023
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
+ }
6024
6512
  const options = {
6025
6513
  client: this.client,
6026
6514
  model: this.model ?? "openai:gpt-5-nano",
@@ -6046,7 +6534,8 @@ ${endPrefix}`
6046
6534
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
6047
6535
  compactionConfig: this.compactionConfig,
6048
6536
  signal: this.signal,
6049
- subagentConfig: this.subagentConfig
6537
+ subagentConfig: this.subagentConfig,
6538
+ onNestedEvent
6050
6539
  };
6051
6540
  return new Agent(AGENT_INTERNAL_KEY, options);
6052
6541
  }
@@ -11106,4 +11595,4 @@ export {
11106
11595
  createEmptyStream,
11107
11596
  createErrorStream
11108
11597
  };
11109
- //# sourceMappingURL=chunk-67MMSOAT.js.map
11598
+ //# sourceMappingURL=chunk-JCFPJMRQ.js.map