llmist 6.2.0 → 7.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.
@@ -44,6 +44,9 @@ function parseEnvBoolean(value) {
44
44
  if (normalized === "false" || normalized === "0") return false;
45
45
  return void 0;
46
46
  }
47
+ function stripAnsi(str) {
48
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
49
+ }
47
50
  function createLogger(options = {}) {
48
51
  const envMinLevel = parseLogLevel(process.env.LLMIST_LOG_LEVEL);
49
52
  const envLogFile = process.env.LLMIST_LOG_FILE?.trim() ?? "";
@@ -52,36 +55,62 @@ function createLogger(options = {}) {
52
55
  const defaultType = options.type ?? "pretty";
53
56
  const name = options.name ?? "llmist";
54
57
  const logReset = options.logReset ?? envLogReset ?? false;
55
- let logFileStream;
56
- let finalType = defaultType;
57
- if (envLogFile) {
58
+ if (envLogFile && (!logFileInitialized || sharedLogFilePath !== envLogFile)) {
58
59
  try {
60
+ if (sharedLogFileStream) {
61
+ sharedLogFileStream.end();
62
+ sharedLogFileStream = void 0;
63
+ }
59
64
  mkdirSync(dirname(envLogFile), { recursive: true });
60
65
  const flags = logReset ? "w" : "a";
61
- logFileStream = createWriteStream(envLogFile, { flags });
62
- finalType = "hidden";
66
+ sharedLogFileStream = createWriteStream(envLogFile, { flags });
67
+ sharedLogFilePath = envLogFile;
68
+ logFileInitialized = true;
69
+ writeErrorCount = 0;
70
+ writeErrorReported = false;
71
+ sharedLogFileStream.on("error", (error) => {
72
+ writeErrorCount++;
73
+ if (!writeErrorReported) {
74
+ console.error(`[llmist] Log file write error: ${error.message}`);
75
+ writeErrorReported = true;
76
+ }
77
+ if (writeErrorCount >= MAX_WRITE_ERRORS_BEFORE_DISABLE) {
78
+ console.error(
79
+ `[llmist] Too many log file errors (${writeErrorCount}), disabling file logging`
80
+ );
81
+ sharedLogFileStream?.end();
82
+ sharedLogFileStream = void 0;
83
+ }
84
+ });
63
85
  } catch (error) {
64
86
  console.error("Failed to initialize LLMIST_LOG_FILE output:", error);
65
87
  }
66
88
  }
89
+ const useFileLogging = Boolean(sharedLogFileStream);
67
90
  const logger = new Logger({
68
91
  name,
69
92
  minLevel,
70
- type: finalType,
71
- // Optimize for production
72
- hideLogPositionForProduction: finalType !== "pretty",
73
- // Pretty output settings
74
- prettyLogTemplate: finalType === "pretty" ? "{{yyyy}}-{{mm}}-{{dd}} {{hh}}:{{MM}}:{{ss}}:{{ms}} {{logLevelName}} [{{name}}] " : void 0
93
+ type: useFileLogging ? "pretty" : defaultType,
94
+ // Hide log position for file logging and non-pretty types
95
+ hideLogPositionForProduction: useFileLogging || defaultType !== "pretty",
96
+ prettyLogTemplate: LOG_TEMPLATE,
97
+ // Use overwrite to redirect tslog's formatted output to file instead of console
98
+ overwrite: useFileLogging ? {
99
+ transportFormatted: (logMetaMarkup, logArgs, _logErrors) => {
100
+ if (!sharedLogFileStream) return;
101
+ const meta = stripAnsi(logMetaMarkup);
102
+ const args = logArgs.map(
103
+ (arg) => typeof arg === "string" ? stripAnsi(arg) : JSON.stringify(arg)
104
+ );
105
+ const line = `${meta}${args.join(" ")}
106
+ `;
107
+ sharedLogFileStream.write(line);
108
+ }
109
+ } : void 0
75
110
  });
76
- if (logFileStream) {
77
- logger.attachTransport((logObj) => {
78
- logFileStream?.write(`${JSON.stringify(logObj)}
79
- `);
80
- });
81
- }
82
111
  return logger;
83
112
  }
84
- var LEVEL_NAME_TO_ID, defaultLogger;
113
+ var LEVEL_NAME_TO_ID, sharedLogFilePath, sharedLogFileStream, logFileInitialized, writeErrorCount, writeErrorReported, MAX_WRITE_ERRORS_BEFORE_DISABLE, LOG_TEMPLATE, defaultLogger;
85
114
  var init_logger = __esm({
86
115
  "src/logging/logger.ts"() {
87
116
  "use strict";
@@ -94,6 +123,11 @@ var init_logger = __esm({
94
123
  error: 5,
95
124
  fatal: 6
96
125
  };
126
+ logFileInitialized = false;
127
+ writeErrorCount = 0;
128
+ writeErrorReported = false;
129
+ MAX_WRITE_ERRORS_BEFORE_DISABLE = 5;
130
+ LOG_TEMPLATE = "{{yyyy}}-{{mm}}-{{dd}} {{hh}}:{{MM}}:{{ss}}:{{ms}} {{logLevelName}} [{{name}}] ";
97
131
  defaultLogger = createLogger();
98
132
  }
99
133
  });
@@ -3859,9 +3893,8 @@ var init_cost_reporting_client = __esm({
3859
3893
  */
3860
3894
  reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens = 0, cacheCreationInputTokens = 0) {
3861
3895
  if (inputTokens === 0 && outputTokens === 0) return;
3862
- const modelName = model.includes(":") ? model.split(":")[1] : model;
3863
3896
  const estimate = this.client.modelRegistry.estimateCost(
3864
- modelName,
3897
+ model,
3865
3898
  inputTokens,
3866
3899
  outputTokens,
3867
3900
  cachedInputTokens,
@@ -4171,6 +4204,7 @@ var init_typed_gadget = __esm({
4171
4204
  });
4172
4205
 
4173
4206
  // src/gadgets/executor.ts
4207
+ import equal from "fast-deep-equal";
4174
4208
  import { z as z4 } from "zod";
4175
4209
  function getHostExportsInternal() {
4176
4210
  if (!cachedHostExports) {
@@ -4298,7 +4332,7 @@ var init_executor = __esm({
4298
4332
  try {
4299
4333
  const cleanedRaw = stripMarkdownFences(call.parametersRaw);
4300
4334
  const initialParse = parseBlockParams(cleanedRaw, { argPrefix: this.argPrefix });
4301
- const parametersWereModified = !this.deepEquals(rawParameters, initialParse);
4335
+ const parametersWereModified = !equal(rawParameters, initialParse);
4302
4336
  if (parametersWereModified) {
4303
4337
  this.logger.debug("Parameters modified by interceptor, skipping re-parse", {
4304
4338
  gadgetName: call.gadgetName
@@ -4375,7 +4409,9 @@ var init_executor = __esm({
4375
4409
  nodeId: gadgetNodeId,
4376
4410
  depth: gadgetDepth,
4377
4411
  // Host exports for external gadgets to use host's llmist classes
4378
- hostExports: getHostExportsInternal()
4412
+ hostExports: getHostExportsInternal(),
4413
+ // Logger for structured logging (respects CLI's log level/file config)
4414
+ logger: this.logger
4379
4415
  };
4380
4416
  let rawResult;
4381
4417
  if (timeoutMs && timeoutMs > 0) {
@@ -4545,27 +4581,6 @@ var init_executor = __esm({
4545
4581
  async executeAll(calls) {
4546
4582
  return Promise.all(calls.map((call) => this.execute(call)));
4547
4583
  }
4548
- /**
4549
- * Deep equality check for objects/arrays.
4550
- * Used to detect if parameters were modified by an interceptor.
4551
- */
4552
- deepEquals(a, b) {
4553
- if (a === b) return true;
4554
- if (a === null || b === null) return a === b;
4555
- if (typeof a !== typeof b) return false;
4556
- if (typeof a !== "object") return a === b;
4557
- if (Array.isArray(a) !== Array.isArray(b)) return false;
4558
- if (Array.isArray(a) && Array.isArray(b)) {
4559
- if (a.length !== b.length) return false;
4560
- return a.every((val, i) => this.deepEquals(val, b[i]));
4561
- }
4562
- const aObj = a;
4563
- const bObj = b;
4564
- const aKeys = Object.keys(aObj);
4565
- const bKeys = Object.keys(bObj);
4566
- if (aKeys.length !== bKeys.length) return false;
4567
- return aKeys.every((key) => this.deepEquals(aObj[key], bObj[key]));
4568
- }
4569
4584
  };
4570
4585
  }
4571
4586
  });
@@ -4603,6 +4618,11 @@ var init_stream_processor = __esm({
4603
4618
  inFlightExecutions = /* @__PURE__ */ new Map();
4604
4619
  /** Queue of completed gadget results ready to be yielded (for real-time streaming) */
4605
4620
  completedResultsQueue = [];
4621
+ // Cross-iteration dependency tracking
4622
+ /** Invocation IDs completed in previous iterations (read-only reference from Agent) */
4623
+ priorCompletedInvocations;
4624
+ /** Invocation IDs that failed in previous iterations (read-only reference from Agent) */
4625
+ priorFailedInvocations;
4606
4626
  constructor(options) {
4607
4627
  this.iteration = options.iteration;
4608
4628
  this.registry = options.registry;
@@ -4611,6 +4631,8 @@ var init_stream_processor = __esm({
4611
4631
  this.tree = options.tree;
4612
4632
  this.parentNodeId = options.parentNodeId ?? null;
4613
4633
  this.baseDepth = options.baseDepth ?? 0;
4634
+ this.priorCompletedInvocations = options.priorCompletedInvocations ?? /* @__PURE__ */ new Set();
4635
+ this.priorFailedInvocations = options.priorFailedInvocations ?? /* @__PURE__ */ new Set();
4614
4636
  this.parser = new GadgetCallParser({
4615
4637
  startPrefix: options.gadgetStartPrefix,
4616
4638
  endPrefix: options.gadgetEndPrefix,
@@ -4809,62 +4831,11 @@ var init_stream_processor = __esm({
4809
4831
  }
4810
4832
  return [{ type: "text", content }];
4811
4833
  }
4812
- /**
4813
- * Process a gadget call through the full lifecycle, handling dependencies.
4814
- *
4815
- * Gadgets without dependencies (or with all dependencies satisfied) execute immediately.
4816
- * Gadgets with unsatisfied dependencies are queued for later execution.
4817
- * After each execution, pending gadgets are checked to see if they can now run.
4818
- */
4819
- async processGadgetCall(call) {
4820
- const events = [];
4821
- events.push({ type: "gadget_call", call });
4822
- if (call.dependencies.length > 0) {
4823
- if (call.dependencies.includes(call.invocationId)) {
4824
- this.logger.warn("Gadget has self-referential dependency (depends on itself)", {
4825
- gadgetName: call.gadgetName,
4826
- invocationId: call.invocationId
4827
- });
4828
- this.failedInvocations.add(call.invocationId);
4829
- const skipEvent = {
4830
- type: "gadget_skipped",
4831
- gadgetName: call.gadgetName,
4832
- invocationId: call.invocationId,
4833
- parameters: call.parameters ?? {},
4834
- failedDependency: call.invocationId,
4835
- failedDependencyError: `Gadget "${call.invocationId}" cannot depend on itself (self-referential dependency)`
4836
- };
4837
- events.push(skipEvent);
4838
- return events;
4839
- }
4840
- const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
4841
- if (failedDep) {
4842
- const skipEvents = await this.handleFailedDependency(call, failedDep);
4843
- events.push(...skipEvents);
4844
- return events;
4845
- }
4846
- const unsatisfied = call.dependencies.filter((dep) => !this.completedResults.has(dep));
4847
- if (unsatisfied.length > 0) {
4848
- this.logger.debug("Queueing gadget for later - waiting on dependencies", {
4849
- gadgetName: call.gadgetName,
4850
- invocationId: call.invocationId,
4851
- waitingOn: unsatisfied
4852
- });
4853
- this.gadgetsAwaitingDependencies.set(call.invocationId, call);
4854
- return events;
4855
- }
4856
- }
4857
- const executeEvents = await this.executeGadgetWithHooks(call);
4858
- events.push(...executeEvents);
4859
- const triggeredEvents = await this.processPendingGadgets();
4860
- events.push(...triggeredEvents);
4861
- return events;
4862
- }
4863
4834
  /**
4864
4835
  * Process a gadget call, yielding events in real-time.
4865
4836
  *
4866
- * Key difference from processGadgetCall: yields gadget_call event IMMEDIATELY
4867
- * when parsed (before execution), enabling real-time UI feedback.
4837
+ * Yields gadget_call event IMMEDIATELY when parsed (before execution),
4838
+ * enabling real-time UI feedback.
4868
4839
  */
4869
4840
  async *processGadgetCallGenerator(call) {
4870
4841
  yield { type: "gadget_call", call };
@@ -4895,7 +4866,9 @@ var init_stream_processor = __esm({
4895
4866
  yield skipEvent;
4896
4867
  return;
4897
4868
  }
4898
- const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
4869
+ const failedDep = call.dependencies.find(
4870
+ (dep) => this.failedInvocations.has(dep) || this.priorFailedInvocations.has(dep)
4871
+ );
4899
4872
  if (failedDep) {
4900
4873
  const skipEvents = await this.handleFailedDependency(call, failedDep);
4901
4874
  for (const evt of skipEvents) {
@@ -4903,7 +4876,9 @@ var init_stream_processor = __esm({
4903
4876
  }
4904
4877
  return;
4905
4878
  }
4906
- const unsatisfied = call.dependencies.filter((dep) => !this.completedResults.has(dep));
4879
+ const unsatisfied = call.dependencies.filter(
4880
+ (dep) => !this.completedResults.has(dep) && !this.priorCompletedInvocations.has(dep)
4881
+ );
4907
4882
  if (unsatisfied.length > 0) {
4908
4883
  this.logger.debug("Queueing gadget for later - waiting on dependencies", {
4909
4884
  gadgetName: call.gadgetName,
@@ -4925,143 +4900,9 @@ var init_stream_processor = __esm({
4925
4900
  this.inFlightExecutions.set(call.invocationId, executionPromise);
4926
4901
  }
4927
4902
  /**
4928
- * Execute a gadget through the full hook lifecycle.
4929
- * This is the core execution logic, extracted from processGadgetCall.
4930
- * @deprecated Use executeGadgetGenerator for real-time streaming
4931
- */
4932
- async executeGadgetWithHooks(call) {
4933
- const events = [];
4934
- if (call.parseError) {
4935
- this.logger.warn("Gadget has parse error", {
4936
- gadgetName: call.gadgetName,
4937
- error: call.parseError,
4938
- rawParameters: call.parametersRaw
4939
- });
4940
- }
4941
- let parameters = call.parameters ?? {};
4942
- if (this.hooks.interceptors?.interceptGadgetParameters) {
4943
- const context = {
4944
- iteration: this.iteration,
4945
- gadgetName: call.gadgetName,
4946
- invocationId: call.invocationId,
4947
- logger: this.logger
4948
- };
4949
- parameters = this.hooks.interceptors.interceptGadgetParameters(parameters, context);
4950
- }
4951
- call.parameters = parameters;
4952
- let shouldSkip = false;
4953
- let syntheticResult;
4954
- if (this.hooks.controllers?.beforeGadgetExecution) {
4955
- const context = {
4956
- iteration: this.iteration,
4957
- gadgetName: call.gadgetName,
4958
- invocationId: call.invocationId,
4959
- parameters,
4960
- logger: this.logger
4961
- };
4962
- const action = await this.hooks.controllers.beforeGadgetExecution(context);
4963
- validateBeforeGadgetExecutionAction(action);
4964
- if (action.action === "skip") {
4965
- shouldSkip = true;
4966
- syntheticResult = action.syntheticResult;
4967
- this.logger.info("Controller skipped gadget execution", {
4968
- gadgetName: call.gadgetName
4969
- });
4970
- }
4971
- }
4972
- const startObservers = [];
4973
- if (this.hooks.observers?.onGadgetExecutionStart) {
4974
- startObservers.push(async () => {
4975
- const context = {
4976
- iteration: this.iteration,
4977
- gadgetName: call.gadgetName,
4978
- invocationId: call.invocationId,
4979
- parameters,
4980
- logger: this.logger
4981
- };
4982
- await this.hooks.observers?.onGadgetExecutionStart?.(context);
4983
- });
4984
- }
4985
- await this.runObserversInParallel(startObservers);
4986
- let result;
4987
- if (shouldSkip) {
4988
- result = {
4989
- gadgetName: call.gadgetName,
4990
- invocationId: call.invocationId,
4991
- parameters,
4992
- result: syntheticResult ?? "Execution skipped",
4993
- executionTimeMs: 0
4994
- };
4995
- } else {
4996
- result = await this.executor.execute(call);
4997
- }
4998
- const originalResult = result.result;
4999
- if (result.result && this.hooks.interceptors?.interceptGadgetResult) {
5000
- const context = {
5001
- iteration: this.iteration,
5002
- gadgetName: result.gadgetName,
5003
- invocationId: result.invocationId,
5004
- parameters,
5005
- executionTimeMs: result.executionTimeMs,
5006
- logger: this.logger
5007
- };
5008
- result.result = this.hooks.interceptors.interceptGadgetResult(result.result, context);
5009
- }
5010
- if (this.hooks.controllers?.afterGadgetExecution) {
5011
- const context = {
5012
- iteration: this.iteration,
5013
- gadgetName: result.gadgetName,
5014
- invocationId: result.invocationId,
5015
- parameters,
5016
- result: result.result,
5017
- error: result.error,
5018
- executionTimeMs: result.executionTimeMs,
5019
- logger: this.logger
5020
- };
5021
- const action = await this.hooks.controllers.afterGadgetExecution(context);
5022
- validateAfterGadgetExecutionAction(action);
5023
- if (action.action === "recover" && result.error) {
5024
- this.logger.info("Controller recovered from gadget error", {
5025
- gadgetName: result.gadgetName,
5026
- originalError: result.error
5027
- });
5028
- result = {
5029
- ...result,
5030
- error: void 0,
5031
- result: action.fallbackResult
5032
- };
5033
- }
5034
- }
5035
- const completeObservers = [];
5036
- if (this.hooks.observers?.onGadgetExecutionComplete) {
5037
- completeObservers.push(async () => {
5038
- const context = {
5039
- iteration: this.iteration,
5040
- gadgetName: result.gadgetName,
5041
- invocationId: result.invocationId,
5042
- parameters,
5043
- originalResult,
5044
- finalResult: result.result,
5045
- error: result.error,
5046
- executionTimeMs: result.executionTimeMs,
5047
- breaksLoop: result.breaksLoop,
5048
- cost: result.cost,
5049
- logger: this.logger
5050
- };
5051
- await this.hooks.observers?.onGadgetExecutionComplete?.(context);
5052
- });
5053
- }
5054
- await this.runObserversInParallel(completeObservers);
5055
- this.completedResults.set(result.invocationId, result);
5056
- if (result.error) {
5057
- this.failedInvocations.add(result.invocationId);
5058
- }
5059
- events.push({ type: "gadget_result", result });
5060
- return events;
5061
- }
5062
- /**
5063
- * Execute a gadget and yield the result event.
5064
- * Generator version that yields gadget_result immediately when execution completes.
4903
+ * Execute a gadget through the full hook lifecycle and yield events.
4904
+ * Handles parameter interception, before/after controllers, observers,
4905
+ * execution, result interception, and tree tracking.
5065
4906
  */
5066
4907
  async *executeGadgetGenerator(call) {
5067
4908
  if (call.parseError) {
@@ -5327,8 +5168,9 @@ var init_stream_processor = __esm({
5327
5168
  invocationId: call.invocationId,
5328
5169
  failedDependency: failedDep
5329
5170
  });
5330
- const executeEvents = await this.executeGadgetWithHooks(call);
5331
- events.push(...executeEvents);
5171
+ for await (const evt of this.executeGadgetGenerator(call)) {
5172
+ events.push(evt);
5173
+ }
5332
5174
  } else if (action.action === "use_fallback") {
5333
5175
  const fallbackResult = {
5334
5176
  gadgetName: call.gadgetName,
@@ -5349,90 +5191,9 @@ var init_stream_processor = __esm({
5349
5191
  }
5350
5192
  /**
5351
5193
  * Process pending gadgets whose dependencies are now satisfied.
5352
- * Executes ready gadgets in parallel and continues until no more can be triggered.
5353
- */
5354
- async processPendingGadgets() {
5355
- const events = [];
5356
- let progress = true;
5357
- while (progress && this.gadgetsAwaitingDependencies.size > 0) {
5358
- progress = false;
5359
- const readyToExecute = [];
5360
- const readyToSkip = [];
5361
- for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
5362
- const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
5363
- if (failedDep) {
5364
- readyToSkip.push({ call, failedDep });
5365
- continue;
5366
- }
5367
- const allSatisfied = call.dependencies.every((dep) => this.completedResults.has(dep));
5368
- if (allSatisfied) {
5369
- readyToExecute.push(call);
5370
- }
5371
- }
5372
- for (const { call, failedDep } of readyToSkip) {
5373
- this.gadgetsAwaitingDependencies.delete(call.invocationId);
5374
- const skipEvents = await this.handleFailedDependency(call, failedDep);
5375
- events.push(...skipEvents);
5376
- progress = true;
5377
- }
5378
- if (readyToExecute.length > 0) {
5379
- this.logger.debug("Executing ready gadgets in parallel", {
5380
- count: readyToExecute.length,
5381
- invocationIds: readyToExecute.map((c) => c.invocationId)
5382
- });
5383
- for (const call of readyToExecute) {
5384
- this.gadgetsAwaitingDependencies.delete(call.invocationId);
5385
- }
5386
- const executePromises = readyToExecute.map((call) => this.executeGadgetWithHooks(call));
5387
- const results = await Promise.all(executePromises);
5388
- for (const executeEvents of results) {
5389
- events.push(...executeEvents);
5390
- }
5391
- progress = true;
5392
- }
5393
- }
5394
- if (this.gadgetsAwaitingDependencies.size > 0) {
5395
- const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
5396
- for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
5397
- const missingDeps = call.dependencies.filter((dep) => !this.completedResults.has(dep));
5398
- const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
5399
- const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
5400
- let errorMessage;
5401
- let logLevel = "warn";
5402
- if (circularDeps.length > 0 && trulyMissingDeps.length > 0) {
5403
- errorMessage = `Dependencies unresolvable: circular=[${circularDeps.join(", ")}], missing=[${trulyMissingDeps.join(", ")}]`;
5404
- logLevel = "error";
5405
- } else if (circularDeps.length > 0) {
5406
- errorMessage = `Circular dependency detected: "${invocationId}" depends on "${circularDeps[0]}" which also depends on "${invocationId}" (directly or indirectly)`;
5407
- } else {
5408
- errorMessage = `Dependency "${missingDeps[0]}" was never executed - check that the invocation ID exists and is spelled correctly`;
5409
- }
5410
- this.logger[logLevel]("Gadget has unresolvable dependencies", {
5411
- gadgetName: call.gadgetName,
5412
- invocationId,
5413
- circularDependencies: circularDeps,
5414
- missingDependencies: trulyMissingDeps
5415
- });
5416
- this.failedInvocations.add(invocationId);
5417
- const skipEvent = {
5418
- type: "gadget_skipped",
5419
- gadgetName: call.gadgetName,
5420
- invocationId,
5421
- parameters: call.parameters ?? {},
5422
- failedDependency: missingDeps[0],
5423
- failedDependencyError: errorMessage
5424
- };
5425
- events.push(skipEvent);
5426
- }
5427
- this.gadgetsAwaitingDependencies.clear();
5428
- }
5429
- return events;
5430
- }
5431
- /**
5432
- * Process pending gadgets, yielding events in real-time.
5433
- * Generator version that yields events as gadgets complete.
5194
+ * Yields events in real-time as gadgets complete.
5434
5195
  *
5435
- * Note: Gadgets are still executed in parallel for efficiency,
5196
+ * Gadgets are executed in parallel for efficiency,
5436
5197
  * but results are yielded as they become available.
5437
5198
  */
5438
5199
  async *processPendingGadgetsGenerator() {
@@ -5442,12 +5203,16 @@ var init_stream_processor = __esm({
5442
5203
  const readyToExecute = [];
5443
5204
  const readyToSkip = [];
5444
5205
  for (const [_invocationId, call] of this.gadgetsAwaitingDependencies) {
5445
- const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
5206
+ const failedDep = call.dependencies.find(
5207
+ (dep) => this.failedInvocations.has(dep) || this.priorFailedInvocations.has(dep)
5208
+ );
5446
5209
  if (failedDep) {
5447
5210
  readyToSkip.push({ call, failedDep });
5448
5211
  continue;
5449
5212
  }
5450
- const allSatisfied = call.dependencies.every((dep) => this.completedResults.has(dep));
5213
+ const allSatisfied = call.dependencies.every(
5214
+ (dep) => this.completedResults.has(dep) || this.priorCompletedInvocations.has(dep)
5215
+ );
5451
5216
  if (allSatisfied) {
5452
5217
  readyToExecute.push(call);
5453
5218
  }
@@ -5488,7 +5253,9 @@ var init_stream_processor = __esm({
5488
5253
  if (this.gadgetsAwaitingDependencies.size > 0) {
5489
5254
  const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
5490
5255
  for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
5491
- const missingDeps = call.dependencies.filter((dep) => !this.completedResults.has(dep));
5256
+ const missingDeps = call.dependencies.filter(
5257
+ (dep) => !this.completedResults.has(dep) && !this.priorCompletedInvocations.has(dep)
5258
+ );
5492
5259
  const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
5493
5260
  const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
5494
5261
  let errorMessage;
@@ -5542,10 +5309,27 @@ var init_stream_processor = __esm({
5542
5309
  */
5543
5310
  async runObserversInParallel(observers) {
5544
5311
  if (observers.length === 0) return;
5545
- const results = await Promise.allSettled(
5312
+ await Promise.allSettled(
5546
5313
  observers.map((observer) => this.safeObserve(observer))
5547
5314
  );
5548
5315
  }
5316
+ // ==========================================================================
5317
+ // Public accessors for cross-iteration dependency tracking
5318
+ // ==========================================================================
5319
+ /**
5320
+ * Get all invocation IDs that completed successfully in this iteration.
5321
+ * Used by Agent to accumulate completed IDs across iterations.
5322
+ */
5323
+ getCompletedInvocationIds() {
5324
+ return new Set(this.completedResults.keys());
5325
+ }
5326
+ /**
5327
+ * Get all invocation IDs that failed in this iteration.
5328
+ * Used by Agent to accumulate failed IDs across iterations.
5329
+ */
5330
+ getFailedInvocationIds() {
5331
+ return new Set(this.failedInvocations);
5332
+ }
5549
5333
  };
5550
5334
  }
5551
5335
  });
@@ -5608,6 +5392,9 @@ var init_agent = __esm({
5608
5392
  onSubagentEvent;
5609
5393
  // Counter for generating synthetic invocation IDs for wrapped text content
5610
5394
  syntheticInvocationCounter = 0;
5395
+ // Cross-iteration dependency tracking - allows gadgets to depend on results from prior iterations
5396
+ completedInvocationIds = /* @__PURE__ */ new Set();
5397
+ failedInvocationIds = /* @__PURE__ */ new Set();
5611
5398
  // Execution Tree - first-class model for nested subagent support
5612
5399
  tree;
5613
5400
  parentNodeId;
@@ -5917,96 +5704,22 @@ var init_agent = __esm({
5917
5704
  maxIterations: this.maxIterations
5918
5705
  });
5919
5706
  while (currentIteration < this.maxIterations) {
5920
- if (this.signal?.aborted) {
5921
- this.logger.info("Agent loop terminated by abort signal", {
5922
- iteration: currentIteration,
5923
- reason: this.signal.reason
5924
- });
5925
- await this.safeObserve(async () => {
5926
- if (this.hooks.observers?.onAbort) {
5927
- const context = {
5928
- iteration: currentIteration,
5929
- reason: this.signal?.reason,
5930
- logger: this.logger
5931
- };
5932
- await this.hooks.observers.onAbort(context);
5933
- }
5934
- });
5707
+ if (await this.checkAbortAndNotify(currentIteration)) {
5935
5708
  return;
5936
5709
  }
5937
5710
  this.logger.debug("Starting iteration", { iteration: currentIteration });
5938
5711
  try {
5939
- if (this.compactionManager) {
5940
- const compactionEvent = await this.compactionManager.checkAndCompact(
5941
- this.conversation,
5942
- currentIteration
5943
- );
5944
- if (compactionEvent) {
5945
- this.logger.info("Context compacted", {
5946
- strategy: compactionEvent.strategy,
5947
- tokensBefore: compactionEvent.tokensBefore,
5948
- tokensAfter: compactionEvent.tokensAfter
5949
- });
5950
- yield { type: "compaction", event: compactionEvent };
5951
- await this.safeObserve(async () => {
5952
- if (this.hooks.observers?.onCompaction) {
5953
- await this.hooks.observers.onCompaction({
5954
- iteration: currentIteration,
5955
- event: compactionEvent,
5956
- // biome-ignore lint/style/noNonNullAssertion: compactionManager exists if compactionEvent is truthy
5957
- stats: this.compactionManager.getStats(),
5958
- logger: this.logger
5959
- });
5960
- }
5961
- });
5962
- }
5712
+ const compactionEvent = await this.checkAndPerformCompaction(currentIteration);
5713
+ if (compactionEvent) {
5714
+ yield compactionEvent;
5963
5715
  }
5964
- let llmOptions = {
5965
- model: this.model,
5966
- messages: this.conversation.getMessages(),
5967
- temperature: this.temperature,
5968
- maxTokens: this.defaultMaxTokens,
5969
- signal: this.signal
5970
- };
5971
- await this.safeObserve(async () => {
5972
- if (this.hooks.observers?.onLLMCallStart) {
5973
- const context = {
5974
- iteration: currentIteration,
5975
- options: llmOptions,
5976
- logger: this.logger
5977
- };
5978
- await this.hooks.observers.onLLMCallStart(context);
5979
- }
5980
- });
5981
- if (this.hooks.controllers?.beforeLLMCall) {
5982
- const context = {
5983
- iteration: currentIteration,
5984
- maxIterations: this.maxIterations,
5985
- options: llmOptions,
5986
- logger: this.logger
5987
- };
5988
- const action = await this.hooks.controllers.beforeLLMCall(context);
5989
- validateBeforeLLMCallAction(action);
5990
- if (action.action === "skip") {
5991
- this.logger.info("Controller skipped LLM call, using synthetic response");
5992
- this.conversation.addAssistantMessage(action.syntheticResponse);
5993
- yield { type: "text", content: action.syntheticResponse };
5994
- break;
5995
- } else if (action.action === "proceed" && action.modifiedOptions) {
5996
- llmOptions = { ...llmOptions, ...action.modifiedOptions };
5997
- }
5716
+ const prepared = await this.prepareLLMCall(currentIteration);
5717
+ const llmOptions = prepared.options;
5718
+ if (prepared.skipWithSynthetic !== void 0) {
5719
+ this.conversation.addAssistantMessage(prepared.skipWithSynthetic);
5720
+ yield { type: "text", content: prepared.skipWithSynthetic };
5721
+ break;
5998
5722
  }
5999
- await this.safeObserve(async () => {
6000
- if (this.hooks.observers?.onLLMCallReady) {
6001
- const context = {
6002
- iteration: currentIteration,
6003
- maxIterations: this.maxIterations,
6004
- options: llmOptions,
6005
- logger: this.logger
6006
- };
6007
- await this.hooks.observers.onLLMCallReady(context);
6008
- }
6009
- });
6010
5723
  this.logger.info("Calling LLM", { model: this.model });
6011
5724
  this.logger.silly("LLM request details", {
6012
5725
  model: llmOptions.model,
@@ -6042,7 +5755,10 @@ var init_agent = __esm({
6042
5755
  tree: this.tree,
6043
5756
  parentNodeId: currentLLMNodeId,
6044
5757
  // Gadgets are children of this LLM call
6045
- baseDepth: this.baseDepth
5758
+ baseDepth: this.baseDepth,
5759
+ // Cross-iteration dependency tracking
5760
+ priorCompletedInvocations: this.completedInvocationIds,
5761
+ priorFailedInvocations: this.failedInvocationIds
6046
5762
  });
6047
5763
  let streamMetadata = null;
6048
5764
  let gadgetCallCount = 0;
@@ -6065,6 +5781,12 @@ var init_agent = __esm({
6065
5781
  if (!streamMetadata) {
6066
5782
  throw new Error("Stream processing completed without metadata event");
6067
5783
  }
5784
+ for (const id of processor.getCompletedInvocationIds()) {
5785
+ this.completedInvocationIds.add(id);
5786
+ }
5787
+ for (const id of processor.getFailedInvocationIds()) {
5788
+ this.failedInvocationIds.add(id);
5789
+ }
6068
5790
  const result = streamMetadata;
6069
5791
  this.logger.info("LLM response completed", {
6070
5792
  finishReason: result.finishReason,
@@ -6088,81 +5810,21 @@ var init_agent = __esm({
6088
5810
  await this.hooks.observers.onLLMCallComplete(context);
6089
5811
  }
6090
5812
  });
6091
- this.tree.completeLLMCall(currentLLMNodeId, {
6092
- response: result.rawResponse,
6093
- usage: result.usage,
6094
- finishReason: result.finishReason
6095
- });
6096
- let finalMessage = result.finalMessage;
6097
- if (this.hooks.controllers?.afterLLMCall) {
6098
- const context = {
6099
- iteration: currentIteration,
6100
- maxIterations: this.maxIterations,
6101
- options: llmOptions,
6102
- finishReason: result.finishReason,
6103
- usage: result.usage,
6104
- finalMessage: result.finalMessage,
6105
- gadgetCallCount,
6106
- logger: this.logger
6107
- };
6108
- const action = await this.hooks.controllers.afterLLMCall(context);
6109
- validateAfterLLMCallAction(action);
6110
- if (action.action === "modify_and_continue" || action.action === "append_and_modify") {
6111
- finalMessage = action.modifiedMessage;
6112
- }
6113
- if (action.action === "append_messages" || action.action === "append_and_modify") {
6114
- for (const msg of action.messages) {
6115
- if (msg.role === "user") {
6116
- this.conversation.addUserMessage(msg.content);
6117
- } else if (msg.role === "assistant") {
6118
- this.conversation.addAssistantMessage(extractMessageText(msg.content));
6119
- } else if (msg.role === "system") {
6120
- this.conversation.addUserMessage(`[System] ${extractMessageText(msg.content)}`);
6121
- }
6122
- }
6123
- }
6124
- }
6125
- if (result.didExecuteGadgets) {
6126
- if (this.textWithGadgetsHandler) {
6127
- const textContent = textOutputs.join("");
6128
- if (textContent.trim()) {
6129
- const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
6130
- const syntheticId = `gc_text_${++this.syntheticInvocationCounter}`;
6131
- this.conversation.addGadgetCallResult(
6132
- gadgetName,
6133
- parameterMapping(textContent),
6134
- resultMapping ? resultMapping(textContent) : textContent,
6135
- syntheticId
6136
- );
6137
- }
6138
- }
6139
- for (const output of gadgetResults) {
6140
- if (output.type === "gadget_result") {
6141
- const gadgetResult = output.result;
6142
- this.conversation.addGadgetCallResult(
6143
- gadgetResult.gadgetName,
6144
- gadgetResult.parameters,
6145
- gadgetResult.error ?? gadgetResult.result ?? "",
6146
- gadgetResult.invocationId,
6147
- gadgetResult.media,
6148
- gadgetResult.mediaIds
6149
- );
6150
- }
6151
- }
6152
- } else {
6153
- if (finalMessage.trim()) {
6154
- const syntheticId = `gc_tell_${++this.syntheticInvocationCounter}`;
6155
- this.conversation.addGadgetCallResult(
6156
- "TellUser",
6157
- { message: finalMessage, done: false, type: "info" },
6158
- `\u2139\uFE0F ${finalMessage}`,
6159
- syntheticId
6160
- );
6161
- }
6162
- const shouldBreak = await this.handleTextOnlyResponse(finalMessage);
6163
- if (shouldBreak) {
6164
- break;
6165
- }
5813
+ this.completeLLMCallInTree(currentLLMNodeId, result);
5814
+ const finalMessage = await this.processAfterLLMCallController(
5815
+ currentIteration,
5816
+ llmOptions,
5817
+ result,
5818
+ gadgetCallCount
5819
+ );
5820
+ const shouldBreakFromTextOnly = await this.updateConversationWithResults(
5821
+ result.didExecuteGadgets,
5822
+ textOutputs,
5823
+ gadgetResults,
5824
+ finalMessage
5825
+ );
5826
+ if (shouldBreakFromTextOnly) {
5827
+ break;
6166
5828
  }
6167
5829
  if (result.shouldBreakLoop) {
6168
5830
  this.logger.info("Loop terminated by gadget or processor");
@@ -6315,6 +5977,210 @@ var init_agent = __esm({
6315
5977
  }
6316
5978
  };
6317
5979
  }
5980
+ // ==========================================================================
5981
+ // Agent Loop Helper Methods (extracted from run() for readability)
5982
+ // ==========================================================================
5983
+ /**
5984
+ * Check abort signal and notify observers if aborted.
5985
+ * @returns true if agent should terminate
5986
+ */
5987
+ async checkAbortAndNotify(iteration) {
5988
+ if (!this.signal?.aborted) return false;
5989
+ this.logger.info("Agent loop terminated by abort signal", {
5990
+ iteration,
5991
+ reason: this.signal.reason
5992
+ });
5993
+ await this.safeObserve(async () => {
5994
+ if (this.hooks.observers?.onAbort) {
5995
+ const context = {
5996
+ iteration,
5997
+ reason: this.signal?.reason,
5998
+ logger: this.logger
5999
+ };
6000
+ await this.hooks.observers.onAbort(context);
6001
+ }
6002
+ });
6003
+ return true;
6004
+ }
6005
+ /**
6006
+ * Check and perform context compaction if needed.
6007
+ * @returns compaction stream event if compaction occurred, null otherwise
6008
+ */
6009
+ async checkAndPerformCompaction(iteration) {
6010
+ if (!this.compactionManager) return null;
6011
+ const compactionEvent = await this.compactionManager.checkAndCompact(
6012
+ this.conversation,
6013
+ iteration
6014
+ );
6015
+ if (!compactionEvent) return null;
6016
+ this.logger.info("Context compacted", {
6017
+ strategy: compactionEvent.strategy,
6018
+ tokensBefore: compactionEvent.tokensBefore,
6019
+ tokensAfter: compactionEvent.tokensAfter
6020
+ });
6021
+ await this.safeObserve(async () => {
6022
+ if (this.hooks.observers?.onCompaction) {
6023
+ await this.hooks.observers.onCompaction({
6024
+ iteration,
6025
+ event: compactionEvent,
6026
+ // biome-ignore lint/style/noNonNullAssertion: compactionManager exists if compactionEvent is truthy
6027
+ stats: this.compactionManager.getStats(),
6028
+ logger: this.logger
6029
+ });
6030
+ }
6031
+ });
6032
+ return { type: "compaction", event: compactionEvent };
6033
+ }
6034
+ /**
6035
+ * Prepare LLM call options and process beforeLLMCall controller.
6036
+ * @returns options and optional skipWithSynthetic response if controller wants to skip
6037
+ */
6038
+ async prepareLLMCall(iteration) {
6039
+ let llmOptions = {
6040
+ model: this.model,
6041
+ messages: this.conversation.getMessages(),
6042
+ temperature: this.temperature,
6043
+ maxTokens: this.defaultMaxTokens,
6044
+ signal: this.signal
6045
+ };
6046
+ await this.safeObserve(async () => {
6047
+ if (this.hooks.observers?.onLLMCallStart) {
6048
+ const context = {
6049
+ iteration,
6050
+ options: llmOptions,
6051
+ logger: this.logger
6052
+ };
6053
+ await this.hooks.observers.onLLMCallStart(context);
6054
+ }
6055
+ });
6056
+ if (this.hooks.controllers?.beforeLLMCall) {
6057
+ const context = {
6058
+ iteration,
6059
+ maxIterations: this.maxIterations,
6060
+ options: llmOptions,
6061
+ logger: this.logger
6062
+ };
6063
+ const action = await this.hooks.controllers.beforeLLMCall(context);
6064
+ validateBeforeLLMCallAction(action);
6065
+ if (action.action === "skip") {
6066
+ this.logger.info("Controller skipped LLM call, using synthetic response");
6067
+ return { options: llmOptions, skipWithSynthetic: action.syntheticResponse };
6068
+ } else if (action.action === "proceed" && action.modifiedOptions) {
6069
+ llmOptions = { ...llmOptions, ...action.modifiedOptions };
6070
+ }
6071
+ }
6072
+ await this.safeObserve(async () => {
6073
+ if (this.hooks.observers?.onLLMCallReady) {
6074
+ const context = {
6075
+ iteration,
6076
+ maxIterations: this.maxIterations,
6077
+ options: llmOptions,
6078
+ logger: this.logger
6079
+ };
6080
+ await this.hooks.observers.onLLMCallReady(context);
6081
+ }
6082
+ });
6083
+ return { options: llmOptions };
6084
+ }
6085
+ /**
6086
+ * Calculate cost and complete LLM call in execution tree.
6087
+ */
6088
+ completeLLMCallInTree(nodeId, result) {
6089
+ const llmCost = this.client.modelRegistry?.estimateCost?.(
6090
+ this.model,
6091
+ result.usage?.inputTokens ?? 0,
6092
+ result.usage?.outputTokens ?? 0,
6093
+ result.usage?.cachedInputTokens ?? 0,
6094
+ result.usage?.cacheCreationInputTokens ?? 0
6095
+ )?.totalCost;
6096
+ this.tree.completeLLMCall(nodeId, {
6097
+ response: result.rawResponse,
6098
+ usage: result.usage,
6099
+ finishReason: result.finishReason,
6100
+ cost: llmCost
6101
+ });
6102
+ }
6103
+ /**
6104
+ * Process afterLLMCall controller and return modified final message.
6105
+ */
6106
+ async processAfterLLMCallController(iteration, llmOptions, result, gadgetCallCount) {
6107
+ let finalMessage = result.finalMessage;
6108
+ if (!this.hooks.controllers?.afterLLMCall) {
6109
+ return finalMessage;
6110
+ }
6111
+ const context = {
6112
+ iteration,
6113
+ maxIterations: this.maxIterations,
6114
+ options: llmOptions,
6115
+ finishReason: result.finishReason,
6116
+ usage: result.usage,
6117
+ finalMessage: result.finalMessage,
6118
+ gadgetCallCount,
6119
+ logger: this.logger
6120
+ };
6121
+ const action = await this.hooks.controllers.afterLLMCall(context);
6122
+ validateAfterLLMCallAction(action);
6123
+ if (action.action === "modify_and_continue" || action.action === "append_and_modify") {
6124
+ finalMessage = action.modifiedMessage;
6125
+ }
6126
+ if (action.action === "append_messages" || action.action === "append_and_modify") {
6127
+ for (const msg of action.messages) {
6128
+ if (msg.role === "user") {
6129
+ this.conversation.addUserMessage(msg.content);
6130
+ } else if (msg.role === "assistant") {
6131
+ this.conversation.addAssistantMessage(extractMessageText(msg.content));
6132
+ } else if (msg.role === "system") {
6133
+ this.conversation.addUserMessage(`[System] ${extractMessageText(msg.content)}`);
6134
+ }
6135
+ }
6136
+ }
6137
+ return finalMessage;
6138
+ }
6139
+ /**
6140
+ * Update conversation history with gadget results or text-only response.
6141
+ * @returns true if loop should break (text-only handler requested termination)
6142
+ */
6143
+ async updateConversationWithResults(didExecuteGadgets, textOutputs, gadgetResults, finalMessage) {
6144
+ if (didExecuteGadgets) {
6145
+ if (this.textWithGadgetsHandler) {
6146
+ const textContent = textOutputs.join("");
6147
+ if (textContent.trim()) {
6148
+ const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
6149
+ const syntheticId = `gc_text_${++this.syntheticInvocationCounter}`;
6150
+ this.conversation.addGadgetCallResult(
6151
+ gadgetName,
6152
+ parameterMapping(textContent),
6153
+ resultMapping ? resultMapping(textContent) : textContent,
6154
+ syntheticId
6155
+ );
6156
+ }
6157
+ }
6158
+ for (const output of gadgetResults) {
6159
+ if (output.type === "gadget_result") {
6160
+ const gadgetResult = output.result;
6161
+ this.conversation.addGadgetCallResult(
6162
+ gadgetResult.gadgetName,
6163
+ gadgetResult.parameters,
6164
+ gadgetResult.error ?? gadgetResult.result ?? "",
6165
+ gadgetResult.invocationId,
6166
+ gadgetResult.media,
6167
+ gadgetResult.mediaIds
6168
+ );
6169
+ }
6170
+ }
6171
+ return false;
6172
+ }
6173
+ if (finalMessage.trim()) {
6174
+ const syntheticId = `gc_tell_${++this.syntheticInvocationCounter}`;
6175
+ this.conversation.addGadgetCallResult(
6176
+ "TellUser",
6177
+ { message: finalMessage, done: false, type: "info" },
6178
+ `\u2139\uFE0F ${finalMessage}`,
6179
+ syntheticId
6180
+ );
6181
+ }
6182
+ return await this.handleTextOnlyResponse(finalMessage);
6183
+ }
6318
6184
  /**
6319
6185
  * Run agent with named event handlers (syntactic sugar).
6320
6186
  *
@@ -9847,11 +9713,13 @@ var init_model_registry = __esm({
9847
9713
  }
9848
9714
  /**
9849
9715
  * Get model specification by model ID
9850
- * @param modelId - Full model identifier (e.g., 'gpt-5', 'claude-sonnet-4-5-20250929')
9716
+ * @param modelId - Full model identifier, optionally with provider prefix
9717
+ * (e.g., 'gpt-5', 'claude-sonnet-4-5-20250929', 'anthropic:claude-sonnet-4-5')
9851
9718
  * @returns ModelSpec if found, undefined otherwise
9852
9719
  */
9853
9720
  getModelSpec(modelId) {
9854
- return this.modelSpecs.find((model) => model.modelId === modelId);
9721
+ const normalizedId = modelId.includes(":") ? modelId.split(":")[1] : modelId;
9722
+ return this.modelSpecs.find((model) => model.modelId === normalizedId);
9855
9723
  }
9856
9724
  /**
9857
9725
  * List all models, optionally filtered by provider
@@ -12343,4 +12211,4 @@ export {
12343
12211
  createEmptyStream,
12344
12212
  createErrorStream
12345
12213
  };
12346
- //# sourceMappingURL=chunk-36YSBSGB.js.map
12214
+ //# sourceMappingURL=chunk-SFZIL2VR.js.map