llmist 15.14.2 → 15.15.1

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
@@ -10625,6 +10625,7 @@ var init_builder = __esm({
10625
10625
  textWithGadgetsHandler;
10626
10626
  defaultGadgetTimeoutMs;
10627
10627
  gadgetExecutionMode;
10628
+ maxGadgetsPerResponse;
10628
10629
  gadgetOutputLimit;
10629
10630
  gadgetOutputLimitPercent;
10630
10631
  compactionConfig;
@@ -11025,6 +11026,37 @@ var init_builder = __esm({
11025
11026
  this.gadgetExecutionMode = mode;
11026
11027
  return this;
11027
11028
  }
11029
+ /**
11030
+ * Set the maximum number of gadgets to execute per LLM response.
11031
+ *
11032
+ * When the limit is reached, remaining gadgets are skipped with an informative
11033
+ * message visible to the LLM, allowing it to adjust on the next iteration.
11034
+ * Gadgets already in-flight (executing in parallel) are allowed to complete.
11035
+ *
11036
+ * @param max - Maximum gadgets per response (0 = unlimited, default)
11037
+ * @returns This builder for chaining
11038
+ * @throws {Error} If max is negative or non-integer
11039
+ *
11040
+ * @example
11041
+ * ```typescript
11042
+ * // Limit to 5 gadgets per response
11043
+ * LLMist.createAgent()
11044
+ * .withModel("sonnet")
11045
+ * .withGadgets(ReadFile, WriteFile, Search)
11046
+ * .withMaxGadgetsPerResponse(5)
11047
+ * .ask("Process these files...");
11048
+ * ```
11049
+ */
11050
+ withMaxGadgetsPerResponse(max) {
11051
+ if (max < 0) {
11052
+ throw new Error("maxGadgetsPerResponse must be a non-negative number");
11053
+ }
11054
+ if (!Number.isInteger(max)) {
11055
+ throw new Error("maxGadgetsPerResponse must be an integer");
11056
+ }
11057
+ this.maxGadgetsPerResponse = max;
11058
+ return this;
11059
+ }
11028
11060
  /**
11029
11061
  * Enable or disable gadget output limiting.
11030
11062
  *
@@ -11623,6 +11655,7 @@ ${endPrefix}`
11623
11655
  textWithGadgetsHandler: this.textWithGadgetsHandler,
11624
11656
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
11625
11657
  gadgetExecutionMode: this.gadgetExecutionMode,
11658
+ maxGadgetsPerResponse: this.maxGadgetsPerResponse,
11626
11659
  gadgetOutputLimit: this.gadgetOutputLimit,
11627
11660
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
11628
11661
  compactionConfig: this.compactionConfig,
@@ -11812,6 +11845,7 @@ ${endPrefix}`
11812
11845
  textWithGadgetsHandler: this.textWithGadgetsHandler,
11813
11846
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
11814
11847
  gadgetExecutionMode: this.gadgetExecutionMode,
11848
+ maxGadgetsPerResponse: this.maxGadgetsPerResponse,
11815
11849
  gadgetOutputLimit: this.gadgetOutputLimit,
11816
11850
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
11817
11851
  compactionConfig: this.compactionConfig,
@@ -13449,6 +13483,10 @@ var init_stream_processor = __esm({
13449
13483
  priorFailedInvocations;
13450
13484
  // Parent observer hooks for subagent visibility
13451
13485
  parentObservers;
13486
+ // Gadget limiting per response
13487
+ maxGadgetsPerResponse;
13488
+ gadgetStartedCount = 0;
13489
+ limitExceeded = false;
13452
13490
  constructor(options) {
13453
13491
  this.iteration = options.iteration;
13454
13492
  this.registry = options.registry;
@@ -13462,6 +13500,7 @@ var init_stream_processor = __esm({
13462
13500
  this.priorFailedInvocations = options.priorFailedInvocations ?? /* @__PURE__ */ new Set();
13463
13501
  this.subagentConfig = options.subagentConfig;
13464
13502
  this.parentObservers = options.parentObservers;
13503
+ this.maxGadgetsPerResponse = options.maxGadgetsPerResponse ?? 0;
13465
13504
  this.parser = new GadgetCallParser({
13466
13505
  startPrefix: options.gadgetStartPrefix,
13467
13506
  endPrefix: options.gadgetEndPrefix,
@@ -13575,6 +13614,10 @@ var init_stream_processor = __esm({
13575
13614
  }
13576
13615
  }
13577
13616
  }
13617
+ if (this.limitExceeded) {
13618
+ this.logger.info("Breaking stream loop due to gadget limit");
13619
+ break;
13620
+ }
13578
13621
  }
13579
13622
  yield { type: "llm_response_end", finishReason, usage };
13580
13623
  for (const event of this.parser.finalize()) {
@@ -13678,6 +13721,9 @@ var init_stream_processor = __esm({
13678
13721
  * enabling real-time UI feedback.
13679
13722
  */
13680
13723
  async *processGadgetCallGenerator(call) {
13724
+ if (this.limitExceeded) {
13725
+ return;
13726
+ }
13681
13727
  yield { type: "gadget_call", call };
13682
13728
  if (this.tree) {
13683
13729
  this.tree.addGadget({
@@ -13757,6 +13803,15 @@ var init_stream_processor = __esm({
13757
13803
  this.gadgetsAwaitingDependencies.set(call.invocationId, call);
13758
13804
  return;
13759
13805
  }
13806
+ const limitCheckGen2 = this.checkGadgetLimitExceeded(call);
13807
+ let limitResult2 = await limitCheckGen2.next();
13808
+ while (!limitResult2.done) {
13809
+ yield limitResult2.value;
13810
+ limitResult2 = await limitCheckGen2.next();
13811
+ }
13812
+ if (limitResult2.value === true) {
13813
+ return;
13814
+ }
13760
13815
  for await (const evt of this.executeGadgetGenerator(call)) {
13761
13816
  yield evt;
13762
13817
  }
@@ -13765,6 +13820,15 @@ var init_stream_processor = __esm({
13765
13820
  }
13766
13821
  return;
13767
13822
  }
13823
+ const limitCheckGen = this.checkGadgetLimitExceeded(call);
13824
+ let limitResult = await limitCheckGen.next();
13825
+ while (!limitResult.done) {
13826
+ yield limitResult.value;
13827
+ limitResult = await limitCheckGen.next();
13828
+ }
13829
+ if (limitResult.value === true) {
13830
+ return;
13831
+ }
13768
13832
  const limit = this.getConcurrencyLimit(call.gadgetName);
13769
13833
  const activeCount = this.activeCountByGadget.get(call.gadgetName) ?? 0;
13770
13834
  if (limit > 0 && activeCount >= limit) {
@@ -14200,6 +14264,81 @@ var init_stream_processor = __esm({
14200
14264
  }
14201
14265
  return events;
14202
14266
  }
14267
+ /**
14268
+ * Check if gadget execution should be skipped due to maxGadgetsPerResponse limit.
14269
+ * If limit is exceeded, yields skip events and returns true.
14270
+ * If execution can proceed, increments counter and returns false.
14271
+ *
14272
+ * @returns true if gadget should be skipped, false if execution can proceed
14273
+ */
14274
+ async *checkGadgetLimitExceeded(call) {
14275
+ if (this.maxGadgetsPerResponse <= 0) {
14276
+ this.gadgetStartedCount++;
14277
+ return false;
14278
+ }
14279
+ if (this.gadgetStartedCount >= this.maxGadgetsPerResponse) {
14280
+ const errorMessage = `Gadget limit (${this.maxGadgetsPerResponse}) exceeded. Consider calling fewer gadgets per response.`;
14281
+ this.limitExceeded = true;
14282
+ this.logger.info("Gadget limit exceeded, stopping stream processing", {
14283
+ gadgetName: call.gadgetName,
14284
+ invocationId: call.invocationId,
14285
+ limit: this.maxGadgetsPerResponse,
14286
+ currentCount: this.gadgetStartedCount
14287
+ });
14288
+ this.failedInvocations.add(call.invocationId);
14289
+ if (this.tree) {
14290
+ const gadgetNode = this.tree.getNodeByInvocationId(call.invocationId);
14291
+ if (gadgetNode) {
14292
+ this.tree.skipGadget(
14293
+ gadgetNode.id,
14294
+ "maxGadgetsPerResponse",
14295
+ errorMessage,
14296
+ "limit_exceeded"
14297
+ );
14298
+ }
14299
+ }
14300
+ const skipEvent = {
14301
+ type: "gadget_skipped",
14302
+ gadgetName: call.gadgetName,
14303
+ invocationId: call.invocationId,
14304
+ parameters: call.parameters ?? {},
14305
+ failedDependency: "maxGadgetsPerResponse",
14306
+ failedDependencyError: errorMessage
14307
+ };
14308
+ yield skipEvent;
14309
+ const limitSkipNode = this.tree?.getNodeByInvocationId(call.invocationId);
14310
+ const limitSkipSubagentContext = this.tree && limitSkipNode ? getSubagentContextForNode(this.tree, limitSkipNode.id) : void 0;
14311
+ if (this.hooks.observers?.onGadgetSkipped) {
14312
+ const context = {
14313
+ iteration: this.iteration,
14314
+ gadgetName: call.gadgetName,
14315
+ invocationId: call.invocationId,
14316
+ parameters: call.parameters ?? {},
14317
+ failedDependency: "maxGadgetsPerResponse",
14318
+ failedDependencyError: errorMessage,
14319
+ logger: this.logger,
14320
+ subagentContext: limitSkipSubagentContext
14321
+ };
14322
+ await this.safeObserve(() => this.hooks.observers.onGadgetSkipped(context));
14323
+ }
14324
+ if (this.parentObservers?.onGadgetSkipped) {
14325
+ const context = {
14326
+ iteration: this.iteration,
14327
+ gadgetName: call.gadgetName,
14328
+ invocationId: call.invocationId,
14329
+ parameters: call.parameters ?? {},
14330
+ failedDependency: "maxGadgetsPerResponse",
14331
+ failedDependencyError: errorMessage,
14332
+ logger: this.logger,
14333
+ subagentContext: limitSkipSubagentContext
14334
+ };
14335
+ await this.safeObserve(() => this.parentObservers.onGadgetSkipped(context));
14336
+ }
14337
+ return true;
14338
+ }
14339
+ this.gadgetStartedCount++;
14340
+ return false;
14341
+ }
14203
14342
  /**
14204
14343
  * Process pending gadgets whose dependencies are now satisfied.
14205
14344
  * Yields events in real-time as gadgets complete.
@@ -14208,6 +14347,9 @@ var init_stream_processor = __esm({
14208
14347
  * but results are yielded as they become available.
14209
14348
  */
14210
14349
  async *processPendingGadgetsGenerator() {
14350
+ if (this.limitExceeded) {
14351
+ return;
14352
+ }
14211
14353
  let progress = true;
14212
14354
  while (progress && this.gadgetsAwaitingDependencies.size > 0) {
14213
14355
  progress = false;
@@ -14246,6 +14388,15 @@ var init_stream_processor = __esm({
14246
14388
  invocationIds: readyToExecute.map((c) => c.invocationId)
14247
14389
  });
14248
14390
  for (const call of readyToExecute) {
14391
+ const limitCheckGen = this.checkGadgetLimitExceeded(call);
14392
+ let limitResult = await limitCheckGen.next();
14393
+ while (!limitResult.done) {
14394
+ yield limitResult.value;
14395
+ limitResult = await limitCheckGen.next();
14396
+ }
14397
+ if (limitResult.value === true) {
14398
+ continue;
14399
+ }
14249
14400
  for await (const evt of this.executeGadgetGenerator(call)) {
14250
14401
  yield evt;
14251
14402
  }
@@ -14258,6 +14409,15 @@ var init_stream_processor = __esm({
14258
14409
  const eventSets = await Promise.all(
14259
14410
  readyToExecute.map(async (call) => {
14260
14411
  const events = [];
14412
+ const limitCheckGen = this.checkGadgetLimitExceeded(call);
14413
+ let limitResult = await limitCheckGen.next();
14414
+ while (!limitResult.done) {
14415
+ events.push(limitResult.value);
14416
+ limitResult = await limitCheckGen.next();
14417
+ }
14418
+ if (limitResult.value === true) {
14419
+ return events;
14420
+ }
14261
14421
  for await (const evt of this.executeGadgetGenerator(call)) {
14262
14422
  events.push(evt);
14263
14423
  }
@@ -14443,6 +14603,8 @@ var init_agent = __esm({
14443
14603
  // Subagent configuration
14444
14604
  agentContextConfig;
14445
14605
  subagentConfig;
14606
+ // Gadget limiting
14607
+ maxGadgetsPerResponse;
14446
14608
  // Counter for generating synthetic invocation IDs for wrapped text content
14447
14609
  syntheticInvocationCounter = 0;
14448
14610
  // Cross-iteration dependency tracking - allows gadgets to depend on results from prior iterations
@@ -14543,6 +14705,7 @@ var init_agent = __esm({
14543
14705
  temperature: this.temperature
14544
14706
  };
14545
14707
  this.subagentConfig = options.subagentConfig;
14708
+ this.maxGadgetsPerResponse = options.maxGadgetsPerResponse ?? 0;
14546
14709
  this.tree = options.parentTree ?? new ExecutionTree();
14547
14710
  this.parentNodeId = options.parentNodeId ?? null;
14548
14711
  this.baseDepth = options.baseDepth ?? 0;
@@ -14830,7 +14993,9 @@ var init_agent = __esm({
14830
14993
  parentObservers: this.parentObservers,
14831
14994
  // Shared rate limit tracker and retry config for subagents
14832
14995
  rateLimitTracker: this.rateLimitTracker,
14833
- retryConfig: this.retryConfig
14996
+ retryConfig: this.retryConfig,
14997
+ // Gadget limiting
14998
+ maxGadgetsPerResponse: this.maxGadgetsPerResponse
14834
14999
  });
14835
15000
  for await (const event of processor.process(stream2)) {
14836
15001
  if (event.type === "stream_complete") {