llmist 3.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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, onSubagentEvent) {
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.onSubagentEvent = onSubagentEvent;
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
+ onSubagentEvent: this.onSubagentEvent
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.onSubagentEvent
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,12 @@ var init_agent = __esm({
4573
4904
  // Subagent configuration
4574
4905
  agentContextConfig;
4575
4906
  subagentConfig;
4907
+ // Subagent event callback for subagent gadgets
4908
+ userSubagentEventCallback;
4909
+ // Internal queue for yielding subagent events in run()
4910
+ pendingSubagentEvents = [];
4911
+ // Combined callback that queues events AND calls user callback
4912
+ onSubagentEvent;
4576
4913
  /**
4577
4914
  * Creates a new Agent instance.
4578
4915
  * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
@@ -4650,6 +4987,71 @@ var init_agent = __esm({
4650
4987
  temperature: this.temperature
4651
4988
  };
4652
4989
  this.subagentConfig = options.subagentConfig;
4990
+ this.userSubagentEventCallback = options.onSubagentEvent;
4991
+ this.onSubagentEvent = (event) => {
4992
+ this.pendingSubagentEvents.push(event);
4993
+ this.userSubagentEventCallback?.(event);
4994
+ const subagentContext = {
4995
+ parentGadgetInvocationId: event.gadgetInvocationId,
4996
+ depth: event.depth
4997
+ };
4998
+ if (event.type === "llm_call_start") {
4999
+ const info = event.event;
5000
+ void this.hooks?.observers?.onLLMCallStart?.({
5001
+ iteration: info.iteration,
5002
+ options: { model: info.model, messages: [] },
5003
+ logger: this.logger,
5004
+ subagentContext
5005
+ });
5006
+ } else if (event.type === "llm_call_end") {
5007
+ const info = event.event;
5008
+ void this.hooks?.observers?.onLLMCallComplete?.({
5009
+ iteration: info.iteration,
5010
+ options: { model: info.model, messages: [] },
5011
+ finishReason: info.finishReason ?? null,
5012
+ usage: info.outputTokens ? {
5013
+ inputTokens: info.inputTokens ?? 0,
5014
+ outputTokens: info.outputTokens,
5015
+ totalTokens: (info.inputTokens ?? 0) + info.outputTokens
5016
+ } : void 0,
5017
+ rawResponse: "",
5018
+ finalMessage: "",
5019
+ logger: this.logger,
5020
+ subagentContext
5021
+ });
5022
+ } else if (event.type === "gadget_call") {
5023
+ const gadgetEvent = event.event;
5024
+ void this.hooks?.observers?.onGadgetExecutionStart?.({
5025
+ iteration: 0,
5026
+ gadgetName: gadgetEvent.call.gadgetName,
5027
+ invocationId: gadgetEvent.call.invocationId,
5028
+ parameters: gadgetEvent.call.parameters ?? {},
5029
+ logger: this.logger,
5030
+ subagentContext
5031
+ });
5032
+ } else if (event.type === "gadget_result") {
5033
+ const resultEvent = event.event;
5034
+ void this.hooks?.observers?.onGadgetExecutionComplete?.({
5035
+ iteration: 0,
5036
+ gadgetName: resultEvent.result.gadgetName ?? "unknown",
5037
+ invocationId: resultEvent.result.invocationId,
5038
+ parameters: {},
5039
+ executionTimeMs: resultEvent.result.executionTimeMs ?? 0,
5040
+ logger: this.logger,
5041
+ subagentContext
5042
+ });
5043
+ }
5044
+ };
5045
+ }
5046
+ /**
5047
+ * Flush pending subagent events as StreamEvents.
5048
+ * Called from run() to yield queued subagent events from subagent gadgets.
5049
+ */
5050
+ *flushPendingSubagentEvents() {
5051
+ while (this.pendingSubagentEvents.length > 0) {
5052
+ const event = this.pendingSubagentEvents.shift();
5053
+ yield { type: "subagent_event", subagentEvent: event };
5054
+ }
4653
5055
  }
4654
5056
  /**
4655
5057
  * Get the gadget registry for this agent.
@@ -4880,12 +5282,31 @@ var init_agent = __esm({
4880
5282
  client: this.client,
4881
5283
  mediaStore: this.mediaStore,
4882
5284
  agentConfig: this.agentContextConfig,
4883
- subagentConfig: this.subagentConfig
5285
+ subagentConfig: this.subagentConfig,
5286
+ onSubagentEvent: this.onSubagentEvent
4884
5287
  });
4885
- const result = await processor.process(stream2);
4886
- for (const output of result.outputs) {
4887
- yield output;
5288
+ let streamMetadata = null;
5289
+ let gadgetCallCount = 0;
5290
+ const textOutputs = [];
5291
+ const gadgetResults = [];
5292
+ for await (const event of processor.process(stream2)) {
5293
+ if (event.type === "stream_complete") {
5294
+ streamMetadata = event;
5295
+ continue;
5296
+ }
5297
+ if (event.type === "text") {
5298
+ textOutputs.push(event.content);
5299
+ } else if (event.type === "gadget_result") {
5300
+ gadgetCallCount++;
5301
+ gadgetResults.push(event);
5302
+ }
5303
+ yield event;
5304
+ yield* this.flushPendingSubagentEvents();
4888
5305
  }
5306
+ if (!streamMetadata) {
5307
+ throw new Error("Stream processing completed without metadata event");
5308
+ }
5309
+ const result = streamMetadata;
4889
5310
  this.logger.info("LLM response completed", {
4890
5311
  finishReason: result.finishReason,
4891
5312
  usage: result.usage,
@@ -4910,9 +5331,6 @@ var init_agent = __esm({
4910
5331
  });
4911
5332
  let finalMessage = result.finalMessage;
4912
5333
  if (this.hooks.controllers?.afterLLMCall) {
4913
- const gadgetCallCount = result.outputs.filter(
4914
- (output) => output.type === "gadget_result"
4915
- ).length;
4916
5334
  const context = {
4917
5335
  iteration: currentIteration,
4918
5336
  maxIterations: this.maxIterations,
@@ -4942,9 +5360,7 @@ var init_agent = __esm({
4942
5360
  }
4943
5361
  if (result.didExecuteGadgets) {
4944
5362
  if (this.textWithGadgetsHandler) {
4945
- const textContent = result.outputs.filter(
4946
- (output) => output.type === "text"
4947
- ).map((output) => output.content).join("");
5363
+ const textContent = textOutputs.join("");
4948
5364
  if (textContent.trim()) {
4949
5365
  const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
4950
5366
  this.conversation.addGadgetCallResult(
@@ -4954,7 +5370,7 @@ var init_agent = __esm({
4954
5370
  );
4955
5371
  }
4956
5372
  }
4957
- for (const output of result.outputs) {
5373
+ for (const output of gadgetResults) {
4958
5374
  if (output.type === "gadget_result") {
4959
5375
  const gadgetResult = output.result;
4960
5376
  this.conversation.addGadgetCallResult(
@@ -5191,6 +5607,8 @@ var init_builder = __esm({
5191
5607
  signal;
5192
5608
  trailingMessage;
5193
5609
  subagentConfig;
5610
+ subagentEventCallback;
5611
+ parentContext;
5194
5612
  constructor(client) {
5195
5613
  this.client = client;
5196
5614
  }
@@ -5687,6 +6105,74 @@ var init_builder = __esm({
5687
6105
  this.subagentConfig = config;
5688
6106
  return this;
5689
6107
  }
6108
+ /**
6109
+ * Set the callback for subagent events.
6110
+ *
6111
+ * Subagent gadgets (like BrowseWeb) can use ExecutionContext.onSubagentEvent
6112
+ * to report their internal LLM calls and gadget executions in real-time.
6113
+ * This callback receives those events, enabling hierarchical progress display.
6114
+ *
6115
+ * @param callback - Function to handle subagent events
6116
+ * @returns This builder for chaining
6117
+ *
6118
+ * @example
6119
+ * ```typescript
6120
+ * .withSubagentEventCallback((event) => {
6121
+ * if (event.type === "llm_call_start") {
6122
+ * console.log(` Subagent LLM #${event.event.iteration} starting...`);
6123
+ * } else if (event.type === "gadget_call") {
6124
+ * console.log(` ⏵ ${event.event.call.gadgetName}...`);
6125
+ * }
6126
+ * })
6127
+ * ```
6128
+ */
6129
+ withSubagentEventCallback(callback) {
6130
+ this.subagentEventCallback = callback;
6131
+ return this;
6132
+ }
6133
+ /**
6134
+ * Enable automatic subagent event forwarding to parent agent.
6135
+ *
6136
+ * When building a subagent inside a gadget, call this method to automatically
6137
+ * forward all LLM calls and gadget events to the parent agent. This enables
6138
+ * hierarchical progress display without any manual event handling.
6139
+ *
6140
+ * The method extracts `invocationId` and `onSubagentEvent` from the execution
6141
+ * context and sets up automatic forwarding via hooks and event wrapping.
6142
+ *
6143
+ * @param ctx - ExecutionContext passed to the gadget's execute() method
6144
+ * @param depth - Nesting depth (default: 1 for direct child)
6145
+ * @returns This builder for chaining
6146
+ *
6147
+ * @example
6148
+ * ```typescript
6149
+ * // In a subagent gadget like BrowseWeb - ONE LINE enables auto-forwarding:
6150
+ * execute: async (params, ctx) => {
6151
+ * const agent = new AgentBuilder(client)
6152
+ * .withModel(model)
6153
+ * .withGadgets(Navigate, Click, Screenshot)
6154
+ * .withParentContext(ctx) // <-- This is all you need!
6155
+ * .ask(params.task);
6156
+ *
6157
+ * for await (const event of agent.run()) {
6158
+ * // Events automatically forwarded - just process normally
6159
+ * if (event.type === "text") {
6160
+ * result = event.content;
6161
+ * }
6162
+ * }
6163
+ * }
6164
+ * ```
6165
+ */
6166
+ withParentContext(ctx, depth = 1) {
6167
+ if (ctx.onSubagentEvent && ctx.invocationId) {
6168
+ this.parentContext = {
6169
+ invocationId: ctx.invocationId,
6170
+ onSubagentEvent: ctx.onSubagentEvent,
6171
+ depth
6172
+ };
6173
+ }
6174
+ return this;
6175
+ }
5690
6176
  /**
5691
6177
  * Add an ephemeral trailing message that appears at the end of each LLM request.
5692
6178
  *
@@ -5754,14 +6240,92 @@ ${endPrefix}`
5754
6240
  return this;
5755
6241
  }
5756
6242
  /**
5757
- * Compose the final hooks, including trailing message if configured.
6243
+ * Compose the final hooks, including:
6244
+ * - Trailing message injection (if configured)
6245
+ * - Subagent event forwarding for LLM calls (if parentContext is set)
5758
6246
  */
5759
6247
  composeHooks() {
6248
+ let hooks = this.hooks;
6249
+ if (this.parentContext) {
6250
+ const { invocationId, onSubagentEvent, depth } = this.parentContext;
6251
+ const existingOnLLMCallStart = hooks?.observers?.onLLMCallStart;
6252
+ const existingOnLLMCallComplete = hooks?.observers?.onLLMCallComplete;
6253
+ const existingOnGadgetExecutionStart = hooks?.observers?.onGadgetExecutionStart;
6254
+ const existingOnGadgetExecutionComplete = hooks?.observers?.onGadgetExecutionComplete;
6255
+ hooks = {
6256
+ ...hooks,
6257
+ observers: {
6258
+ ...hooks?.observers,
6259
+ onLLMCallStart: async (context) => {
6260
+ onSubagentEvent({
6261
+ type: "llm_call_start",
6262
+ gadgetInvocationId: invocationId,
6263
+ depth,
6264
+ event: {
6265
+ iteration: context.iteration,
6266
+ model: context.options.model
6267
+ }
6268
+ });
6269
+ if (existingOnLLMCallStart) {
6270
+ await existingOnLLMCallStart(context);
6271
+ }
6272
+ },
6273
+ onLLMCallComplete: async (context) => {
6274
+ onSubagentEvent({
6275
+ type: "llm_call_end",
6276
+ gadgetInvocationId: invocationId,
6277
+ depth,
6278
+ event: {
6279
+ iteration: context.iteration,
6280
+ model: context.options.model,
6281
+ outputTokens: context.usage?.outputTokens,
6282
+ finishReason: context.finishReason
6283
+ }
6284
+ });
6285
+ if (existingOnLLMCallComplete) {
6286
+ await existingOnLLMCallComplete(context);
6287
+ }
6288
+ },
6289
+ onGadgetExecutionStart: async (context) => {
6290
+ onSubagentEvent({
6291
+ type: "gadget_call",
6292
+ gadgetInvocationId: invocationId,
6293
+ depth,
6294
+ event: {
6295
+ call: {
6296
+ invocationId: context.invocationId,
6297
+ gadgetName: context.gadgetName,
6298
+ parameters: context.parameters
6299
+ }
6300
+ }
6301
+ });
6302
+ if (existingOnGadgetExecutionStart) {
6303
+ await existingOnGadgetExecutionStart(context);
6304
+ }
6305
+ },
6306
+ onGadgetExecutionComplete: async (context) => {
6307
+ onSubagentEvent({
6308
+ type: "gadget_result",
6309
+ gadgetInvocationId: invocationId,
6310
+ depth,
6311
+ event: {
6312
+ result: {
6313
+ invocationId: context.invocationId
6314
+ }
6315
+ }
6316
+ });
6317
+ if (existingOnGadgetExecutionComplete) {
6318
+ await existingOnGadgetExecutionComplete(context);
6319
+ }
6320
+ }
6321
+ }
6322
+ };
6323
+ }
5760
6324
  if (!this.trailingMessage) {
5761
- return this.hooks;
6325
+ return hooks;
5762
6326
  }
5763
6327
  const trailingMsg = this.trailingMessage;
5764
- const existingBeforeLLMCall = this.hooks?.controllers?.beforeLLMCall;
6328
+ const existingBeforeLLMCall = hooks?.controllers?.beforeLLMCall;
5765
6329
  const trailingMessageController = async (ctx) => {
5766
6330
  const result = existingBeforeLLMCall ? await existingBeforeLLMCall(ctx) : { action: "proceed" };
5767
6331
  if (result.action === "skip") {
@@ -5776,9 +6340,9 @@ ${endPrefix}`
5776
6340
  };
5777
6341
  };
5778
6342
  return {
5779
- ...this.hooks,
6343
+ ...hooks,
5780
6344
  controllers: {
5781
- ...this.hooks?.controllers,
6345
+ ...hooks?.controllers,
5782
6346
  beforeLLMCall: trailingMessageController
5783
6347
  }
5784
6348
  };
@@ -5839,6 +6403,19 @@ ${endPrefix}`
5839
6403
  this.client = new LLMistClass();
5840
6404
  }
5841
6405
  const registry = GadgetRegistry.from(this.gadgets);
6406
+ let onSubagentEvent = this.subagentEventCallback;
6407
+ if (this.parentContext) {
6408
+ const { invocationId, onSubagentEvent: parentCallback, depth } = this.parentContext;
6409
+ const existingCallback = this.subagentEventCallback;
6410
+ onSubagentEvent = (event) => {
6411
+ parentCallback({
6412
+ ...event,
6413
+ gadgetInvocationId: invocationId,
6414
+ depth: event.depth + depth
6415
+ });
6416
+ existingCallback?.(event);
6417
+ };
6418
+ }
5842
6419
  return {
5843
6420
  client: this.client,
5844
6421
  model: this.model ?? "openai:gpt-5-nano",
@@ -5864,7 +6441,8 @@ ${endPrefix}`
5864
6441
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
5865
6442
  compactionConfig: this.compactionConfig,
5866
6443
  signal: this.signal,
5867
- subagentConfig: this.subagentConfig
6444
+ subagentConfig: this.subagentConfig,
6445
+ onSubagentEvent
5868
6446
  };
5869
6447
  }
5870
6448
  ask(userPrompt) {
@@ -6021,6 +6599,19 @@ ${endPrefix}`
6021
6599
  this.client = new LLMistClass();
6022
6600
  }
6023
6601
  const registry = GadgetRegistry.from(this.gadgets);
6602
+ let onSubagentEvent = this.subagentEventCallback;
6603
+ if (this.parentContext) {
6604
+ const { invocationId, onSubagentEvent: parentCallback, depth } = this.parentContext;
6605
+ const existingCallback = this.subagentEventCallback;
6606
+ onSubagentEvent = (event) => {
6607
+ parentCallback({
6608
+ ...event,
6609
+ gadgetInvocationId: invocationId,
6610
+ depth: event.depth + depth
6611
+ });
6612
+ existingCallback?.(event);
6613
+ };
6614
+ }
6024
6615
  const options = {
6025
6616
  client: this.client,
6026
6617
  model: this.model ?? "openai:gpt-5-nano",
@@ -6046,7 +6637,8 @@ ${endPrefix}`
6046
6637
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
6047
6638
  compactionConfig: this.compactionConfig,
6048
6639
  signal: this.signal,
6049
- subagentConfig: this.subagentConfig
6640
+ subagentConfig: this.subagentConfig,
6641
+ onSubagentEvent
6050
6642
  };
6051
6643
  return new Agent(AGENT_INTERNAL_KEY, options);
6052
6644
  }
@@ -11106,4 +11698,4 @@ export {
11106
11698
  createEmptyStream,
11107
11699
  createErrorStream
11108
11700
  };
11109
- //# sourceMappingURL=chunk-67MMSOAT.js.map
11701
+ //# sourceMappingURL=chunk-RHR2M6T6.js.map