llmist 11.0.0 → 11.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -756,6 +756,20 @@ var init_execution_tree = __esm({
756
756
  });
757
757
  return node;
758
758
  }
759
+ /**
760
+ * Update a gadget's parameters (e.g., after interceptor modifies them).
761
+ * This is called after the gadget is added to the tree but before execution.
762
+ */
763
+ updateGadgetParameters(invocationId, parameters) {
764
+ const nodeId = this.invocationIdToNodeId.get(invocationId);
765
+ if (!nodeId) {
766
+ return;
767
+ }
768
+ const node = this.nodes.get(nodeId);
769
+ if (node?.type === "gadget") {
770
+ node.parameters = parameters;
771
+ }
772
+ }
759
773
  /**
760
774
  * Mark a gadget as started (running).
761
775
  */
@@ -8685,6 +8699,9 @@ var init_stream_processor = __esm({
8685
8699
  parameters = this.hooks.interceptors.interceptGadgetParameters(parameters, context);
8686
8700
  }
8687
8701
  call.parameters = parameters;
8702
+ if (this.tree) {
8703
+ this.tree.updateGadgetParameters(call.invocationId, parameters);
8704
+ }
8688
8705
  let shouldSkip = false;
8689
8706
  let syntheticResult;
8690
8707
  if (this.hooks.controllers?.beforeGadgetExecution) {
@@ -8705,20 +8722,6 @@ var init_stream_processor = __esm({
8705
8722
  });
8706
8723
  }
8707
8724
  }
