llmist 18.1.0 → 18.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1926,7 +1926,8 @@ function resolveRetryConfig(config) {
1926
1926
  onRetriesExhausted: config.onRetriesExhausted,
1927
1927
  shouldRetry: config.shouldRetry,
1928
1928
  respectRetryAfter: config.respectRetryAfter ?? DEFAULT_RETRY_CONFIG.respectRetryAfter,
1929
- maxRetryAfterMs: config.maxRetryAfterMs ?? DEFAULT_RETRY_CONFIG.maxRetryAfterMs
1929
+ maxRetryAfterMs: config.maxRetryAfterMs ?? DEFAULT_RETRY_CONFIG.maxRetryAfterMs,
1930
+ retryOnEmpty: config.retryOnEmpty ?? DEFAULT_RETRY_CONFIG.retryOnEmpty
1930
1931
  };
1931
1932
  }
1932
1933
  function getErrorStatusCode(error) {
@@ -2178,8 +2179,9 @@ var init_retry = __esm({
2178
2179
  factor: 2,
2179
2180
  randomize: true,
2180
2181
  respectRetryAfter: true,
2181
- maxRetryAfterMs: 12e4
2182
+ maxRetryAfterMs: 12e4,
2182
2183
  // 2 minutes cap
2184
+ retryOnEmpty: true
2183
2185
  };
2184
2186
  }
2185
2187
  });
@@ -3256,6 +3258,18 @@ async function runWithHandlers(agentGenerator, handlers) {
3256
3258
  });
3257
3259
  }
3258
3260
  break;
3261
+ case "gadget_args_partial":
3262
+ if (handlers.onGadgetArgsPartial) {
3263
+ await handlers.onGadgetArgsPartial({
3264
+ gadgetName: event.gadgetName,
3265
+ invocationId: event.invocationId,
3266
+ fieldPath: event.fieldPath,
3267
+ value: event.value,
3268
+ delta: event.delta,
3269
+ isFieldComplete: event.isFieldComplete
3270
+ });
3271
+ }
3272
+ break;
3259
3273
  case "gadget_result":
3260
3274
  if (handlers.onGadgetResult) {
3261
3275
  await handlers.onGadgetResult(event.result);
@@ -5188,11 +5202,51 @@ var init_output_limit_manager = __esm({
5188
5202
  }
5189
5203
  });
5190
5204
 
5205
+ // src/core/errors.ts
5206
+ function isAbortError(error) {
5207
+ if (!(error instanceof Error)) return false;
5208
+ if (error.name === "AbortError") return true;
5209
+ if (error.name === "APIConnectionAbortedError") return true;
5210
+ if (error.name === "APIUserAbortError") return true;
5211
+ const message = error.message.toLowerCase();
5212
+ if (message.includes("abort")) return true;
5213
+ if (message.includes("cancelled")) return true;
5214
+ if (message.includes("canceled")) return true;
5215
+ return false;
5216
+ }
5217
+ var EmptyCompletionError;
5218
+ var init_errors = __esm({
5219
+ "src/core/errors.ts"() {
5220
+ "use strict";
5221
+ EmptyCompletionError = class extends Error {
5222
+ /** Agent iteration on which the empty completion was observed. */
5223
+ iteration;
5224
+ /** Finish reason reported alongside the empty body (often null). */
5225
+ finishReason;
5226
+ constructor(params) {
5227
+ super(
5228
+ `LLM returned an empty completion (no text, tool calls, or reasoning) on iteration ${params.iteration}`
5229
+ );
5230
+ this.name = "EmptyCompletionError";
5231
+ this.iteration = params.iteration;
5232
+ this.finishReason = params.finishReason;
5233
+ }
5234
+ };
5235
+ }
5236
+ });
5237
+
5191
5238
  // src/agent/retry-orchestrator.ts
5239
+ function isEmptyCompletion(meta) {
5240
+ if (meta.didExecuteGadgets) return false;
5241
+ if (meta.rawResponse?.trim() || meta.finalMessage?.trim()) return false;
5242
+ if (meta.thinkingContent?.trim()) return false;
5243
+ return true;
5244
+ }
5192
5245
  var RetryOrchestrator;
