llmist 6.1.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.
@@ -52,6 +52,9 @@ function parseEnvBoolean(value) {
52
52
  if (normalized === "false" || normalized === "0") return false;
53
53
  return void 0;
54
54
  }
55
+ function stripAnsi(str) {
56
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
57
+ }
55
58
  function createLogger(options = {}) {
56
59
  const envMinLevel = parseLogLevel(process.env.LLMIST_LOG_LEVEL);
57
60
  const envLogFile = process.env.LLMIST_LOG_FILE?.trim() ?? "";
@@ -60,36 +63,62 @@ function createLogger(options = {}) {
60
63
  const defaultType = options.type ?? "pretty";
61
64
  const name = options.name ?? "llmist";
62
65
  const logReset = options.logReset ?? envLogReset ?? false;
63
- let logFileStream;
64
- let finalType = defaultType;
65
- if (envLogFile) {
66
+ if (envLogFile && (!logFileInitialized || sharedLogFilePath !== envLogFile)) {
66
67
  try {
68
+ if (sharedLogFileStream) {
69
+ sharedLogFileStream.end();
70
+ sharedLogFileStream = void 0;
71
+ }
67
72
  (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(envLogFile), { recursive: true });
68
73
  const flags = logReset ? "w" : "a";
69
- logFileStream = (0, import_node_fs.createWriteStream)(envLogFile, { flags });
70
- finalType = "hidden";
74
+ sharedLogFileStream = (0, import_node_fs.createWriteStream)(envLogFile, { flags });
75
+ sharedLogFilePath = envLogFile;
76
+ logFileInitialized = true;
77
+ writeErrorCount = 0;
78
+ writeErrorReported = false;
79
+ sharedLogFileStream.on("error", (error) => {
80
+ writeErrorCount++;
81
+ if (!writeErrorReported) {
82
+ console.error(`[llmist] Log file write error: ${error.message}`);
83
+ writeErrorReported = true;
84
+ }
85
+ if (writeErrorCount >= MAX_WRITE_ERRORS_BEFORE_DISABLE) {
86
+ console.error(
87
+ `[llmist] Too many log file errors (${writeErrorCount}), disabling file logging`
88
+ );
89
+ sharedLogFileStream?.end();
90
+ sharedLogFileStream = void 0;
91
+ }
92
+ });
71
93
  } catch (error) {
72
94
  console.error("Failed to initialize LLMIST_LOG_FILE output:", error);
73
95
  }
74
96
  }
97
+ const useFileLogging = Boolean(sharedLogFileStream);
75
98
  const logger = new import_tslog.Logger({
76
99
  name,
77
100
  minLevel,
78
- type: finalType,
79
- // Optimize for production
80
- hideLogPositionForProduction: finalType !== "pretty",
81
- // Pretty output settings
82
- prettyLogTemplate: finalType === "pretty" ? "{{yyyy}}-{{mm}}-{{dd}} {{hh}}:{{MM}}:{{ss}}:{{ms}} {{logLevelName}} [{{name}}] " : void 0
101
+ type: useFileLogging ? "pretty" : defaultType,
102
+ // Hide log position for file logging and non-pretty types
103
+ hideLogPositionForProduction: useFileLogging || defaultType !== "pretty",
104
+ prettyLogTemplate: LOG_TEMPLATE,
105
+ // Use overwrite to redirect tslog's formatted output to file instead of console
106
+ overwrite: useFileLogging ? {
107
+ transportFormatted: (logMetaMarkup, logArgs, _logErrors) => {
108
+ if (!sharedLogFileStream) return;
109
+ const meta = stripAnsi(logMetaMarkup);
110
+ const args = logArgs.map(
111
+ (arg) => typeof arg === "string" ? stripAnsi(arg) : JSON.stringify(arg)
112
+ );
113
+ const line = `${meta}${args.join(" ")}
114
+ `;
115
+ sharedLogFileStream.write(line);
116
+ }
117
+ } : void 0
83
118
  });
84
- if (logFileStream) {
85
- logger.attachTransport((logObj) => {
86
- logFileStream?.write(`${JSON.stringify(logObj)}
87
- `);
88
- });
89
- }
90
119
  return logger;
91
120
  }
92
- var import_node_fs, import_node_path, import_tslog, LEVEL_NAME_TO_ID, defaultLogger;
121
+ var import_node_fs, import_node_path, import_tslog, LEVEL_NAME_TO_ID, sharedLogFilePath, sharedLogFileStream, logFileInitialized, writeErrorCount, writeErrorReported, MAX_WRITE_ERRORS_BEFORE_DISABLE, LOG_TEMPLATE, defaultLogger;
93
122
  var init_logger = __esm({
94
123
  "src/logging/logger.ts"() {
95
124
  "use strict";
@@ -105,6 +134,11 @@ var init_logger = __esm({
105
134
  error: 5,
106
135
  fatal: 6
107
136
  };
137
+ logFileInitialized = false;
138
+ writeErrorCount = 0;
139
+ writeErrorReported = false;
140
+ MAX_WRITE_ERRORS_BEFORE_DISABLE = 5;
141
+ LOG_TEMPLATE = "{{yyyy}}-{{mm}}-{{dd}} {{hh}}:{{MM}}:{{ss}}:{{ms}} {{logLevelName}} [{{name}}] ";
108
142
  defaultLogger = createLogger();
109
143
  }
110
144
  });
@@ -3795,9 +3829,8 @@ var init_cost_reporting_client = __esm({
3795
3829
  */
3796
3830
  reportCostFromUsage(model, inputTokens, outputTokens, cachedInputTokens = 0, cacheCreationInputTokens = 0) {
3797
3831
  if (inputTokens === 0 && outputTokens === 0) return;
3798
- const modelName = model.includes(":") ? model.split(":")[1] : model;
3799
3832
  const estimate = this.client.modelRegistry.estimateCost(
3800
- modelName,
3833
+ model,
3801
3834
  inputTokens,
3802
3835
  outputTokens,
3803
3836
  cachedInputTokens,
@@ -4080,18 +4113,64 @@ var init_parser = __esm({
4080
4113
  }
4081
4114
  });
4082
4115
 
4116
+ // src/gadgets/typed-gadget.ts
4117
+ function Gadget(config) {
4118
+ class GadgetBase extends AbstractGadget {
4119
+ description = config.description;
4120
+ parameterSchema = config.schema;
4121
+ name = config.name;
4122
+ timeoutMs = config.timeoutMs;
4123
+ examples = config.examples;
4124
+ /**
4125
+ * Type helper property for accessing inferred parameter type.
4126
+ * This is used in the execute method signature: `execute(params: this['params'])`
4127
+ *
4128
+ * Note: This is just for type inference - the actual params in execute()
4129
+ * will be Record<string, unknown> which you can safely cast to this['params']
4130
+ */
4131
+ params;
4132
+ }
4133
+ return GadgetBase;
4134
+ }
4135
+ var init_typed_gadget = __esm({
4136
+ "src/gadgets/typed-gadget.ts"() {
4137
+ "use strict";
4138
+ init_gadget();
4139
+ }
4140
+ });
4141
+
4083
4142
  // src/gadgets/executor.ts
4084
- var GadgetExecutor;
4143
+ function getHostExportsInternal() {
4144
+ if (!cachedHostExports) {
4145
+ cachedHostExports = {
4146
+ AgentBuilder,
4147
+ Gadget,
4148
+ createGadget,
4149
+ ExecutionTree,
4150
+ LLMist,
4151
+ z: import_zod2.z
4152
+ };
4153
+ }
4154
+ return cachedHostExports;
4155
+ }
4156
+ var import_fast_deep_equal, import_zod2, cachedHostExports, GadgetExecutor;
4085
4157
  var init_executor = __esm({
4086
4158
  "src/gadgets/executor.ts"() {
4087
4159
  "use strict";
4160
+ import_fast_deep_equal = __toESM(require("fast-deep-equal"), 1);
4161
+ import_zod2 = require("zod");
4162
+ init_builder();
4163
+ init_client();
4088
4164
  init_constants();
4165
+ init_execution_tree();
4089
4166
  init_logger();
4090
4167
  init_block_params();
4091
4168
  init_cost_reporting_client();
4169
+ init_create_gadget();
4092
4170
  init_error_formatter();
4093
4171
  init_exceptions();
4094
4172
  init_parser();
4173
+ init_typed_gadget();
4095
4174
  GadgetExecutor = class {
4096
4175
  constructor(registry, requestHumanInput, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig, onSubagentEvent, tree, parentNodeId, baseDepth) {
4097
4176
  this.registry = registry;
@@ -4189,7 +4268,7 @@ var init_executor = __esm({
4189
4268
  try {
4190
4269
  const cleanedRaw = stripMarkdownFences(call.parametersRaw);
4191
4270
  const initialParse = parseBlockParams(cleanedRaw, { argPrefix: this.argPrefix });
4192
- const parametersWereModified = !this.deepEquals(rawParameters, initialParse);
4271
+ const parametersWereModified = !(0, import_fast_deep_equal.default)(rawParameters, initialParse);
4193
4272
  if (parametersWereModified) {
4194
4273
  this.logger.debug("Parameters modified by interceptor, skipping re-parse", {
4195
4274
  gadgetName: call.gadgetName
@@ -4264,7 +4343,11 @@ var init_executor = __esm({
4264
4343
  // Tree context for subagent support - use gadget's own node ID
4265
4344
  tree: this.tree,
4266
4345
  nodeId: gadgetNodeId,
4267
- depth: gadgetDepth
4346
+ depth: gadgetDepth,
4347
+ // Host exports for external gadgets to use host's llmist classes
4348
+ hostExports: getHostExportsInternal(),
4349
+ // Logger for structured logging (respects CLI's log level/file config)
4350
+ logger: this.logger
4268
4351
  };
4269
4352
  let rawResult;
4270
4353
  if (timeoutMs && timeoutMs > 0) {
@@ -4434,27 +4517,6 @@ var init_executor = __esm({
4434
4517
  async executeAll(calls) {
4435
4518
  return Promise.all(calls.map((call) => this.execute(call)));
4436
4519
  }
4437
- /**
4438
- * Deep equality check for objects/arrays.
4439
- * Used to detect if parameters were modified by an interceptor.
4440
- */
4441
- deepEquals(a, b) {
4442
- if (a === b) return true;
4443
- if (a === null || b === null) return a === b;
4444
- if (typeof a !== typeof b) return false;
4445
- if (typeof a !== "object") return a === b;
4446
- if (Array.isArray(a) !== Array.isArray(b)) return false;
4447
- if (Array.isArray(a) && Array.isArray(b)) {
4448
- if (a.length !== b.length) return false;
4449
- return a.every((val, i) => this.deepEquals(val, b[i]));
4450
- }
4451
- const aObj = a;
4452
- const bObj = b;
4453
- const aKeys = Object.keys(aObj);
4454
- const bKeys = Object.keys(bObj);
4455
- if (aKeys.length !== bKeys.length) return false;
4456
- return aKeys.every((key) => this.deepEquals(aObj[key], bObj[key]));
4457
- }
4458
4520
  };
4459
4521
  }
4460
4522
  });
@@ -4492,6 +4554,11 @@ var init_stream_processor = __esm({
4492
4554
  inFlightExecutions = /* @__PURE__ */ new Map();
4493
4555
  /** Queue of completed gadget results ready to be yielded (for real-time streaming) */
4494
4556
  completedResultsQueue = [];
4557
+ // Cross-iteration dependency tracking
4558
+ /** Invocation IDs completed in previous iterations (read-only reference from Agent) */
4559
+ priorCompletedInvocations;
4560
+ /** Invocation IDs that failed in previous iterations (read-only reference from Agent) */
4561
+ priorFailedInvocations;
4495
4562
  constructor(options) {
4496
4563
  this.iteration = options.iteration;
4497
4564
  this.registry = options.registry;
@@ -4500,6 +4567,8 @@ var init_stream_processor = __esm({
4500
4567
  this.tree = options.tree;
4501
4568
  this.parentNodeId = options.parentNodeId ?? null;
4502
4569
  this.baseDepth = options.baseDepth ?? 0;
4570
+ this.priorCompletedInvocations = options.priorCompletedInvocations ?? /* @__PURE__ */ new Set();
4571
+ this.priorFailedInvocations = options.priorFailedInvocations ?? /* @__PURE__ */ new Set();
4503
4572
  this.parser = new GadgetCallParser({
4504
4573
  startPrefix: options.gadgetStartPrefix,
4505
4574
  endPrefix: options.gadgetEndPrefix,
@@ -4698,62 +4767,11 @@ var init_stream_processor = __esm({
4698
4767
  }
4699
4768
  return [{ type: "text", content }];
4700
4769
  }
4701
- /**
4702
- * Process a gadget call through the full lifecycle, handling dependencies.
4703
- *
4704
- * Gadgets without dependencies (or with all dependencies satisfied) execute immediately.
4705
- * Gadgets with unsatisfied dependencies are queued for later execution.
4706
- * After each execution, pending gadgets are checked to see if they can now run.
4707
- */
4708
- async processGadgetCall(call) {
4709
- const events = [];
4710
- events.push({ type: "gadget_call", call });
4711
- if (call.dependencies.length > 0) {
4712
- if (call.dependencies.includes(call.invocationId)) {
4713
- this.logger.warn("Gadget has self-referential dependency (depends on itself)", {
4714
- gadgetName: call.gadgetName,
4715
- invocationId: call.invocationId
4716
- });
4717
- this.failedInvocations.add(call.invocationId);
4718
- const skipEvent = {
4719
- type: "gadget_skipped",
4720
- gadgetName: call.gadgetName,
4721
- invocationId: call.invocationId,
4722
- parameters: call.parameters ?? {},
4723
- failedDependency: call.invocationId,
4724
- failedDependencyError: `Gadget "${call.invocationId}" cannot depend on itself (self-referential dependency)`
4725
- };
4726
- events.push(skipEvent);
4727
- return events;
4728
- }
4729
- const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
4730
- if (failedDep) {
4731
- const skipEvents = await this.handleFailedDependency(call, failedDep);
4732
- events.push(...skipEvents);
4733
- return events;
4734
- }
4735
- const unsatisfied = call.dependencies.filter((dep) => !this.completedResults.has(dep));
4736
- if (unsatisfied.length > 0) {
4737
- this.logger.debug("Queueing gadget for later - waiting on dependencies", {
4738
- gadgetName: call.gadgetName,
4739
- invocationId: call.invocationId,
4740
- waitingOn: unsatisfied
4741
- });
4742
- this.gadgetsAwaitingDependencies.set(call.invocationId, call);
4743
- return events;
4744
- }
4745
- }
4746
- const executeEvents = await this.executeGadgetWithHooks(call);
4747
- events.push(...executeEvents);
4748
- const triggeredEvents = await this.processPendingGadgets();
4749
- events.push(...triggeredEvents);
4750
- return events;
4751
- }
4752
4770
  /**
4753
4771
  * Process a gadget call, yielding events in real-time.
4754
4772
  *
4755
- * Key difference from processGadgetCall: yields gadget_call event IMMEDIATELY
4756
- * when parsed (before execution), enabling real-time UI feedback.
4773
+ * Yields gadget_call event IMMEDIATELY when parsed (before execution),
4774
+ * enabling real-time UI feedback.
4757
4775
  */
4758
4776
  async *processGadgetCallGenerator(call) {
4759
4777
  yield { type: "gadget_call", call };
@@ -4784,7 +4802,9 @@ var init_stream_processor = __esm({
4784
4802
  yield skipEvent;
4785
4803
  return;
4786
4804
  }
4787
- const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
4805
+ const failedDep = call.dependencies.find(
4806
+ (dep) => this.failedInvocations.has(dep) || this.priorFailedInvocations.has(dep)
4807
+ );
4788
4808
  if (failedDep) {
4789
4809
  const skipEvents = await this.handleFailedDependency(call, failedDep);
4790
4810
  for (const evt of skipEvents) {
@@ -4792,7 +4812,9 @@ var init_stream_processor = __esm({
4792
4812
  }
4793
4813
  return;
4794
4814
  }
4795
- const unsatisfied = call.dependencies.filter((dep) => !this.completedResults.has(dep));
4815
+ const unsatisfied = call.dependencies.filter(
4816
+ (dep) => !this.completedResults.has(dep) && !this.priorCompletedInvocations.has(dep)
4817
+ );
4796
4818
  if (unsatisfied.length > 0) {
4797
4819
  this.logger.debug("Queueing gadget for later - waiting on dependencies", {
4798
4820
  gadgetName: call.gadgetName,
@@ -4814,143 +4836,9 @@ var init_stream_processor = __esm({
4814
4836
  this.inFlightExecutions.set(call.invocationId, executionPromise);
4815
4837
  }
4816
4838
  /**
4817
- * Execute a gadget through the full hook lifecycle.
4818
- * This is the core execution logic, extracted from processGadgetCall.
4819
- * @deprecated Use executeGadgetGenerator for real-time streaming
4820
- */
4821
- async executeGadgetWithHooks(call) {
4822
- const events = [];
4823
- if (call.parseError) {
4824
- this.logger.warn("Gadget has parse error", {
4825
- gadgetName: call.gadgetName,
4826
- error: call.parseError,
4827
- rawParameters: call.parametersRaw
4828
- });
4829
- }
4830
- let parameters = call.parameters ?? {};
4831
- if (this.hooks.interceptors?.interceptGadgetParameters) {
4832
- const context = {
4833
- iteration: this.iteration,
4834
- gadgetName: call.gadgetName,
4835
- invocationId: call.invocationId,
4836
- logger: this.logger
4837
- };
4838
- parameters = this.hooks.interceptors.interceptGadgetParameters(parameters, context);
4839
- }
4840
- call.parameters = parameters;
4841
- let shouldSkip = false;
4842
- let syntheticResult;
4843
- if (this.hooks.controllers?.beforeGadgetExecution) {
4844
- const context = {
4845
- iteration: this.iteration,
4846
- gadgetName: call.gadgetName,
4847
- invocationId: call.invocationId,
4848
- parameters,
4849
- logger: this.logger
4850
- };
4851
- const action = await this.hooks.controllers.beforeGadgetExecution(context);
4852
- validateBeforeGadgetExecutionAction(action);
4853
- if (action.action === "skip") {
4854
- shouldSkip = true;
4855
- syntheticResult = action.syntheticResult;
4856
- this.logger.info("Controller skipped gadget execution", {
4857
- gadgetName: call.gadgetName
4858
- });
4859
- }
4860
- }
4861
- const startObservers = [];
4862
- if (this.hooks.observers?.onGadgetExecutionStart) {
4863
- startObservers.push(async () => {
4864
- const context = {
4865
- iteration: this.iteration,
4866
- gadgetName: call.gadgetName,
4867
- invocationId: call.invocationId,
4868
- parameters,
4869
- logger: this.logger
4870
- };
4871
- await this.hooks.observers?.onGadgetExecutionStart?.(context);
4872
- });
4873
- }
4874
- await this.runObserversInParallel(startObservers);
4875
- let result;
4876
- if (shouldSkip) {
4877
- result = {
4878
- gadgetName: call.gadgetName,
4879
- invocationId: call.invocationId,
4880
- parameters,
4881
- result: syntheticResult ?? "Execution skipped",
4882
- executionTimeMs: 0
4883
- };
4884
- } else {
4885
- result = await this.executor.execute(call);
4886
- }
4887
- const originalResult = result.result;
4888
- if (result.result && this.hooks.interceptors?.interceptGadgetResult) {
4889
- const context = {
4890
- iteration: this.iteration,
4891
- gadgetName: result.gadgetName,
4892
- invocationId: result.invocationId,
4893
- parameters,
4894
- executionTimeMs: result.executionTimeMs,
4895
- logger: this.logger
4896
- };
4897
- result.result = this.hooks.interceptors.interceptGadgetResult(result.result, context);
4898
- }
4899
- if (this.hooks.controllers?.afterGadgetExecution) {
4900
- const context = {
4901
- iteration: this.iteration,
4902
- gadgetName: result.gadgetName,
4903
- invocationId: result.invocationId,
4904
- parameters,
4905
- result: result.result,
4906
- error: result.error,
4907
- executionTimeMs: result.executionTimeMs,
4908
- logger: this.logger
4909
- };
4910
- const action = await this.hooks.controllers.afterGadgetExecution(context);
4911
- validateAfterGadgetExecutionAction(action);
4912
- if (action.action === "recover" && result.error) {
4913
- this.logger.info("Controller recovered from gadget error", {
4914
- gadgetName: result.gadgetName,
4915
- originalError: result.error
4916
- });
4917
- result = {
4918
- ...result,
4919
- error: void 0,
4920
- result: action.fallbackResult
4921
- };
4922
- }
4923
- }
4924
- const completeObservers = [];
4925
- if (this.hooks.observers?.onGadgetExecutionComplete) {
4926
- completeObservers.push(async () => {
4927
- const context = {
4928
- iteration: this.iteration,
4929
- gadgetName: result.gadgetName,
4930
- invocationId: result.invocationId,
4931
- parameters,
4932
- originalResult,
4933
- finalResult: result.result,
4934
- error: result.error,
4935
- executionTimeMs: result.executionTimeMs,
4936
- breaksLoop: result.breaksLoop,
4937
- cost: result.cost,
4938
- logger: this.logger
4939
- };
4940
- await this.hooks.observers?.onGadgetExecutionComplete?.(context);
4941
- });
4942
- }
4943
- await this.runObserversInParallel(completeObservers);
4944
- this.completedResults.set(result.invocationId, result);
4945
- if (result.error) {
4946
- this.failedInvocations.add(result.invocationId);
4947
- }
4948
- events.push({ type: "gadget_result", result });
4949
- return events;
4950
- }
4951
- /**
4952
- * Execute a gadget and yield the result event.
4953
- * Generator version that yields gadget_result immediately when execution completes.
4839
+ * Execute a gadget through the full hook lifecycle and yield events.
4840
+ * Handles parameter interception, before/after controllers, observers,
4841
+ * execution, result interception, and tree tracking.
4954
4842
  */
4955
4843
  async *executeGadgetGenerator(call) {
4956
4844
  if (call.parseError) {
@@ -5216,8 +5104,9 @@ var init_stream_processor = __esm({
5216
5104
  invocationId: call.invocationId,
5217
5105
  failedDependency: failedDep
5218
5106
  });
5219
- const executeEvents = await this.executeGadgetWithHooks(call);
5220
- events.push(...executeEvents);
5107
+ for await (const evt of this.executeGadgetGenerator(call)) {
5108
+ events.push(evt);
5109
+ }
5221
5110
  } else if (action.action === "use_fallback") {
5222
5111
  const fallbackResult = {
5223
5112
  gadgetName: call.gadgetName,
@@ -5238,90 +5127,9 @@ var init_stream_processor = __esm({
5238
5127
  }
5239
5128
  /**
5240
5129
  * Process pending gadgets whose dependencies are now satisfied.
5241
- * Executes ready gadgets in parallel and continues until no more can be triggered.
5242
- */
5243
- async processPendingGadgets() {
5244
- const events = [];
5245
- let progress = true;
5246
- while (progress && this.gadgetsAwaitingDependencies.size > 0) {
5247
- progress = false;
5248
- const readyToExecute = [];
5249
- const readyToSkip = [];
5250
- for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
5251
- const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
5252
- if (failedDep) {
5253
- readyToSkip.push({ call, failedDep });
5254
- continue;
5255
- }
5256
- const allSatisfied = call.dependencies.every((dep) => this.completedResults.has(dep));
5257
- if (allSatisfied) {
5258
- readyToExecute.push(call);
5259
- }
5260
- }
5261
- for (const { call, failedDep } of readyToSkip) {
5262
- this.gadgetsAwaitingDependencies.delete(call.invocationId);
5263
- const skipEvents = await this.handleFailedDependency(call, failedDep);
5264
- events.push(...skipEvents);
5265
- progress = true;
5266
- }
5267
- if (readyToExecute.length > 0) {
5268
- this.logger.debug("Executing ready gadgets in parallel", {
5269
- count: readyToExecute.length,
5270
- invocationIds: readyToExecute.map((c) => c.invocationId)
5271
- });
5272
- for (const call of readyToExecute) {
5273
- this.gadgetsAwaitingDependencies.delete(call.invocationId);
5274
- }
5275
- const executePromises = readyToExecute.map((call) => this.executeGadgetWithHooks(call));
5276
- const results = await Promise.all(executePromises);
5277
- for (const executeEvents of results) {
5278
- events.push(...executeEvents);
5279
- }
5280
- progress = true;
5281
- }
5282
- }
5283
- if (this.gadgetsAwaitingDependencies.size > 0) {
5284
- const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
5285
- for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
5286
- const missingDeps = call.dependencies.filter((dep) => !this.completedResults.has(dep));
5287
- const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
5288
- const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
5289
- let errorMessage;
5290
- let logLevel = "warn";
5291
- if (circularDeps.length > 0 && trulyMissingDeps.length > 0) {
5292
- errorMessage = `Dependencies unresolvable: circular=[${circularDeps.join(", ")}], missing=[${trulyMissingDeps.join(", ")}]`;
5293
- logLevel = "error";
5294
- } else if (circularDeps.length > 0) {
5295
- errorMessage = `Circular dependency detected: "${invocationId}" depends on "${circularDeps[0]}" which also depends on "${invocationId}" (directly or indirectly)`;
5296
- } else {
5297
- errorMessage = `Dependency "${missingDeps[0]}" was never executed - check that the invocation ID exists and is spelled correctly`;
5298
- }
5299
- this.logger[logLevel]("Gadget has unresolvable dependencies", {
5300
- gadgetName: call.gadgetName,
5301
- invocationId,
5302
- circularDependencies: circularDeps,
5303
- missingDependencies: trulyMissingDeps
5304
- });
5305
- this.failedInvocations.add(invocationId);
5306
- const skipEvent = {
5307
- type: "gadget_skipped",
5308
- gadgetName: call.gadgetName,
5309
- invocationId,
5310
- parameters: call.parameters ?? {},
5311
- failedDependency: missingDeps[0],
5312
- failedDependencyError: errorMessage
5313
- };
5314
- events.push(skipEvent);
5315
- }
5316
- this.gadgetsAwaitingDependencies.clear();
5317
- }
5318
- return events;
5319
- }
5320
- /**
5321
- * Process pending gadgets, yielding events in real-time.
5322
- * Generator version that yields events as gadgets complete.
5130
+ * Yields events in real-time as gadgets complete.
5323
5131
  *
5324
- * Note: Gadgets are still executed in parallel for efficiency,
5132
+ * Gadgets are executed in parallel for efficiency,
5325
5133
  * but results are yielded as they become available.
5326
5134
  */
5327
5135
  async *processPendingGadgetsGenerator() {
@@ -5331,12 +5139,16 @@ var init_stream_processor = __esm({
5331
5139
  const readyToExecute = [];
5332
5140
  const readyToSkip = [];
5333
5141
  for (const [_invocationId, call] of this.gadgetsAwaitingDependencies) {
5334
- const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
5142
+ const failedDep = call.dependencies.find(
5143
+ (dep) => this.failedInvocations.has(dep) || this.priorFailedInvocations.has(dep)
5144
+ );
5335
5145
  if (failedDep) {
5336
5146
  readyToSkip.push({ call, failedDep });
5337
5147
  continue;
5338
5148
  }
5339
- const allSatisfied = call.dependencies.every((dep) => this.completedResults.has(dep));
5149
+ const allSatisfied = call.dependencies.every(
5150
+ (dep) => this.completedResults.has(dep) || this.priorCompletedInvocations.has(dep)
5151
+ );
5340
5152
  if (allSatisfied) {
5341
5153
  readyToExecute.push(call);
5342
5154
  }
@@ -5377,7 +5189,9 @@ var init_stream_processor = __esm({
5377
5189
  if (this.gadgetsAwaitingDependencies.size > 0) {
5378
5190
  const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
5379
5191
  for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
5380
- const missingDeps = call.dependencies.filter((dep) => !this.completedResults.has(dep));
5192
+ const missingDeps = call.dependencies.filter(
5193
+ (dep) => !this.completedResults.has(dep) && !this.priorCompletedInvocations.has(dep)
5194
+ );
5381
5195
  const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
5382
5196
  const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
5383
5197
  let errorMessage;
@@ -5431,10 +5245,27 @@ var init_stream_processor = __esm({
5431
5245
  */
5432
5246
  async runObserversInParallel(observers) {
5433
5247
  if (observers.length === 0) return;
5434
- const results = await Promise.allSettled(
5248
+ await Promise.allSettled(
5435
5249
  observers.map((observer) => this.safeObserve(observer))
5436
5250
  );
5437
5251
  }
5252
+ // ==========================================================================
5253
+ // Public accessors for cross-iteration dependency tracking
5254
+ // ==========================================================================
5255
+ /**
5256
+ * Get all invocation IDs that completed successfully in this iteration.
5257
+ * Used by Agent to accumulate completed IDs across iterations.
5258
+ */
5259
+ getCompletedInvocationIds() {
5260
+ return new Set(this.completedResults.keys());
5261
+ }
5262
+ /**
5263
+ * Get all invocation IDs that failed in this iteration.
5264
+ * Used by Agent to accumulate failed IDs across iterations.
5265
+ */
5266
+ getFailedInvocationIds() {
5267
+ return new Set(this.failedInvocations);
5268
+ }
5438
5269
  };
5439
5270
  }
5440
5271
  });
@@ -5497,6 +5328,9 @@ var init_agent = __esm({
5497
5328
  onSubagentEvent;
5498
5329
  // Counter for generating synthetic invocation IDs for wrapped text content
5499
5330
  syntheticInvocationCounter = 0;
5331
+ // Cross-iteration dependency tracking - allows gadgets to depend on results from prior iterations
5332
+ completedInvocationIds = /* @__PURE__ */ new Set();
5333
+ failedInvocationIds = /* @__PURE__ */ new Set();
5500
5334
  // Execution Tree - first-class model for nested subagent support
5501
5335
  tree;
5502
5336
  parentNodeId;
@@ -5806,96 +5640,22 @@ var init_agent = __esm({
5806
5640
  maxIterations: this.maxIterations
5807
5641
  });
5808
5642
  while (currentIteration < this.maxIterations) {
5809
- if (this.signal?.aborted) {
5810
- this.logger.info("Agent loop terminated by abort signal", {
5811
- iteration: currentIteration,
5812
- reason: this.signal.reason
5813
- });
5814
- await this.safeObserve(async () => {
5815
- if (this.hooks.observers?.onAbort) {
5816
- const context = {
5817
- iteration: currentIteration,
5818
- reason: this.signal?.reason,
5819
- logger: this.logger
5820
- };
5821
- await this.hooks.observers.onAbort(context);
5822
- }
5823
- });
5643
+ if (await this.checkAbortAndNotify(currentIteration)) {
5824
5644
  return;
5825
5645
  }
5826
5646
  this.logger.debug("Starting iteration", { iteration: currentIteration });
5827
5647
  try {
5828
- if (this.compactionManager) {
5829
- const compactionEvent = await this.compactionManager.checkAndCompact(
5830
- this.conversation,
5831
- currentIteration
5832
- );
5833
- if (compactionEvent) {
5834
- this.logger.info("Context compacted", {
5835
- strategy: compactionEvent.strategy,
5836
- tokensBefore: compactionEvent.tokensBefore,
5837
- tokensAfter: compactionEvent.tokensAfter
5838
- });
5839
- yield { type: "compaction", event: compactionEvent };
5840
- await this.safeObserve(async () => {
5841
- if (this.hooks.observers?.onCompaction) {
5842
- await this.hooks.observers.onCompaction({
5843
- iteration: currentIteration,
5844
- event: compactionEvent,
5845
- // biome-ignore lint/style/noNonNullAssertion: compactionManager exists if compactionEvent is truthy
5846
- stats: this.compactionManager.getStats(),
5847
- logger: this.logger
5848
- });
5849
- }
5850
- });
5851
- }
5648
+ const compactionEvent = await this.checkAndPerformCompaction(currentIteration);
5649
+ if (compactionEvent) {
5650
+ yield compactionEvent;
5852
5651
  }
5853
- let llmOptions = {
5854
- model: this.model,
5855
- messages: this.conversation.getMessages(),
5856
- temperature: this.temperature,
5857
- maxTokens: this.defaultMaxTokens,
5858
- signal: this.signal
5859
- };
5860
- await this.safeObserve(async () => {
5861
- if (this.hooks.observers?.onLLMCallStart) {
5862
- const context = {
5863
- iteration: currentIteration,
5864
- options: llmOptions,
5865
- logger: this.logger
5866
- };
5867
- await this.hooks.observers.onLLMCallStart(context);
5868
- }
5869
- });
5870
- if (this.hooks.controllers?.beforeLLMCall) {
5871
- const context = {
5872
- iteration: currentIteration,
5873
- maxIterations: this.maxIterations,
5874
- options: llmOptions,
5875
- logger: this.logger
5876
- };
5877
- const action = await this.hooks.controllers.beforeLLMCall(context);
5878
- validateBeforeLLMCallAction(action);
5879
- if (action.action === "skip") {
5880
- this.logger.info("Controller skipped LLM call, using synthetic response");
5881
- this.conversation.addAssistantMessage(action.syntheticResponse);
5882
- yield { type: "text", content: action.syntheticResponse };
5883
- break;
5884
- } else if (action.action === "proceed" && action.modifiedOptions) {
5885
- llmOptions = { ...llmOptions, ...action.modifiedOptions };
5886
- }
5652
+ const prepared = await this.prepareLLMCall(currentIteration);
5653
+ const llmOptions = prepared.options;
5654
+ if (prepared.skipWithSynthetic !== void 0) {
5655
+ this.conversation.addAssistantMessage(prepared.skipWithSynthetic);
5656
+ yield { type: "text", content: prepared.skipWithSynthetic };
5657
+ break;
5887
5658
  }
5888
- await this.safeObserve(async () => {
5889
- if (this.hooks.observers?.onLLMCallReady) {
5890
- const context = {
5891
- iteration: currentIteration,
5892
- maxIterations: this.maxIterations,
5893
- options: llmOptions,
5894
- logger: this.logger
5895
- };
5896
- await this.hooks.observers.onLLMCallReady(context);
5897
- }
5898
- });
5899
5659
  this.logger.info("Calling LLM", { model: this.model });
5900
5660
  this.logger.silly("LLM request details", {
5901
5661
  model: llmOptions.model,
@@ -5931,7 +5691,10 @@ var init_agent = __esm({
5931
5691
  tree: this.tree,
5932
5692
  parentNodeId: currentLLMNodeId,
5933
5693
  // Gadgets are children of this LLM call
5934
- baseDepth: this.baseDepth
5694
+ baseDepth: this.baseDepth,
5695
+ // Cross-iteration dependency tracking
5696
+ priorCompletedInvocations: this.completedInvocationIds,
5697
+ priorFailedInvocations: this.failedInvocationIds
5935
5698
  });
5936
5699
  let streamMetadata = null;
5937
5700
  let gadgetCallCount = 0;
@@ -5954,6 +5717,12 @@ var init_agent = __esm({
5954
5717
  if (!streamMetadata) {
5955
5718
  throw new Error("Stream processing completed without metadata event");
5956
5719
  }
5720
+ for (const id of processor.getCompletedInvocationIds()) {
5721
+ this.completedInvocationIds.add(id);
5722
+ }
5723
+ for (const id of processor.getFailedInvocationIds()) {
5724
+ this.failedInvocationIds.add(id);
5725
+ }
5957
5726
  const result = streamMetadata;
5958
5727
  this.logger.info("LLM response completed", {
5959
5728
  finishReason: result.finishReason,
@@ -5977,81 +5746,21 @@ var init_agent = __esm({
5977
5746
  await this.hooks.observers.onLLMCallComplete(context);
5978
5747
  }
5979
5748
  });
5980
- this.tree.completeLLMCall(currentLLMNodeId, {
5981
- response: result.rawResponse,
5982
- usage: result.usage,
5983
- finishReason: result.finishReason
5984
- });
5985
- let finalMessage = result.finalMessage;
5986
- if (this.hooks.controllers?.afterLLMCall) {
5987
- const context = {
5988
- iteration: currentIteration,
5989
- maxIterations: this.maxIterations,
5990
- options: llmOptions,
5991
- finishReason: result.finishReason,
5992
- usage: result.usage,
5993
- finalMessage: result.finalMessage,
5994
- gadgetCallCount,
5995
- logger: this.logger
5996
- };
5997
- const action = await this.hooks.controllers.afterLLMCall(context);
5998
- validateAfterLLMCallAction(action);
5999
- if (action.action === "modify_and_continue" || action.action === "append_and_modify") {
6000
- finalMessage = action.modifiedMessage;
6001
- }
6002
- if (action.action === "append_messages" || action.action === "append_and_modify") {
6003
- for (const msg of action.messages) {
6004
- if (msg.role === "user") {
6005
- this.conversation.addUserMessage(msg.content);
6006
- } else if (msg.role === "assistant") {
6007
- this.conversation.addAssistantMessage(extractMessageText(msg.content));
6008
- } else if (msg.role === "system") {
6009
- this.conversation.addUserMessage(`[System] ${extractMessageText(msg.content)}`);
6010
- }
6011
- }
6012
- }
6013
- }
6014
- if (result.didExecuteGadgets) {
6015
- if (this.textWithGadgetsHandler) {
6016
- const textContent = textOutputs.join("");
6017
- if (textContent.trim()) {
6018
- const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
6019
- const syntheticId = `gc_text_${++this.syntheticInvocationCounter}`;
6020
- this.conversation.addGadgetCallResult(
6021
- gadgetName,
6022
- parameterMapping(textContent),
6023
- resultMapping ? resultMapping(textContent) : textContent,
6024
- syntheticId
6025
- );
6026
- }
6027
- }
6028
- for (const output of gadgetResults) {
6029
- if (output.type === "gadget_result") {
6030
- const gadgetResult = output.result;
6031
- this.conversation.addGadgetCallResult(
6032
- gadgetResult.gadgetName,
6033
- gadgetResult.parameters,
6034
- gadgetResult.error ?? gadgetResult.result ?? "",
6035
- gadgetResult.invocationId,
6036
- gadgetResult.media,
6037
- gadgetResult.mediaIds
6038
- );
6039
- }
6040
- }
6041
- } else {
6042
- if (finalMessage.trim()) {
6043
- const syntheticId = `gc_tell_${++this.syntheticInvocationCounter}`;
6044
- this.conversation.addGadgetCallResult(
6045
- "TellUser",
6046
- { message: finalMessage, done: false, type: "info" },
6047
- `\u2139\uFE0F ${finalMessage}`,
6048
- syntheticId
6049
- );
6050
- }
6051
- const shouldBreak = await this.handleTextOnlyResponse(finalMessage);
6052
- if (shouldBreak) {
6053
- break;
6054
- }
5749
+ this.completeLLMCallInTree(currentLLMNodeId, result);
5750
+ const finalMessage = await this.processAfterLLMCallController(
5751
+ currentIteration,
5752
+ llmOptions,
5753
+ result,
5754
+ gadgetCallCount
5755
+ );
5756
+ const shouldBreakFromTextOnly = await this.updateConversationWithResults(
5757
+ result.didExecuteGadgets,
5758
+ textOutputs,
5759
+ gadgetResults,
5760
+ finalMessage
5761
+ );
5762
+ if (shouldBreakFromTextOnly) {
5763
+ break;
6055
5764
  }
6056
5765
  if (result.shouldBreakLoop) {
6057
5766
  this.logger.info("Loop terminated by gadget or processor");
@@ -6204,6 +5913,210 @@ var init_agent = __esm({
6204
5913
  }
6205
5914
  };
6206
5915
  }
5916
+ // ==========================================================================
5917
+ // Agent Loop Helper Methods (extracted from run() for readability)
5918
+ // ==========================================================================
5919
+ /**
5920
+ * Check abort signal and notify observers if aborted.
5921
+ * @returns true if agent should terminate
5922
+ */
5923
+ async checkAbortAndNotify(iteration) {
5924
+ if (!this.signal?.aborted) return false;
5925
+ this.logger.info("Agent loop terminated by abort signal", {
5926
+ iteration,
5927
+ reason: this.signal.reason
5928
+ });
5929
+ await this.safeObserve(async () => {
5930
+ if (this.hooks.observers?.onAbort) {
5931
+ const context = {
5932
+ iteration,
5933
+ reason: this.signal?.reason,
5934
+ logger: this.logger
5935
+ };
5936
+ await this.hooks.observers.onAbort(context);
5937
+ }
5938
+ });
5939
+ return true;
5940
+ }
5941
+ /**
5942
+ * Check and perform context compaction if needed.
5943
+ * @returns compaction stream event if compaction occurred, null otherwise
5944
+ */
5945
+ async checkAndPerformCompaction(iteration) {
5946
+ if (!this.compactionManager) return null;
5947
+ const compactionEvent = await this.compactionManager.checkAndCompact(
5948
+ this.conversation,
5949
+ iteration
5950
+ );
5951
+ if (!compactionEvent) return null;
5952
+ this.logger.info("Context compacted", {
5953
+ strategy: compactionEvent.strategy,
5954
+ tokensBefore: compactionEvent.tokensBefore,
5955
+ tokensAfter: compactionEvent.tokensAfter
5956
+ });
5957
+ await this.safeObserve(async () => {
5958
+ if (this.hooks.observers?.onCompaction) {
5959
+ await this.hooks.observers.onCompaction({
5960
+ iteration,
5961
+ event: compactionEvent,
5962
+ // biome-ignore lint/style/noNonNullAssertion: compactionManager exists if compactionEvent is truthy
5963
+ stats: this.compactionManager.getStats(),
5964
+ logger: this.logger
5965
+ });
5966
+ }
5967
+ });
5968
+ return { type: "compaction", event: compactionEvent };
5969
+ }
5970
+ /**
5971
+ * Prepare LLM call options and process beforeLLMCall controller.
5972
+ * @returns options and optional skipWithSynthetic response if controller wants to skip
5973
+ */
5974
+ async prepareLLMCall(iteration) {
5975
+ let llmOptions = {
5976
+ model: this.model,
5977
+ messages: this.conversation.getMessages(),
5978
+ temperature: this.temperature,
5979
+ maxTokens: this.defaultMaxTokens,
5980
+ signal: this.signal
5981
+ };
5982
+ await this.safeObserve(async () => {
5983
+ if (this.hooks.observers?.onLLMCallStart) {
5984
+ const context = {
5985
+ iteration,
5986
+ options: llmOptions,
5987
+ logger: this.logger
5988
+ };
5989
+ await this.hooks.observers.onLLMCallStart(context);
5990
+ }
5991
+ });
5992
+ if (this.hooks.controllers?.beforeLLMCall) {
5993
+ const context = {
5994
+ iteration,
5995
+ maxIterations: this.maxIterations,
5996
+ options: llmOptions,
5997
+ logger: this.logger
5998
+ };
5999
+ const action = await this.hooks.controllers.beforeLLMCall(context);
6000
+ validateBeforeLLMCallAction(action);
6001
+ if (action.action === "skip") {
6002
+ this.logger.info("Controller skipped LLM call, using synthetic response");
6003
+ return { options: llmOptions, skipWithSynthetic: action.syntheticResponse };
6004
+ } else if (action.action === "proceed" && action.modifiedOptions) {
6005
+ llmOptions = { ...llmOptions, ...action.modifiedOptions };
6006
+ }
6007
+ }
6008
+ await this.safeObserve(async () => {
6009
+ if (this.hooks.observers?.onLLMCallReady) {
6010
+ const context = {
6011
+ iteration,
6012
+ maxIterations: this.maxIterations,
6013
+ options: llmOptions,
6014
+ logger: this.logger
6015
+ };
6016
+ await this.hooks.observers.onLLMCallReady(context);
6017
+ }
6018
+ });
6019
+ return { options: llmOptions };
6020
+ }
6021
+ /**
6022
+ * Calculate cost and complete LLM call in execution tree.
6023
+ */
6024
+ completeLLMCallInTree(nodeId, result) {
6025
+ const llmCost = this.client.modelRegistry?.estimateCost?.(
6026
+ this.model,
6027
+ result.usage?.inputTokens ?? 0,
6028
+ result.usage?.outputTokens ?? 0,
6029
+ result.usage?.cachedInputTokens ?? 0,
6030
+ result.usage?.cacheCreationInputTokens ?? 0
6031
+ )?.totalCost;
6032
+ this.tree.completeLLMCall(nodeId, {
6033
+ response: result.rawResponse,
6034
+ usage: result.usage,
6035
+ finishReason: result.finishReason,
6036
+ cost: llmCost
6037
+ });
6038
+ }
6039
+ /**
6040
+ * Process afterLLMCall controller and return modified final message.
6041
+ */
6042
+ async processAfterLLMCallController(iteration, llmOptions, result, gadgetCallCount) {
6043
+ let finalMessage = result.finalMessage;
6044
+ if (!this.hooks.controllers?.afterLLMCall) {
6045
+ return finalMessage;
6046
+ }
6047
+ const context = {
6048
+ iteration,
6049
+ maxIterations: this.maxIterations,
6050
+ options: llmOptions,
6051
+ finishReason: result.finishReason,
6052
+ usage: result.usage,
6053
+ finalMessage: result.finalMessage,
6054
+ gadgetCallCount,
6055
+ logger: this.logger
6056
+ };
6057
+ const action = await this.hooks.controllers.afterLLMCall(context);
6058
+ validateAfterLLMCallAction(action);
6059
+ if (action.action === "modify_and_continue" || action.action === "append_and_modify") {
6060
+ finalMessage = action.modifiedMessage;
6061
+ }
6062
+ if (action.action === "append_messages" || action.action === "append_and_modify") {
6063
+ for (const msg of action.messages) {
6064
+ if (msg.role === "user") {
6065
+ this.conversation.addUserMessage(msg.content);
6066
+ } else if (msg.role === "assistant") {
6067
+ this.conversation.addAssistantMessage(extractMessageText(msg.content));
6068
+ } else if (msg.role === "system") {
6069
+ this.conversation.addUserMessage(`[System] ${extractMessageText(msg.content)}`);
6070
+ }
6071
+ }
6072
+ }
6073
+ return finalMessage;
6074
+ }
6075
+ /**
6076
+ * Update conversation history with gadget results or text-only response.
6077
+ * @returns true if loop should break (text-only handler requested termination)
6078
+ */
6079
+ async updateConversationWithResults(didExecuteGadgets, textOutputs, gadgetResults, finalMessage) {
6080
+ if (didExecuteGadgets) {
6081
+ if (this.textWithGadgetsHandler) {
6082
+ const textContent = textOutputs.join("");
6083
+ if (textContent.trim()) {
6084
+ const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
6085
+ const syntheticId = `gc_text_${++this.syntheticInvocationCounter}`;
6086
+ this.conversation.addGadgetCallResult(
6087
+ gadgetName,
6088
+ parameterMapping(textContent),
6089
+ resultMapping ? resultMapping(textContent) : textContent,
6090
+ syntheticId
6091
+ );
6092
+ }
6093
+ }
6094
+ for (const output of gadgetResults) {
6095
+ if (output.type === "gadget_result") {
6096
+ const gadgetResult = output.result;
6097
+ this.conversation.addGadgetCallResult(
6098
+ gadgetResult.gadgetName,
6099
+ gadgetResult.parameters,
6100
+ gadgetResult.error ?? gadgetResult.result ?? "",
6101
+ gadgetResult.invocationId,
6102
+ gadgetResult.media,
6103
+ gadgetResult.mediaIds
6104
+ );
6105
+ }
6106
+ }
6107
+ return false;
6108
+ }
6109
+ if (finalMessage.trim()) {
6110
+ const syntheticId = `gc_tell_${++this.syntheticInvocationCounter}`;
6111
+ this.conversation.addGadgetCallResult(
6112
+ "TellUser",
6113
+ { message: finalMessage, done: false, type: "info" },
6114
+ `\u2139\uFE0F ${finalMessage}`,
6115
+ syntheticId
6116
+ );
6117
+ }
6118
+ return await this.handleTextOnlyResponse(finalMessage);
6119
+ }
6207
6120
  /**
6208
6121
  * Run agent with named event handlers (syntactic sugar).
6209
6122
  *
@@ -9736,11 +9649,13 @@ var init_model_registry = __esm({
9736
9649
  }
9737
9650
  /**
9738
9651
  * Get model specification by model ID
9739
- * @param modelId - Full model identifier (e.g., 'gpt-5', 'claude-sonnet-4-5-20250929')
9652
+ * @param modelId - Full model identifier, optionally with provider prefix
9653
+ * (e.g., 'gpt-5', 'claude-sonnet-4-5-20250929', 'anthropic:claude-sonnet-4-5')
9740
9654
  * @returns ModelSpec if found, undefined otherwise
9741
9655
  */
9742
9656
  getModelSpec(modelId) {
9743
- return this.modelSpecs.find((model) => model.modelId === modelId);
9657
+ const normalizedId = modelId.includes(":") ? modelId.split(":")[1] : modelId;
9658
+ return this.modelSpecs.find((model) => model.modelId === normalizedId);
9744
9659
  }
9745
9660
  /**
9746
9661
  * List all models, optionally filtered by provider