8708
- const startObservers = [];
8709
- if (this.hooks.observers?.onGadgetExecutionStart) {
8710
- startObservers.push(async () => {
8711
- const context = {
8712
- iteration: this.iteration,
8713
- gadgetName: call.gadgetName,
8714
- invocationId: call.invocationId,
8715
- parameters,
8716
- logger: this.logger
8717
- };
8718
- await this.hooks.observers?.onGadgetExecutionStart?.(context);
8719
- });
8720
- }
8721
- await this.runObserversInParallel(startObservers);
8722
8725
  if (this.tree) {
8723
8726
  const gadgetNode = this.tree.getNodeByInvocationId(call.invocationId);
8724
8727
  if (gadgetNode) {
@@ -8737,7 +8740,6 @@ var init_stream_processor = __esm({
8737
8740
  } else {
8738
8741
  result = await this.executor.execute(call);
8739
8742
  }
8740
- const originalResult = result.result;
8741
8743
  if (result.result && this.hooks.interceptors?.interceptGadgetResult) {
8742
8744
  const context = {
8743
8745
  iteration: this.iteration,
@@ -8774,26 +8776,6 @@ var init_stream_processor = __esm({
8774
8776
  };
8775
8777
  }
8776
8778
  }
8777
- const completeObservers = [];
8778
- if (this.hooks.observers?.onGadgetExecutionComplete) {
8779
- completeObservers.push(async () => {
8780
- const context = {
8781
- iteration: this.iteration,
8782
- gadgetName: result.gadgetName,
8783
- invocationId: result.invocationId,
8784
- parameters,
8785
- originalResult,
8786
- finalResult: result.result,
8787
- error: result.error,
8788
- executionTimeMs: result.executionTimeMs,
8789
- breaksLoop: result.breaksLoop,
8790
- cost: result.cost,
8791
- logger: this.logger
8792
- };
8793
- await this.hooks.observers?.onGadgetExecutionComplete?.(context);
8794
- });
8795
- }
8796
- await this.runObserversInParallel(completeObservers);
8797
8779
  if (this.tree) {
8798
8780
  const gadgetNode = this.tree.getNodeByInvocationId(result.invocationId);
8799
8781
  if (gadgetNode) {
@@ -8938,18 +8920,6 @@ var init_stream_processor = __esm({
8938
8920
  failedDependencyError: depError
8939
8921
  };
8940
8922
  events.push(skipEvent);
8941
- if (this.hooks.observers?.onGadgetSkipped) {
8942
- const observeContext = {
8943
- iteration: this.iteration,
8944
- gadgetName: call.gadgetName,
8945
- invocationId: call.invocationId,
8946
- parameters: call.parameters ?? {},
8947
- failedDependency: failedDep,
8948
- failedDependencyError: depError,
8949
- logger: this.logger
8950
- };
8951
- await this.safeObserve(() => this.hooks.observers?.onGadgetSkipped?.(observeContext));
8952
- }
8953
8923
  this.logger.info("Gadget skipped due to failed dependency", {
8954
8924
  gadgetName: call.gadgetName,
8955
8925
  invocationId: call.invocationId,
@@ -9127,6 +9097,160 @@ var init_stream_processor = __esm({
9127
9097
  }
9128
9098
  });
9129
9099
 
9100
+ // src/agent/tree-hook-bridge.ts
9101
+ function findParentGadgetInvocationId(tree, nodeId) {
9102
+ let currentId = nodeId;
9103
+ while (currentId) {
9104
+ const node = tree.getNode(currentId);
9105
+ if (!node) break;
9106
+ currentId = node.parentId;
9107
+ if (!currentId) break;
9108
+ const parentNode = tree.getNode(currentId);
9109
+ if (parentNode?.type === "gadget") {
9110
+ return parentNode.invocationId;
9111
+ }
9112
+ }
9113
+ return void 0;
9114
+ }
9115
+ function getIterationFromTree(tree, nodeId) {
9116
+ let currentId = nodeId;
9117
+ while (currentId) {
9118
+ const node = tree.getNode(currentId);
9119
+ if (!node) break;
9120
+ if (node.type === "llm_call") {
9121
+ return node.iteration;
9122
+ }
9123
+ currentId = node.parentId;
9124
+ }
9125
+ return 0;
9126
+ }
9127
+ function buildSubagentContext(tree, event) {
9128
+ const parentGadgetInvocationId = findParentGadgetInvocationId(tree, event.nodeId);
9129
+ if (!parentGadgetInvocationId) {
9130
+ return void 0;
9131
+ }
9132
+ return {
9133
+ parentGadgetInvocationId,
9134
+ depth: event.depth
9135
+ };
9136
+ }
9137
+ function getSubagentContextForNode(tree, nodeId) {
9138
+ const node = tree.getNode(nodeId);
9139
+ if (!node) return void 0;
9140
+ const parentGadgetInvocationId = findParentGadgetInvocationId(tree, nodeId);
9141
+ if (!parentGadgetInvocationId) {
9142
+ return void 0;
9143
+ }
9144
+ return {
9145
+ parentGadgetInvocationId,
9146
+ depth: node.depth
9147
+ };
9148
+ }
9149
+ async function safeObserve(fn, logger, eventType) {
9150
+ try {
9151
+ await fn();
9152
+ } catch (error) {
9153
+ logger.warn(`Observer error in ${eventType}:`, error);
9154
+ }
9155
+ }
9156
+ function bridgeTreeToHooks(tree, hooks, logger) {
9157
+ return tree.onAll((event) => {
9158
+ const subagentContext = buildSubagentContext(tree, event);
9159
+ switch (event.type) {
9160
+ case "gadget_start": {
9161
+ if (hooks.observers?.onGadgetExecutionStart) {
9162
+ const gadgetEvent = event;
9163
+ const gadgetNode = tree.getNodeByInvocationId(gadgetEvent.invocationId);
9164
+ const context = {
9165
+ iteration: getIterationFromTree(tree, event.nodeId),
9166
+ gadgetName: gadgetEvent.name,
9167
+ invocationId: gadgetEvent.invocationId,
9168
+ parameters: gadgetNode?.parameters ?? {},
9169
+ logger,
9170
+ subagentContext
9171
+ };
9172
+ safeObserve(
9173
+ () => hooks.observers.onGadgetExecutionStart(context),
9174
+ logger,
9175
+ "onGadgetExecutionStart"
9176
+ );
9177
+ }
9178
+ break;
9179
+ }
9180
+ case "gadget_complete": {
9181
+ if (hooks.observers?.onGadgetExecutionComplete) {
9182
+ const gadgetEvent = event;
9183
+ const gadgetNode = tree.getNodeByInvocationId(gadgetEvent.invocationId);
9184
+ const context = {
9185
+ iteration: getIterationFromTree(tree, event.nodeId),
9186
+ gadgetName: gadgetEvent.name,
9187
+ invocationId: gadgetEvent.invocationId,
9188
+ parameters: gadgetNode?.parameters ?? {},
9189
+ finalResult: gadgetEvent.result,
9190
+ executionTimeMs: gadgetEvent.executionTimeMs,
9191
+ cost: gadgetEvent.cost,
9192
+ logger,
9193
+ subagentContext
9194
+ };
9195
+ safeObserve(
9196
+ () => hooks.observers.onGadgetExecutionComplete(context),
9197
+ logger,
9198
+ "onGadgetExecutionComplete"
9199
+ );
9200
+ }
9201
+ break;
9202
+ }
9203
+ case "gadget_error": {
9204
+ if (hooks.observers?.onGadgetExecutionComplete) {
9205
+ const gadgetNode = tree.getNodeByInvocationId(event.invocationId);
9206
+ const context = {
9207
+ iteration: getIterationFromTree(tree, event.nodeId),
9208
+ gadgetName: event.name,
9209
+ invocationId: event.invocationId,
9210
+ parameters: gadgetNode?.parameters ?? {},
9211
+ error: event.error,
9212
+ executionTimeMs: event.executionTimeMs,
9213
+ logger,
9214
+ subagentContext
9215
+ };
9216
+ safeObserve(
9217
+ () => hooks.observers.onGadgetExecutionComplete(context),
9218
+ logger,
9219
+ "onGadgetExecutionComplete"
9220
+ );
9221
+ }
9222
+ break;
9223
+ }
9224
+ case "gadget_skipped": {
9225
+ if (hooks.observers?.onGadgetSkipped) {
9226
+ const gadgetNode = tree.getNodeByInvocationId(event.invocationId);
9227
+ const context = {
9228
+ iteration: getIterationFromTree(tree, event.nodeId),
9229
+ gadgetName: event.name,
9230
+ invocationId: event.invocationId,
9231
+ parameters: gadgetNode?.parameters ?? {},
9232
+ failedDependency: event.failedDependency ?? "unknown",
9233
+ failedDependencyError: event.failedDependencyError ?? event.error,
9234
+ logger,
9235
+ subagentContext
9236
+ };
9237
+ safeObserve(
9238
+ () => hooks.observers.onGadgetSkipped(context),
9239
+ logger,
9240
+ "onGadgetSkipped"
9241
+ );
9242
+ }
9243
+ break;
9244
+ }
9245
+ }
9246
+ });
9247
+ }
9248
+ var init_tree_hook_bridge = __esm({
9249
+ "src/agent/tree-hook-bridge.ts"() {
9250
+ "use strict";
9251
+ }
9252
+ });
9253
+
9130
9254
  // src/agent/agent.ts
9131
9255
  var import_p_retry, Agent;
9132
9256
  var init_agent = __esm({
@@ -9148,6 +9272,7 @@ var init_agent = __esm({
9148
9272
  init_gadget_output_store();
9149
9273
  init_hook_validators();
9150
9274
  init_stream_processor();
9275
+ init_tree_hook_bridge();
9151
9276
  Agent = class {
9152
9277
  client;
9153
9278
  model;
@@ -9454,6 +9579,14 @@ var init_agent = __esm({
9454
9579
  * Run the agent loop.
9455
9580
  * Clean, simple orchestration - all complexity is in StreamProcessor.
9456
9581
  *
9582
+ * ## Event Architecture
9583
+ *
9584
+ * ExecutionTree is the single source of truth for all agent events.
9585
+ * Gadget observer hooks (`onGadgetExecutionStart`, `onGadgetExecutionComplete`,
9586
+ * `onGadgetSkipped`) are derived from tree events via `tree-hook-bridge.ts`.
9587
+ * This ensures consistent `subagentContext` for nested agents - both the TUI
9588
+ * and user hook observers receive identical event context.
9589
+ *
9457
9590
  * @throws {Error} If no user prompt was provided (when using build() without ask())
9458
9591
  */
9459
9592
  async *run() {
@@ -9462,173 +9595,179 @@ var init_agent = __esm({
9462
9595
  "No user prompt provided. Use .ask(prompt) instead of .build(), or call agent.run() after providing a prompt."
9463
9596
  );
9464
9597
  }
9598
+ const unsubscribeBridge = bridgeTreeToHooks(this.tree, this.hooks, this.logger);
9465
9599
  let currentIteration = 0;
9466
9600
  this.logger.info("Starting agent loop", {
9467
9601
  model: this.model,
9468
9602
  maxIterations: this.maxIterations
9469
9603
  });
9470
- while (currentIteration < this.maxIterations) {
9471
- if (await this.checkAbortAndNotify(currentIteration)) {
9472
- return;
9473
- }
9474
- while (this.pendingUserMessages.length > 0) {
9475
- const msg = this.pendingUserMessages.shift();
9476
- this.conversation.addUserMessage(msg);
9477
- this.logger.info("Injected user message into conversation", {
9478
- iteration: currentIteration,
9479
- messageLength: msg.length
9480
- });
9481
- }
9482
- this.logger.debug("Starting iteration", { iteration: currentIteration });
9483
- try {
9484
- const compactionEvent = await this.checkAndPerformCompaction(currentIteration);
9485
- if (compactionEvent) {
9486
- yield compactionEvent;
9604
+ try {
9605
+ while (currentIteration < this.maxIterations) {
9606
+ if (await this.checkAbortAndNotify(currentIteration)) {
9607
+ return;
9487
9608
  }
9488
- const prepared = await this.prepareLLMCall(currentIteration);
9489
- const llmOptions = prepared.options;
9490
- if (prepared.skipWithSynthetic !== void 0) {
9491
- this.conversation.addAssistantMessage(prepared.skipWithSynthetic);
9492
- yield { type: "text", content: prepared.skipWithSynthetic };
9493
- break;
9609
+ while (this.pendingUserMessages.length > 0) {
9610
+ const msg = this.pendingUserMessages.shift();
9611
+ this.conversation.addUserMessage(msg);
9612
+ this.logger.info("Injected user message into conversation", {
9613
+ iteration: currentIteration,
9614
+ messageLength: msg.length
9615
+ });
9494
9616
  }
9495
- this.logger.info("Calling LLM", { model: this.model });
9496
- this.logger.silly("LLM request details", {
9497
- model: llmOptions.model,
9498
- temperature: llmOptions.temperature,
9499
- maxTokens: llmOptions.maxTokens,
9500
- messageCount: llmOptions.messages.length,
9501
- messages: llmOptions.messages
9502
- });
9503
- const llmNode = this.tree.addLLMCall({
9504
- iteration: currentIteration,
9505
- model: llmOptions.model,
9506
- parentId: this.parentNodeId,
9507
- request: llmOptions.messages
9508
- });
9509
- const currentLLMNodeId = llmNode.id;
9510
- const stream2 = await this.createStreamWithRetry(llmOptions, currentIteration);
9511
- const processor = new StreamProcessor({
9512
- iteration: currentIteration,
9513
- registry: this.registry,
9514
- gadgetStartPrefix: this.gadgetStartPrefix,
9515
- gadgetEndPrefix: this.gadgetEndPrefix,
9516
- gadgetArgPrefix: this.gadgetArgPrefix,
9517
- hooks: this.hooks,
9518
- logger: this.logger.getSubLogger({ name: "stream-processor" }),
9519
- requestHumanInput: this.requestHumanInput,
9520
- defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
9521
- client: this.client,
9522
- mediaStore: this.mediaStore,
9523
- agentConfig: this.agentContextConfig,
9524
- subagentConfig: this.subagentConfig,
9525
- // Tree context for execution tracking
9526
- tree: this.tree,
9527
- parentNodeId: currentLLMNodeId,
9528
- // Gadgets are children of this LLM call
9529
- baseDepth: this.baseDepth,
9530
- // Cross-iteration dependency tracking
9531
- priorCompletedInvocations: this.completedInvocationIds,
9532
- priorFailedInvocations: this.failedInvocationIds
9533
- });
9534
- let streamMetadata = null;
9535
- let gadgetCallCount = 0;
9536
- const textOutputs = [];
9537
- const gadgetResults = [];
9538
- for await (const event of processor.process(stream2)) {
9539
- if (event.type === "stream_complete") {
9540
- streamMetadata = event;
9541
- continue;
9617
+ this.logger.debug("Starting iteration", { iteration: currentIteration });
9618
+ let currentLLMNodeId;
9619
+ let llmOptions;
9620
+ try {
9621
+ const compactionEvent = await this.checkAndPerformCompaction(currentIteration);
9622
+ if (compactionEvent) {
9623
+ yield compactionEvent;
9542
9624
  }
9543
- if (event.type === "text") {
9544
- textOutputs.push(event.content);
9545
- } else if (event.type === "gadget_result") {
9546
- gadgetCallCount++;
9547
- gadgetResults.push(event);
9625
+ const prepared = await this.prepareLLMCall(currentIteration);
9626
+ llmOptions = prepared.options;
9627
+ currentLLMNodeId = prepared.llmNodeId;
9628
+ if (prepared.skipWithSynthetic !== void 0) {
9629
+ this.conversation.addAssistantMessage(prepared.skipWithSynthetic);
9630
+ yield { type: "text", content: prepared.skipWithSynthetic };
9631
+ break;
9548
9632
  }
9549
- yield event;
9550
- }
9551
- if (!streamMetadata) {
9552
- throw new Error("Stream processing completed without metadata event");
9553
- }
9554
- for (const id of processor.getCompletedInvocationIds()) {
9555
- this.completedInvocationIds.add(id);
9556
- }
9557
- for (const id of processor.getFailedInvocationIds()) {
9558
- this.failedInvocationIds.add(id);
9559
- }
9560
- const result = streamMetadata;
9561
- this.logger.info("LLM response completed", {
9562
- finishReason: result.finishReason,
9563
- usage: result.usage,
9564
- didExecuteGadgets: result.didExecuteGadgets
9565
- });
9566
- this.logger.silly("LLM response details", {
9567
- rawResponse: result.rawResponse
9568
- });
9569
- await this.safeObserve(async () => {
9570
- if (this.hooks.observers?.onLLMCallComplete) {
9571
- const context = {
9572
- iteration: currentIteration,
9573
- options: llmOptions,
9574
- finishReason: result.finishReason,
9575
- usage: result.usage,
9576
- rawResponse: result.rawResponse,
9577
- finalMessage: result.finalMessage,
9578
- logger: this.logger
9579
- };
9580
- await this.hooks.observers.onLLMCallComplete(context);
9633
+ this.logger.info("Calling LLM", { model: this.model });
9634
+ this.logger.silly("LLM request details", {
9635
+ model: llmOptions.model,
9636
+ temperature: llmOptions.temperature,
9637
+ maxTokens: llmOptions.maxTokens,
9638
+ messageCount: llmOptions.messages.length,
9639
+ messages: llmOptions.messages
9640
+ });
9641
+ const stream2 = await this.createStreamWithRetry(llmOptions, currentIteration);
9642
+ const processor = new StreamProcessor({
9643
+ iteration: currentIteration,
9644
+ registry: this.registry,
9645
+ gadgetStartPrefix: this.gadgetStartPrefix,
9646
+ gadgetEndPrefix: this.gadgetEndPrefix,
9647
+ gadgetArgPrefix: this.gadgetArgPrefix,
9648
+ hooks: this.hooks,
9649
+ logger: this.logger.getSubLogger({ name: "stream-processor" }),
9650
+ requestHumanInput: this.requestHumanInput,
9651
+ defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
9652
+ client: this.client,
9653
+ mediaStore: this.mediaStore,
9654
+ agentConfig: this.agentContextConfig,
9655
+ subagentConfig: this.subagentConfig,
9656
+ // Tree context for execution tracking
9657
+ tree: this.tree,
9658
+ parentNodeId: currentLLMNodeId,
9659
+ // Gadgets are children of this LLM call
9660
+ baseDepth: this.baseDepth,
9661
+ // Cross-iteration dependency tracking
9662
+ priorCompletedInvocations: this.completedInvocationIds,
9663
+ priorFailedInvocations: this.failedInvocationIds
9664
+ });
9665
+ let streamMetadata = null;
9666
+ let gadgetCallCount = 0;
9667
+ const textOutputs = [];
9668
+ const gadgetResults = [];
9669
+ for await (const event of processor.process(stream2)) {
9670
+ if (event.type === "stream_complete") {
9671
+ streamMetadata = event;
9672
+ continue;
9673
+ }
9674
+ if (event.type === "text") {
9675
+ textOutputs.push(event.content);
9676
+ } else if (event.type === "gadget_result") {
9677
+ gadgetCallCount++;
9678
+ gadgetResults.push(event);
9679
+ }
9680
+ yield event;
9581
9681
  }
9582
- });
9583
- this.completeLLMCallInTree(currentLLMNodeId, result);
9584
- const finalMessage = await this.processAfterLLMCallController(
9585
- currentIteration,
9586
- llmOptions,
9587
- result,
9588
- gadgetCallCount
9589
- );
9590
- const shouldBreakFromTextOnly = await this.updateConversationWithResults(
9591
- result.didExecuteGadgets,
9592
- textOutputs,
9593
- gadgetResults,
9594
- finalMessage
9595
- );
9596
- if (shouldBreakFromTextOnly) {
9597
- break;
9598
- }
9599
- if (result.shouldBreakLoop) {
9600
- this.logger.info("Loop terminated by gadget or processor");
9601
- break;
9602
- }
9603
- } catch (error) {
9604
- const errorHandled = await this.handleLLMError(error, currentIteration);
9605
- await this.safeObserve(async () => {
9606
- if (this.hooks.observers?.onLLMCallError) {
9607
- const context = {
9608
- iteration: currentIteration,
9609
- options: {
9682
+ if (!streamMetadata) {
9683
+ throw new Error("Stream processing completed without metadata event");
9684
+ }
9685
+ for (const id of processor.getCompletedInvocationIds()) {
9686
+ this.completedInvocationIds.add(id);
9687
+ }
9688
+ for (const id of processor.getFailedInvocationIds()) {
9689
+ this.failedInvocationIds.add(id);
9690
+ }
9691
+ const result = streamMetadata;
9692
+ this.logger.info("LLM response completed", {
9693
+ finishReason: result.finishReason,
9694
+ usage: result.usage,
9695
+ didExecuteGadgets: result.didExecuteGadgets
9696
+ });
9697
+ this.logger.silly("LLM response details", {
9698
+ rawResponse: result.rawResponse
9699
+ });
9700
+ await this.safeObserve(async () => {
9701
+ if (this.hooks.observers?.onLLMCallComplete) {
9702
+ const subagentContext = getSubagentContextForNode(this.tree, currentLLMNodeId);
9703
+ const context = {
9704
+ iteration: currentIteration,
9705
+ options: llmOptions,
9706
+ finishReason: result.finishReason,
9707
+ usage: result.usage,
9708
+ rawResponse: result.rawResponse,
9709
+ finalMessage: result.finalMessage,
9710
+ logger: this.logger,
9711
+ subagentContext
9712
+ };
9713
+ await this.hooks.observers.onLLMCallComplete(context);
9714
+ }
9715
+ });
9716
+ this.completeLLMCallInTree(currentLLMNodeId, result);
9717
+ const finalMessage = await this.processAfterLLMCallController(
9718
+ currentIteration,
9719
+ llmOptions,
9720
+ result,
9721
+ gadgetCallCount
9722
+ );
9723
+ const shouldBreakFromTextOnly = await this.updateConversationWithResults(
9724
+ result.didExecuteGadgets,
9725
+ textOutputs,
9726
+ gadgetResults,
9727
+ finalMessage
9728
+ );
9729
+ if (shouldBreakFromTextOnly) {
9730
+ break;
9731
+ }
9732
+ if (result.shouldBreakLoop) {
9733
+ this.logger.info("Loop terminated by gadget or processor");
9734
+ break;
9735
+ }
9736
+ } catch (error) {
9737
+ const errorHandled = await this.handleLLMError(error, currentIteration);
9738
+ await this.safeObserve(async () => {
9739
+ if (this.hooks.observers?.onLLMCallError) {
9740
+ const options = llmOptions ?? {
9610
9741
  model: this.model,
9611
9742
  messages: this.conversation.getMessages(),
9612
9743
  temperature: this.temperature,
9613
9744
  maxTokens: this.defaultMaxTokens
9614
- },
9615
- error,
9616
- recovered: errorHandled,
9617
- logger: this.logger
9618
- };
9619
- await this.hooks.observers.onLLMCallError(context);
9745
+ };
9746
+ const subagentContext = currentLLMNodeId ? getSubagentContextForNode(this.tree, currentLLMNodeId) : void 0;
9747
+ const context = {
9748
+ iteration: currentIteration,
9749
+ options,
9750
+ error,
9751
+ recovered: errorHandled,
9752
+ logger: this.logger,
9753
+ subagentContext
9754
+ };
9755
+ await this.hooks.observers.onLLMCallError(context);
9756
+ }
9757
+ });
9758
+ if (!errorHandled) {
9759
+ throw error;
9620
9760
  }
9621
- });
9622
- if (!errorHandled) {
9623
- throw error;
9624
9761
  }
9762
+ currentIteration++;
9625
9763
  }
9626
- currentIteration++;
9764
+ this.logger.info("Agent loop completed", {
9765
+ totalIterations: currentIteration,
9766
+ reason: currentIteration >= this.maxIterations ? "max_iterations" : "natural_completion"
9767
+ });
9768
+ } finally {
9769
+ unsubscribeBridge();
9627
9770
  }
9628
- this.logger.info("Agent loop completed", {
9629
- totalIterations: currentIteration,
9630
- reason: currentIteration >= this.maxIterations ? "max_iterations" : "natural_completion"
9631
- });
9632
9771
  }
9633
9772
  /**
9634
9773
  * Create LLM stream with retry logic.
@@ -9849,8 +9988,8 @@ var init_agent = __esm({
9849
9988
  return { type: "compaction", event: compactionEvent };
9850
9989
  }
9851
9990
  /**
9852
- * Prepare LLM call options and process beforeLLMCall controller.
9853
- * @returns options and optional skipWithSynthetic response if controller wants to skip
9991
+ * Prepare LLM call options, create tree node, and process beforeLLMCall controller.
9992
+ * @returns options, node ID, and optional skipWithSynthetic response if controller wants to skip
9854
9993
  */
9855
9994
  async prepareLLMCall(iteration) {
9856
9995
  let llmOptions = {
@@ -9860,12 +9999,20 @@ var init_agent = __esm({
9860
9999
  maxTokens: this.defaultMaxTokens,
9861
10000
  signal: this.signal
9862
10001
  };
10002
+ const llmNode = this.tree.addLLMCall({
10003
+ iteration,
10004
+ model: llmOptions.model,
10005
+ parentId: this.parentNodeId,
10006
+ request: llmOptions.messages
10007
+ });
9863
10008
  await this.safeObserve(async () => {
9864
10009
  if (this.hooks.observers?.onLLMCallStart) {
10010
+ const subagentContext = getSubagentContextForNode(this.tree, llmNode.id);
9865
10011
  const context = {
9866
10012
  iteration,
9867
10013
  options: llmOptions,
9868
- logger: this.logger
10014
+ logger: this.logger,
10015
+ subagentContext
9869
10016
  };
9870
10017
  await this.hooks.observers.onLLMCallStart(context);
9871
10018
  }
@@ -9881,23 +10028,25 @@ var init_agent = __esm({
9881
10028
  validateBeforeLLMCallAction(action);
9882
10029
  if (action.action === "skip") {
9883
10030
  this.logger.info("Controller skipped LLM call, using synthetic response");
9884
- return { options: llmOptions, skipWithSynthetic: action.syntheticResponse };
10031
+ return { options: llmOptions, llmNodeId: llmNode.id, skipWithSynthetic: action.syntheticResponse };
9885
10032
  } else if (action.action === "proceed" && action.modifiedOptions) {
9886
10033
  llmOptions = { ...llmOptions, ...action.modifiedOptions };
9887
10034
  }
9888
10035
  }
9889
10036
  await this.safeObserve(async () => {
9890
10037
  if (this.hooks.observers?.onLLMCallReady) {
10038
+ const subagentContext = getSubagentContextForNode(this.tree, llmNode.id);
9891
10039
  const context = {
9892
10040
  iteration,
9893
10041
  maxIterations: this.maxIterations,
9894
10042
  options: llmOptions,
9895
- logger: this.logger
10043
+ logger: this.logger,
10044
+ subagentContext
9896
10045
  };
9897
10046
  await this.hooks.observers.onLLMCallReady(context);
9898
10047
  }
9899
10048
  });
9900
- return { options: llmOptions };
10049
+ return { options: llmOptions, llmNodeId: llmNode.id };
9901
10050
  }
9902
10051
  /**
9903
10052
  * Calculate cost and complete LLM call in execution tree.