5193
5246
  var init_retry_orchestrator = __esm({
5194
5247
  "src/agent/retry-orchestrator.ts"() {
5195
5248
  "use strict";
5249
+ init_errors();
5196
5250
  init_retry();
5197
5251
  init_safe_observe();
5198
5252
  init_tree_hook_bridge();
@@ -5258,6 +5312,7 @@ var init_retry_orchestrator = __esm({
5258
5312
  let gadgetCallCount = 0;
5259
5313
  const textOutputs = [];
5260
5314
  const gadgetResults = [];
5315
+ let emptyFailure = null;
5261
5316
  while (streamAttempt < maxStreamAttempts) {
5262
5317
  streamAttempt++;
5263
5318
  try {
@@ -5266,6 +5321,7 @@ var init_retry_orchestrator = __esm({
5266
5321
  for await (const event of processor.process(stream2)) {
5267
5322
  if (event.type === "stream_complete") {
5268
5323
  streamMetadata = event;
5324
+ continue;
5269
5325
  }
5270
5326
  if (event.type === "llm_response_end") {
5271
5327
  this.tree.endLLMResponse(llmNodeId, {
@@ -5287,43 +5343,44 @@ var init_retry_orchestrator = __esm({
5287
5343
  for (const id of processor.getFailedInvocationIds()) {
5288
5344
  this.failedInvocationIds.add(id);
5289
5345
  }
5346
+ if (this.retryConfig.enabled && this.retryConfig.retryOnEmpty && streamMetadata !== null && !streamMetadata.finishReason && isEmptyCompletion(streamMetadata)) {
5347
+ const emptyError = new EmptyCompletionError({
5348
+ iteration,
5349
+ finishReason: streamMetadata.finishReason
5350
+ });
5351
+ if (streamAttempt < maxStreamAttempts) {
5352
+ await this.backoffBeforeRetry(
5353
+ emptyError,
5354
+ streamAttempt,
5355
+ maxStreamAttempts,
5356
+ iteration,
5357
+ llmNodeId
5358
+ );
5359
+ streamMetadata = null;
5360
+ gadgetCallCount = 0;
5361
+ textOutputs.length = 0;
5362
+ gadgetResults.length = 0;
5363
+ continue;
5364
+ }
5365
+ emptyFailure = emptyError;
5366
+ break;
5367
+ }
5368
+ if (streamMetadata !== null) {
5369
+ yield streamMetadata;
5370
+ }
5290
5371
  break;
5291
5372
  } catch (streamError) {
5292
5373
  const error = streamError;
5293
5374
  const canRetry = this.retryConfig.enabled && streamAttempt < maxStreamAttempts;
5294
5375
  const shouldRetryError = this.retryConfig.shouldRetry ? this.retryConfig.shouldRetry(error) : isRetryableError(error);
5295
5376
  if (canRetry && shouldRetryError) {
5296
- const retryAfterMs = this.retryConfig.respectRetryAfter ? extractRetryAfterMs(error) : null;
5297
- const baseDelay = this.retryConfig.minTimeout * this.retryConfig.factor ** (streamAttempt - 1);
5298
- const cappedBaseDelay = Math.min(baseDelay, this.retryConfig.maxTimeout);
5299
- const delay = retryAfterMs !== null ? Math.min(retryAfterMs, this.retryConfig.maxRetryAfterMs) : cappedBaseDelay;
5300
- const finalDelay = this.retryConfig.randomize ? delay * (0.5 + Math.random()) : delay;
5301
- this.logger.warn(
5302
- `Stream iteration failed (attempt ${streamAttempt}/${maxStreamAttempts}), retrying...`,
5303
- {
5304
- error: error.message,
5305
- retriesLeft: maxStreamAttempts - streamAttempt,
5306
- delayMs: Math.round(finalDelay),
5307
- retryAfterMs
5308
- }
5377
+ await this.backoffBeforeRetry(
5378
+ error,
5379
+ streamAttempt,
5380
+ maxStreamAttempts,
5381
+ iteration,
5382
+ llmNodeId
5309
5383
  );
5310
- this.retryConfig.onRetry?.(error, streamAttempt);
5311
- await safeObserve(async () => {
5312
- if (this.hooks.observers?.onRetryAttempt) {
5313
- const subagentContext = getSubagentContextForNode(this.tree, llmNodeId);
5314
- const hookContext = {
5315
- iteration,
5316
- attemptNumber: streamAttempt,
5317
- retriesLeft: maxStreamAttempts - streamAttempt,
5318
- error,
5319
- retryAfterMs: retryAfterMs ?? void 0,
5320
- logger: this.logger,
5321
- subagentContext
5322
- };
5323
- await this.hooks.observers.onRetryAttempt(hookContext);
5324
- }
5325
- }, this.logger);
5326
- await this.sleep(finalDelay);
5327
5384
  streamMetadata = null;
5328
5385
  gadgetCallCount = 0;
5329
5386
  textOutputs.length = 0;
@@ -5340,8 +5397,55 @@ var init_retry_orchestrator = __esm({
5340
5397
  throw error;
5341
5398
  }
5342
5399
  }
5400
+ if (emptyFailure !== null) {
5401
+ this.logger.error(`LLM returned empty completions on all ${streamAttempt} attempts`, {
5402
+ iteration
5403
+ });
5404
+ this.retryConfig.onRetriesExhausted?.(emptyFailure, streamAttempt);
5405
+ throw emptyFailure;
5406
+ }
5343
5407
  return streamMetadata !== null ? { streamMetadata, textOutputs, gadgetResults, gadgetCallCount } : null;
5344
5408
  }
5409
+ /**
5410
+ * Apply the configured backoff before a retry attempt: compute the delay
5411
+ * (Retry-After hint or exponential backoff, with optional jitter), emit the
5412
+ * retry log, fire the `onRetry` callback and `onRetryAttempt` observer, then
5413
+ * sleep. Shared by the error-retry and empty-completion-retry paths so both
5414
+ * honour identical backoff and observer semantics.
5415
+ */
5416
+ async backoffBeforeRetry(error, streamAttempt, maxStreamAttempts, iteration, llmNodeId) {
5417
+ const retryAfterMs = this.retryConfig.respectRetryAfter ? extractRetryAfterMs(error) : null;
5418
+ const baseDelay = this.retryConfig.minTimeout * this.retryConfig.factor ** (streamAttempt - 1);
5419
+ const cappedBaseDelay = Math.min(baseDelay, this.retryConfig.maxTimeout);
5420
+ const delay = retryAfterMs !== null ? Math.min(retryAfterMs, this.retryConfig.maxRetryAfterMs) : cappedBaseDelay;
5421
+ const finalDelay = this.retryConfig.randomize ? delay * (0.5 + Math.random()) : delay;
5422
+ this.logger.warn(
5423
+ `Stream iteration failed (attempt ${streamAttempt}/${maxStreamAttempts}), retrying...`,
5424
+ {
5425
+ error: error.message,
5426
+ retriesLeft: maxStreamAttempts - streamAttempt,
5427
+ delayMs: Math.round(finalDelay),
5428
+ retryAfterMs
5429
+ }
5430
+ );
5431
+ this.retryConfig.onRetry?.(error, streamAttempt);
5432
+ await safeObserve(async () => {
5433
+ if (this.hooks.observers?.onRetryAttempt) {
5434
+ const subagentContext = getSubagentContextForNode(this.tree, llmNodeId);
5435
+ const hookContext = {
5436
+ iteration,
5437
+ attemptNumber: streamAttempt,
5438
+ retriesLeft: maxStreamAttempts - streamAttempt,
5439
+ error,
5440
+ retryAfterMs: retryAfterMs ?? void 0,
5441
+ logger: this.logger,
5442
+ subagentContext
5443
+ };
5444
+ await this.hooks.observers.onRetryAttempt(hookContext);
5445
+ }
5446
+ }, this.logger);
5447
+ await this.sleep(finalDelay);
5448
+ }
5345
5449
  };
5346
5450
  }
5347
5451
  });
@@ -12483,7 +12587,7 @@ var init_model_registry = __esm({
12483
12587
  * Register a provider and collect its model specifications
12484
12588
  */
12485
12589
  registerProvider(provider) {
12486
- const specs = provider.getModelSpecs?.() ?? [];
12590
+ const specs = [...provider.getModelSpecs?.() ?? []];
12487
12591
  if (specs.length > 0) {
12488
12592
  this.modelSpecs.push(...specs);
12489
12593
  this.providerMap.set(provider.providerId, specs);
@@ -12577,7 +12681,7 @@ var init_model_registry = __esm({
12577
12681
  if (!providerId) {
12578
12682
  return [...this.modelSpecs];
12579
12683
  }
12580
- return this.providerMap.get(providerId) ?? [];
12684
+ return [...this.providerMap.get(providerId) ?? []];
12581
12685
  }
12582
12686
  /**
12583
12687
  * Get context window and output limits for a model
@@ -14478,13 +14582,22 @@ var init_parser2 = __esm({
14478
14582
  GadgetCallParser = class {
14479
14583
  buffer = "";
14480
14584
  lastEmittedTextOffset = 0;
14585
+ /** Non-null only while a single trailing gadget block is mid-stream. */
14586
+ currentPartial = null;
14481
14587
  startPrefix;
14482
14588
  endPrefix;
14483
14589
  argPrefix;
14590
+ /** Length of the longest marker; `maxMarkerLength - 1` is the scan-resume overlap. */
14591
+ maxMarkerLength;
14484
14592
  constructor(options = {}) {
14485
14593
  this.startPrefix = options.startPrefix ?? GADGET_START_PREFIX;
14486
14594
  this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
14487
14595
  this.argPrefix = options.argPrefix ?? GADGET_ARG_PREFIX;
14596
+ this.maxMarkerLength = Math.max(
14597
+ this.startPrefix.length,
14598
+ this.endPrefix.length,
14599
+ this.argPrefix.length
14600
+ );
14488
14601
  }
14489
14602
  /**
14490
14603
  * Extract and consume text up to the given index.
@@ -14571,12 +14684,13 @@ var init_parser2 = __esm({
14571
14684
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
14572
14685
  if (metadataEndIndex === -1) break;
14573
14686
  const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
14574
- const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
14687
+ const { gadgetName, invocationId, dependencies } = this.currentPartial ? this.currentPartial : this.parseInvocationMetadata(headerLine);
14575
14688
  const contentStartIndex = metadataEndIndex + 1;
14576
14689
  let partEndIndex;
14577
14690
  let endMarkerLength = 0;
14578
- const nextStartPos = this.buffer.indexOf(this.startPrefix, contentStartIndex);
14579
- const endPos = this.buffer.indexOf(this.endPrefix, contentStartIndex);
14691
+ const bodySearchStart = this.currentPartial ? contentStartIndex + Math.max(0, this.currentPartial.bodyScannedLen - (this.maxMarkerLength - 1)) : contentStartIndex;
14692
+ const nextStartPos = this.buffer.indexOf(this.startPrefix, bodySearchStart);
14693
+ const endPos = this.buffer.indexOf(this.endPrefix, bodySearchStart);
14580
14694
  if (nextStartPos !== -1 && (endPos === -1 || nextStartPos < endPos)) {
14581
14695
  partEndIndex = nextStartPos;
14582
14696
  endMarkerLength = 0;
@@ -14584,9 +14698,22 @@ var init_parser2 = __esm({
14584
14698
  partEndIndex = endPos;
14585
14699
  endMarkerLength = this.endPrefix.length;
14586
14700
  } else {
14701
+ if (!this.currentPartial) {
14702
+ this.currentPartial = this.newPartialState(gadgetName, invocationId, dependencies);
14703
+ }
14704
+ yield* this.emitArgPartials(
14705
+ this.currentPartial,
14706
+ contentStartIndex,
14707
+ this.buffer.length,
14708
+ false
14709
+ );
14587
14710
  break;
14588
14711
  }
14589
- const parametersRaw = this.buffer.substring(contentStartIndex, partEndIndex).trim();
14712
+ const rawSlice = this.buffer.substring(contentStartIndex, partEndIndex);
14713
+ if (this.currentPartial) {
14714
+ yield* this.emitArgPartials(this.currentPartial, contentStartIndex, partEndIndex, true);
14715
+ }
14716
+ const parametersRaw = rawSlice.trim();
14590
14717
  const { parameters, parseError } = this.parseParameters(parametersRaw);
14591
14718
  yield {
14592
14719
  type: "gadget_call",
@@ -14599,6 +14726,7 @@ var init_parser2 = __esm({
14599
14726
  dependencies
14600
14727
  }
14601
14728
  };
14729
+ this.currentPartial = null;
14602
14730
  startIndex = partEndIndex + endMarkerLength;
14603
14731
  this.lastEmittedTextOffset = startIndex;
14604
14732
  }
@@ -14607,6 +14735,117 @@ var init_parser2 = __esm({
14607
14735
  this.lastEmittedTextOffset = 0;
14608
14736
  }
14609
14737
  }
14738
+ /** Create fresh partial-tracking state for a newly-started streaming gadget. */
14739
+ newPartialState(gadgetName, invocationId, dependencies) {
14740
+ return {
14741
+ gadgetName,
14742
+ invocationId,
14743
+ dependencies,
14744
+ emittedFieldLengths: /* @__PURE__ */ new Map(),
14745
+ completedFields: /* @__PURE__ */ new Set(),
14746
+ bodyScannedLen: 0,
14747
+ lastArgRelOffset: -1
14748
+ };
14749
+ }
14750
+ /**
14751
+ * Emit per-field "growing value" partials for an in-progress (or, when
14752
+ * `allComplete`, a just-completed) gadget body delimited by [bodyStart, bodyEnd).
14753
+ *
14754
+ * Incremental by design: each call resumes the `!!!ARG:` scan near where the last
14755
+ * one stopped (backing off by one marker's worth so a marker split across a chunk
14756
+ * boundary is still found) and only re-touches the in-progress field, so a long
14757
+ * streamed body costs O(new bytes) per feed instead of O(body). Every field except
14758
+ * the in-progress (last) one is complete — a following `!!!ARG:` terminated it; the
14759
+ * last field is tentative unless `allComplete`. The tentative field holds back any
14760
+ * suffix that is a partial prefix of a gadget marker so it never leaks into a value.
14761
+ *
14762
+ * We deliberately do NOT run stripMarkdownFences here: an unbalanced opening fence
14763
+ * sits before the first `!!!ARG:` (never emitted) and the authoritative gadget_call
14764
+ * still strips fences from the full raw parameters.
14765
+ */
14766
+ *emitArgPartials(state, bodyStart, bodyEnd, allComplete) {
14767
+ const argLen = this.argPrefix.length;
14768
+ let searchAbs = bodyStart + Math.max(0, state.bodyScannedLen - (this.maxMarkerLength - 1));
14769
+ if (state.lastArgRelOffset >= 0) {
14770
+ searchAbs = Math.max(searchAbs, bodyStart + state.lastArgRelOffset + argLen);
14771
+ }
14772
+ while (true) {
14773
+ const argAbs = this.buffer.indexOf(this.argPrefix, searchAbs);
14774
+ if (argAbs === -1 || argAbs >= bodyEnd) break;
14775
+ if (state.lastArgRelOffset >= 0) {
14776
+ yield* this.emitFieldRange(state, bodyStart + state.lastArgRelOffset, argAbs, true);
14777
+ }
14778
+ state.lastArgRelOffset = argAbs - bodyStart;
14779
+ searchAbs = argAbs + argLen;
14780
+ }
14781
+ if (state.lastArgRelOffset >= 0) {
14782
+ yield* this.emitFieldRange(state, bodyStart + state.lastArgRelOffset, bodyEnd, allComplete);
14783
+ }
14784
+ state.bodyScannedLen = bodyEnd - bodyStart;
14785
+ }
14786
+ /**
14787
+ * Emit a single field whose `!!!ARG:` marker starts at `markerAbs` and whose value
14788
+ * runs to `valueEndAbs` (the next marker, or the body end). Mirrors the per-field
14789
+ * semantics of the old split-based emitter: field-path line, hold-back for a
14790
+ * tentative value, single trailing-newline strip.
14791
+ */
14792
+ *emitFieldRange(state, markerAbs, valueEndAbs, complete2) {
14793
+ const pathStart = markerAbs + this.argPrefix.length;
14794
+ const newlineAbs = this.buffer.indexOf("\n", pathStart);
14795
+ if (newlineAbs === -1 || newlineAbs >= valueEndAbs) {
14796
+ if (!complete2) return;
14797
+ const pointer = this.buffer.substring(pathStart, valueEndAbs).trim();
14798
+ if (pointer) yield* this.emitFieldDelta(state, pointer, "", true);
14799
+ return;
14800
+ }
14801
+ const fieldPath = this.buffer.substring(pathStart, newlineAbs).trim();
14802
+ if (!fieldPath) return;
14803
+ let value = this.buffer.substring(newlineAbs + 1, valueEndAbs);
14804
+ if (!complete2) {
14805
+ const hold = this.trailingPartialMarkerLength(value);
14806
+ if (hold > 0) value = value.slice(0, value.length - hold);
14807
+ }
14808
+ if (value.endsWith("\n")) value = value.slice(0, -1);
14809
+ yield* this.emitFieldDelta(state, fieldPath, value, complete2);
14810
+ }
14811
+ /**
14812
+ * Emit a single partial for a field, but only when its value grew or it newly
14813
+ * completed — keeping event volume proportional to field growth, not characters.
14814
+ */
14815
+ *emitFieldDelta(state, fieldPath, value, fieldComplete) {
14816
+ const previousLength = state.emittedFieldLengths.get(fieldPath) ?? -1;
14817
+ const grew = value.length > previousLength;
14818
+ const newlyComplete = fieldComplete && !state.completedFields.has(fieldPath);
14819
+ if (!grew && !newlyComplete) return;
14820
+ const delta = previousLength < 0 ? value : value.slice(Math.min(previousLength, value.length));
14821
+ state.emittedFieldLengths.set(fieldPath, value.length);
14822
+ if (fieldComplete) state.completedFields.add(fieldPath);
14823
+ yield {
14824
+ type: "gadget_args_partial",
14825
+ invocationId: state.invocationId,
14826
+ gadgetName: state.gadgetName,
14827
+ fieldPath,
14828
+ value,
14829
+ delta,
14830
+ isFieldComplete: fieldComplete
14831
+ };
14832
+ }
14833
+ /**
14834
+ * Length of the longest suffix of `value` that is a proper prefix of any gadget
14835
+ * marker (start/end/arg). Used to hold back the beginning of an incoming marker
14836
+ * so it never appears inside a streamed value.
14837
+ */
14838
+ trailingPartialMarkerLength(value) {
14839
+ const markers = [this.startPrefix, this.endPrefix, this.argPrefix];
14840
+ const limit = Math.min(this.maxMarkerLength - 1, value.length);
14841
+ for (let len = limit; len >= 1; len--) {
14842
+ const suffix = value.slice(value.length - len);
14843
+ for (const marker of markers) {
14844
+ if (suffix.length < marker.length && marker.startsWith(suffix)) return len;
14845
+ }
14846
+ }
14847
+ return 0;
14848
+ }
14610
14849
  // Finalize parsing and return remaining text or incomplete gadgets
14611
14850
  *finalize() {
14612
14851
  const startIndex = this.buffer.indexOf(this.startPrefix, this.lastEmittedTextOffset);
@@ -14619,9 +14858,18 @@ var init_parser2 = __esm({
14619
14858
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
14620
14859
  if (metadataEndIndex !== -1) {
14621
14860
  const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
14622
- const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
14861
+ const { gadgetName, invocationId, dependencies } = this.currentPartial ? this.currentPartial : this.parseInvocationMetadata(headerLine);
14623
14862
  const contentStartIndex = metadataEndIndex + 1;
14624
- const parametersRaw = this.buffer.substring(contentStartIndex).trim();
14863
+ const contentRaw = this.buffer.substring(contentStartIndex);
14864
+ if (this.currentPartial) {
14865
+ yield* this.emitArgPartials(
14866
+ this.currentPartial,
14867
+ contentStartIndex,
14868
+ this.buffer.length,
14869
+ true
14870
+ );
14871
+ }
14872
+ const parametersRaw = contentRaw.trim();
14625
14873
  const { parameters, parseError } = this.parseParameters(parametersRaw);
14626
14874
  yield {
14627
14875
  type: "gadget_call",
@@ -14634,6 +14882,7 @@ var init_parser2 = __esm({
14634
14882
  dependencies
14635
14883
  }
14636
14884
  };
14885
+ this.currentPartial = null;
14637
14886
  return;
14638
14887
  }
14639
14888
  }
@@ -14646,6 +14895,7 @@ var init_parser2 = __esm({
14646
14895
  reset() {
14647
14896
  this.buffer = "";
14648
14897
  this.lastEmittedTextOffset = 0;
14898
+ this.currentPartial = null;
14649
14899
  }
14650
14900
  };
14651
14901
  }
@@ -15571,6 +15821,29 @@ async function notifyGadgetStart(ctx) {
15571
15821
  await safeObserve(() => hookFn(context), ctx.logger);
15572
15822
  }
15573
15823
  }
15824
+ async function notifyGadgetArgsPartial(ctx) {
15825
+ if (!ctx.hooks?.onGadgetArgsPartial && !ctx.parentObservers?.onGadgetArgsPartial) return;
15826
+ const subagentContext = ctx.tree && ctx.parentNodeId ? getSubagentContextForNode(ctx.tree, ctx.parentNodeId) : void 0;
15827
+ const context = {
15828
+ iteration: ctx.iteration,
15829
+ invocationId: ctx.event.invocationId,
15830
+ gadgetName: ctx.event.gadgetName,
15831
+ fieldPath: ctx.event.fieldPath,
15832
+ value: ctx.event.value,
15833
+ delta: ctx.event.delta,
15834
+ isFieldComplete: ctx.event.isFieldComplete,
15835
+ logger: ctx.logger,
15836
+ subagentContext
15837
+ };
15838
+ if (ctx.hooks?.onGadgetArgsPartial) {
15839
+ const hookFn = ctx.hooks.onGadgetArgsPartial;
15840
+ await safeObserve(() => hookFn(context), ctx.logger);
15841
+ }
15842
+ if (ctx.parentObservers?.onGadgetArgsPartial) {
15843
+ const hookFn = ctx.parentObservers.onGadgetArgsPartial;
15844
+ await safeObserve(() => hookFn(context), ctx.logger);
15845
+ }
15846
+ }
15574
15847
  async function notifyGadgetComplete(ctx) {
15575
15848
  const gadgetNode = ctx.tree?.getNodeByInvocationId(ctx.invocationId);
15576
15849
  const subagentContext = ctx.tree && gadgetNode ? getSubagentContextForNode(ctx.tree, gadgetNode.id) : void 0;
@@ -16379,6 +16652,7 @@ var init_stream_processor = __esm({
16379
16652
  init_gadget_dispatcher();
16380
16653
  init_gadget_hook_lifecycle();
16381
16654
  init_gadget_limit_guard();
16655
+ init_observer_notifier();
16382
16656
  init_safe_observe();
16383
16657
  StreamProcessor = class {
16384
16658
  iteration;
@@ -16387,6 +16661,10 @@ var init_stream_processor = __esm({
16387
16661
  parser;
16388
16662
  // Execution Tree context
16389
16663
  tree;
16664
+ /** LLM-call node these gadgets hang off; used to derive subagentContext for partials. */
16665
+ parentNodeId;
16666
+ /** Parent agent observers (subagent visibility) — also notified for arg partials. */
16667
+ parentObservers;
16390
16668
  responseText = "";
16391
16669
  // Dependency resolution is delegated to GadgetDependencyResolver
16392
16670
  dependencyResolver;
@@ -16400,6 +16678,8 @@ var init_stream_processor = __esm({
16400
16678
  this.hooks = options.hooks ?? {};
16401
16679
  this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
16402
16680
  this.tree = options.tree;
16681
+ this.parentNodeId = options.parentNodeId;
16682
+ this.parentObservers = options.parentObservers;
16403
16683
  this.dependencyResolver = new GadgetDependencyResolver({
16404
16684
  priorCompletedInvocations: options.priorCompletedInvocations,
16405
16685
  priorFailedInvocations: options.priorFailedInvocations
@@ -16601,6 +16881,17 @@ var init_stream_processor = __esm({
16601
16881
  for await (const e of this.dispatcher.dispatch(event.call)) {
16602
16882
  yield e;
16603
16883
  }
16884
+ } else if (event.type === "gadget_args_partial") {
16885
+ await notifyGadgetArgsPartial({
16886
+ tree: this.tree,
16887
+ hooks: this.hooks.observers,
16888
+ parentObservers: this.parentObservers,
16889
+ logger: this.logger,
16890
+ iteration: this.iteration,
16891
+ parentNodeId: this.parentNodeId,
16892
+ event
16893
+ });
16894
+ yield event;
16604
16895
  } else {
16605
16896
  yield event;
16606
16897
  }
@@ -16772,7 +17063,7 @@ var init_stream_processor_factory = __esm({
16772
17063
 
16773
17064
  // src/mcp/errors.ts
16774
17065
  var McpError, McpUntrustedCommandError, McpConnectError, McpToolCallError, McpTimeoutError, JsonSchemaConversionError;
16775
- var init_errors = __esm({
17066
+ var init_errors2 = __esm({
16776
17067
  "src/mcp/errors.ts"() {
16777
17068
  "use strict";
16778
17069
  McpError = class extends Error {
@@ -16858,7 +17149,7 @@ var init_allowlist = __esm({
16858
17149
  "src/mcp/allowlist.ts"() {
16859
17150
  "use strict";
16860
17151
  import_node_path6 = __toESM(require("path"), 1);
16861
- init_errors();
17152
+ init_errors2();
16862
17153
  DEFAULT_MCP_COMMAND_ALLOWLIST = /* @__PURE__ */ new Set([
16863
17154
  "npx",
16864
17155
  "node",
@@ -16896,7 +17187,7 @@ var init_client2 = __esm({
16896
17187
  "src/mcp/client.ts"() {
16897
17188
  "use strict";
16898
17189
  init_allowlist();
16899
- init_errors();
17190
+ init_errors2();
16900
17191
  cachedSdk = null;
16901
17192
  DEFAULT_CLIENT_INFO = { name: "llmist", version: "0.0.0" };
16902
17193
  McpClient = class {
@@ -17478,7 +17769,7 @@ var init_json_schema_to_zod = __esm({
17478
17769
  "src/mcp/json-schema-to-zod.ts"() {
17479
17770
  "use strict";
17480
17771
  import_zod4 = require("zod");
17481
- init_errors();
17772
+ init_errors2();
17482
17773
  }
17483
17774
  });
17484
17775
 
@@ -18485,6 +18776,7 @@ __export(index_exports, {
18485
18776
  DEFAULT_RATE_LIMIT_CONFIG: () => DEFAULT_RATE_LIMIT_CONFIG,
18486
18777
  DEFAULT_RETRY_CONFIG: () => DEFAULT_RETRY_CONFIG,
18487
18778
  DEFAULT_SUMMARIZATION_PROMPT: () => DEFAULT_SUMMARIZATION_PROMPT,
18779
+ EmptyCompletionError: () => EmptyCompletionError,
18488
18780
  ExecutionTree: () => ExecutionTree,
18489
18781
  FALLBACK_CHARS_PER_TOKEN: () => FALLBACK_CHARS_PER_TOKEN,
18490
18782
  GADGET_ARG_PREFIX: () => GADGET_ARG_PREFIX,
@@ -18770,19 +19062,7 @@ init_stream_processor();
18770
19062
  // src/index.ts
18771
19063
  init_client();
18772
19064
  init_constants();
18773
-
18774
- // src/core/errors.ts
18775
- function isAbortError(error) {
18776
- if (!(error instanceof Error)) return false;
18777
- if (error.name === "AbortError") return true;
18778
- if (error.name === "APIConnectionAbortedError") return true;
18779
- if (error.name === "APIUserAbortError") return true;
18780
- const message = error.message.toLowerCase();
18781
- if (message.includes("abort")) return true;
18782
- if (message.includes("cancelled")) return true;
18783
- if (message.includes("canceled")) return true;
18784
- return false;
18785
- }
19065
+ init_errors();
18786
19066
 
18787
19067
  // src/core/execution-events.ts
18788
19068
  function isLLMEvent(event) {
@@ -18842,7 +19122,7 @@ init_typed_gadget();
18842
19122
  // src/mcp/index.ts
18843
19123
  init_allowlist();
18844
19124
  init_client2();
18845
- init_errors();
19125
+ init_errors2();
18846
19126
 
18847
19127
  // src/mcp/gadget-exporter.ts
18848
19128
  init_schema_to_json();
@@ -19492,6 +19772,7 @@ function getHostExports2(ctx) {
19492
19772
  DEFAULT_RATE_LIMIT_CONFIG,
19493
19773
  DEFAULT_RETRY_CONFIG,
19494
19774
  DEFAULT_SUMMARIZATION_PROMPT,
19775
+ EmptyCompletionError,
19495
19776
  ExecutionTree,
19496
19777
  FALLBACK_CHARS_PER_TOKEN,
19497
19778
  GADGET_ARG_PREFIX,