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.js CHANGED
@@ -1305,7 +1305,8 @@ function resolveRetryConfig(config) {
1305
1305
  onRetriesExhausted: config.onRetriesExhausted,
1306
1306
  shouldRetry: config.shouldRetry,
1307
1307
  respectRetryAfter: config.respectRetryAfter ?? DEFAULT_RETRY_CONFIG.respectRetryAfter,
1308
- maxRetryAfterMs: config.maxRetryAfterMs ?? DEFAULT_RETRY_CONFIG.maxRetryAfterMs
1308
+ maxRetryAfterMs: config.maxRetryAfterMs ?? DEFAULT_RETRY_CONFIG.maxRetryAfterMs,
1309
+ retryOnEmpty: config.retryOnEmpty ?? DEFAULT_RETRY_CONFIG.retryOnEmpty
1309
1310
  };
1310
1311
  }
1311
1312
  function getErrorStatusCode(error) {
@@ -1557,8 +1558,9 @@ var init_retry = __esm({
1557
1558
  factor: 2,
1558
1559
  randomize: true,
1559
1560
  respectRetryAfter: true,
1560
- maxRetryAfterMs: 12e4
1561
+ maxRetryAfterMs: 12e4,
1561
1562
  // 2 minutes cap
1563
+ retryOnEmpty: true
1562
1564
  };
1563
1565
  }
1564
1566
  });
@@ -2465,6 +2467,18 @@ async function runWithHandlers(agentGenerator, handlers) {
2465
2467
  });
2466
2468
  }
2467
2469
  break;
2470
+ case "gadget_args_partial":
2471
+ if (handlers.onGadgetArgsPartial) {
2472
+ await handlers.onGadgetArgsPartial({
2473
+ gadgetName: event.gadgetName,
2474
+ invocationId: event.invocationId,
2475
+ fieldPath: event.fieldPath,
2476
+ value: event.value,
2477
+ delta: event.delta,
2478
+ isFieldComplete: event.isFieldComplete
2479
+ });
2480
+ }
2481
+ break;
2468
2482
  case "gadget_result":
2469
2483
  if (handlers.onGadgetResult) {
2470
2484
  await handlers.onGadgetResult(event.result);
@@ -3773,11 +3787,51 @@ var init_output_limit_manager = __esm({
3773
3787
  }
3774
3788
  });
3775
3789
 
3790
+ // src/core/errors.ts
3791
+ function isAbortError(error) {
3792
+ if (!(error instanceof Error)) return false;
3793
+ if (error.name === "AbortError") return true;
3794
+ if (error.name === "APIConnectionAbortedError") return true;
3795
+ if (error.name === "APIUserAbortError") return true;
3796
+ const message = error.message.toLowerCase();
3797
+ if (message.includes("abort")) return true;
3798
+ if (message.includes("cancelled")) return true;
3799
+ if (message.includes("canceled")) return true;
3800
+ return false;
3801
+ }
3802
+ var EmptyCompletionError;
3803
+ var init_errors2 = __esm({
3804
+ "src/core/errors.ts"() {
3805
+ "use strict";
3806
+ EmptyCompletionError = class extends Error {
3807
+ /** Agent iteration on which the empty completion was observed. */
3808
+ iteration;
3809
+ /** Finish reason reported alongside the empty body (often null). */
3810
+ finishReason;
3811
+ constructor(params) {
3812
+ super(
3813
+ `LLM returned an empty completion (no text, tool calls, or reasoning) on iteration ${params.iteration}`
3814
+ );
3815
+ this.name = "EmptyCompletionError";
3816
+ this.iteration = params.iteration;
3817
+ this.finishReason = params.finishReason;
3818
+ }
3819
+ };
3820
+ }
3821
+ });
3822
+
3776
3823
  // src/agent/retry-orchestrator.ts
3824
+ function isEmptyCompletion(meta) {
3825
+ if (meta.didExecuteGadgets) return false;
3826
+ if (meta.rawResponse?.trim() || meta.finalMessage?.trim()) return false;
3827
+ if (meta.thinkingContent?.trim()) return false;
3828
+ return true;
3829
+ }
3777
3830
  var RetryOrchestrator;
3778
3831
  var init_retry_orchestrator = __esm({
3779
3832
  "src/agent/retry-orchestrator.ts"() {
3780
3833
  "use strict";
3834
+ init_errors2();
3781
3835
  init_retry();
3782
3836
  init_safe_observe();
3783
3837
  init_tree_hook_bridge();
@@ -3843,6 +3897,7 @@ var init_retry_orchestrator = __esm({
3843
3897
  let gadgetCallCount = 0;
3844
3898
  const textOutputs = [];
3845
3899
  const gadgetResults = [];
3900
+ let emptyFailure = null;
3846
3901
  while (streamAttempt < maxStreamAttempts) {
3847
3902
  streamAttempt++;
3848
3903
  try {
@@ -3851,6 +3906,7 @@ var init_retry_orchestrator = __esm({
3851
3906
  for await (const event of processor.process(stream2)) {
3852
3907
  if (event.type === "stream_complete") {
3853
3908
  streamMetadata = event;
3909
+ continue;
3854
3910
  }
3855
3911
  if (event.type === "llm_response_end") {
3856
3912
  this.tree.endLLMResponse(llmNodeId, {
@@ -3872,43 +3928,44 @@ var init_retry_orchestrator = __esm({
3872
3928
  for (const id of processor.getFailedInvocationIds()) {
3873
3929
  this.failedInvocationIds.add(id);
3874
3930
  }
3931
+ if (this.retryConfig.enabled && this.retryConfig.retryOnEmpty && streamMetadata !== null && !streamMetadata.finishReason && isEmptyCompletion(streamMetadata)) {
3932
+ const emptyError = new EmptyCompletionError({
3933
+ iteration,
3934
+ finishReason: streamMetadata.finishReason
3935
+ });
3936
+ if (streamAttempt < maxStreamAttempts) {
3937
+ await this.backoffBeforeRetry(
3938
+ emptyError,
3939
+ streamAttempt,
3940
+ maxStreamAttempts,
3941
+ iteration,
3942
+ llmNodeId
3943
+ );
3944
+ streamMetadata = null;
3945
+ gadgetCallCount = 0;
3946
+ textOutputs.length = 0;
3947
+ gadgetResults.length = 0;
3948
+ continue;
3949
+ }
3950
+ emptyFailure = emptyError;
3951
+ break;
3952
+ }
3953
+ if (streamMetadata !== null) {
3954
+ yield streamMetadata;
3955
+ }
3875
3956
  break;
3876
3957
  } catch (streamError) {
3877
3958
  const error = streamError;
3878
3959
  const canRetry = this.retryConfig.enabled && streamAttempt < maxStreamAttempts;
3879
3960
  const shouldRetryError = this.retryConfig.shouldRetry ? this.retryConfig.shouldRetry(error) : isRetryableError(error);
3880
3961
  if (canRetry && shouldRetryError) {
3881
- const retryAfterMs = this.retryConfig.respectRetryAfter ? extractRetryAfterMs(error) : null;
3882
- const baseDelay = this.retryConfig.minTimeout * this.retryConfig.factor ** (streamAttempt - 1);
3883
- const cappedBaseDelay = Math.min(baseDelay, this.retryConfig.maxTimeout);
3884
- const delay = retryAfterMs !== null ? Math.min(retryAfterMs, this.retryConfig.maxRetryAfterMs) : cappedBaseDelay;
3885
- const finalDelay = this.retryConfig.randomize ? delay * (0.5 + Math.random()) : delay;
3886
- this.logger.warn(
3887
- `Stream iteration failed (attempt ${streamAttempt}/${maxStreamAttempts}), retrying...`,
3888
- {
3889
- error: error.message,
3890
- retriesLeft: maxStreamAttempts - streamAttempt,
3891
- delayMs: Math.round(finalDelay),
3892
- retryAfterMs
3893
- }
3962
+ await this.backoffBeforeRetry(
3963
+ error,
3964
+ streamAttempt,
3965
+ maxStreamAttempts,
3966
+ iteration,
3967
+ llmNodeId
3894
3968
  );
3895
- this.retryConfig.onRetry?.(error, streamAttempt);
3896
- await safeObserve(async () => {
3897
- if (this.hooks.observers?.onRetryAttempt) {
3898
- const subagentContext = getSubagentContextForNode(this.tree, llmNodeId);
3899
- const hookContext = {
3900
- iteration,
3901
- attemptNumber: streamAttempt,
3902
- retriesLeft: maxStreamAttempts - streamAttempt,
3903
- error,
3904
- retryAfterMs: retryAfterMs ?? void 0,
3905
- logger: this.logger,
3906
- subagentContext
3907
- };
3908
- await this.hooks.observers.onRetryAttempt(hookContext);
3909
- }
3910
- }, this.logger);
3911
- await this.sleep(finalDelay);
3912
3969
  streamMetadata = null;
3913
3970
  gadgetCallCount = 0;
3914
3971
  textOutputs.length = 0;
@@ -3925,8 +3982,55 @@ var init_retry_orchestrator = __esm({
3925
3982
  throw error;
3926
3983
  }
3927
3984
  }
3985
+ if (emptyFailure !== null) {
3986
+ this.logger.error(`LLM returned empty completions on all ${streamAttempt} attempts`, {
3987
+ iteration
3988
+ });
3989
+ this.retryConfig.onRetriesExhausted?.(emptyFailure, streamAttempt);
3990
+ throw emptyFailure;
3991
+ }
3928
3992
  return streamMetadata !== null ? { streamMetadata, textOutputs, gadgetResults, gadgetCallCount } : null;
3929
3993
  }
3994
+ /**
3995
+ * Apply the configured backoff before a retry attempt: compute the delay
3996
+ * (Retry-After hint or exponential backoff, with optional jitter), emit the
3997
+ * retry log, fire the `onRetry` callback and `onRetryAttempt` observer, then
3998
+ * sleep. Shared by the error-retry and empty-completion-retry paths so both
3999
+ * honour identical backoff and observer semantics.
4000
+ */
4001
+ async backoffBeforeRetry(error, streamAttempt, maxStreamAttempts, iteration, llmNodeId) {
4002
+ const retryAfterMs = this.retryConfig.respectRetryAfter ? extractRetryAfterMs(error) : null;
4003
+ const baseDelay = this.retryConfig.minTimeout * this.retryConfig.factor ** (streamAttempt - 1);
4004
+ const cappedBaseDelay = Math.min(baseDelay, this.retryConfig.maxTimeout);
4005
+ const delay = retryAfterMs !== null ? Math.min(retryAfterMs, this.retryConfig.maxRetryAfterMs) : cappedBaseDelay;
4006
+ const finalDelay = this.retryConfig.randomize ? delay * (0.5 + Math.random()) : delay;
4007
+ this.logger.warn(
4008
+ `Stream iteration failed (attempt ${streamAttempt}/${maxStreamAttempts}), retrying...`,
4009
+ {
4010
+ error: error.message,
4011
+ retriesLeft: maxStreamAttempts - streamAttempt,
4012
+ delayMs: Math.round(finalDelay),
4013
+ retryAfterMs
4014
+ }
4015
+ );
4016
+ this.retryConfig.onRetry?.(error, streamAttempt);
4017
+ await safeObserve(async () => {
4018
+ if (this.hooks.observers?.onRetryAttempt) {
4019
+ const subagentContext = getSubagentContextForNode(this.tree, llmNodeId);
4020
+ const hookContext = {
4021
+ iteration,
4022
+ attemptNumber: streamAttempt,
4023
+ retriesLeft: maxStreamAttempts - streamAttempt,
4024
+ error,
4025
+ retryAfterMs: retryAfterMs ?? void 0,
4026
+ logger: this.logger,
4027
+ subagentContext
4028
+ };
4029
+ await this.hooks.observers.onRetryAttempt(hookContext);
4030
+ }
4031
+ }, this.logger);
4032
+ await this.sleep(finalDelay);
4033
+ }
3930
4034
  };
3931
4035
  }
3932
4036
  });
@@ -11067,7 +11171,7 @@ var init_model_registry = __esm({
11067
11171
  * Register a provider and collect its model specifications
11068
11172
  */
11069
11173
  registerProvider(provider) {
11070
- const specs = provider.getModelSpecs?.() ?? [];
11174
+ const specs = [...provider.getModelSpecs?.() ?? []];
11071
11175
  if (specs.length > 0) {
11072
11176
  this.modelSpecs.push(...specs);
11073
11177
  this.providerMap.set(provider.providerId, specs);
@@ -11161,7 +11265,7 @@ var init_model_registry = __esm({
11161
11265
  if (!providerId) {
11162
11266
  return [...this.modelSpecs];
11163
11267
  }
11164
- return this.providerMap.get(providerId) ?? [];
11268
+ return [...this.providerMap.get(providerId) ?? []];
11165
11269
  }
11166
11270
  /**
11167
11271
  * Get context window and output limits for a model
@@ -13062,13 +13166,22 @@ var init_parser2 = __esm({
13062
13166
  GadgetCallParser = class {
13063
13167
  buffer = "";
13064
13168
  lastEmittedTextOffset = 0;
13169
+ /** Non-null only while a single trailing gadget block is mid-stream. */
13170
+ currentPartial = null;
13065
13171
  startPrefix;
13066
13172
  endPrefix;
13067
13173
  argPrefix;
13174
+ /** Length of the longest marker; `maxMarkerLength - 1` is the scan-resume overlap. */
13175
+ maxMarkerLength;
13068
13176
  constructor(options = {}) {
13069
13177
  this.startPrefix = options.startPrefix ?? GADGET_START_PREFIX;
13070
13178
  this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
13071
13179
  this.argPrefix = options.argPrefix ?? GADGET_ARG_PREFIX;
13180
+ this.maxMarkerLength = Math.max(
13181
+ this.startPrefix.length,
13182
+ this.endPrefix.length,
13183
+ this.argPrefix.length
13184
+ );
13072
13185
  }
13073
13186
  /**
13074
13187
  * Extract and consume text up to the given index.
@@ -13155,12 +13268,13 @@ var init_parser2 = __esm({
13155
13268
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
13156
13269
  if (metadataEndIndex === -1) break;
13157
13270
  const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
13158
- const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
13271
+ const { gadgetName, invocationId, dependencies } = this.currentPartial ? this.currentPartial : this.parseInvocationMetadata(headerLine);
13159
13272
  const contentStartIndex = metadataEndIndex + 1;
13160
13273
  let partEndIndex;
13161
13274
  let endMarkerLength = 0;
13162
- const nextStartPos = this.buffer.indexOf(this.startPrefix, contentStartIndex);
13163
- const endPos = this.buffer.indexOf(this.endPrefix, contentStartIndex);
13275
+ const bodySearchStart = this.currentPartial ? contentStartIndex + Math.max(0, this.currentPartial.bodyScannedLen - (this.maxMarkerLength - 1)) : contentStartIndex;
13276
+ const nextStartPos = this.buffer.indexOf(this.startPrefix, bodySearchStart);
13277
+ const endPos = this.buffer.indexOf(this.endPrefix, bodySearchStart);
13164
13278
  if (nextStartPos !== -1 && (endPos === -1 || nextStartPos < endPos)) {
13165
13279
  partEndIndex = nextStartPos;
13166
13280
  endMarkerLength = 0;
@@ -13168,9 +13282,22 @@ var init_parser2 = __esm({
13168
13282
  partEndIndex = endPos;
13169
13283
  endMarkerLength = this.endPrefix.length;
13170
13284
  } else {
13285
+ if (!this.currentPartial) {
13286
+ this.currentPartial = this.newPartialState(gadgetName, invocationId, dependencies);
13287
+ }
13288
+ yield* this.emitArgPartials(
13289
+ this.currentPartial,
13290
+ contentStartIndex,
13291
+ this.buffer.length,
13292
+ false
13293
+ );
13171
13294
  break;
13172
13295
  }
13173
- const parametersRaw = this.buffer.substring(contentStartIndex, partEndIndex).trim();
13296
+ const rawSlice = this.buffer.substring(contentStartIndex, partEndIndex);
13297
+ if (this.currentPartial) {
13298
+ yield* this.emitArgPartials(this.currentPartial, contentStartIndex, partEndIndex, true);
13299
+ }
13300
+ const parametersRaw = rawSlice.trim();
13174
13301
  const { parameters, parseError } = this.parseParameters(parametersRaw);
13175
13302
  yield {
13176
13303
  type: "gadget_call",
@@ -13183,6 +13310,7 @@ var init_parser2 = __esm({
13183
13310
  dependencies
13184
13311
  }
13185
13312
  };
13313
+ this.currentPartial = null;
13186
13314
  startIndex = partEndIndex + endMarkerLength;
13187
13315
  this.lastEmittedTextOffset = startIndex;
13188
13316
  }
@@ -13191,6 +13319,117 @@ var init_parser2 = __esm({
13191
13319
  this.lastEmittedTextOffset = 0;
13192
13320
  }
13193
13321
  }
13322
+ /** Create fresh partial-tracking state for a newly-started streaming gadget. */
13323
+ newPartialState(gadgetName, invocationId, dependencies) {
13324
+ return {
13325
+ gadgetName,
13326
+ invocationId,
13327
+ dependencies,
13328
+ emittedFieldLengths: /* @__PURE__ */ new Map(),
13329
+ completedFields: /* @__PURE__ */ new Set(),
13330
+ bodyScannedLen: 0,
13331
+ lastArgRelOffset: -1
13332
+ };
13333
+ }
13334
+ /**
13335
+ * Emit per-field "growing value" partials for an in-progress (or, when
13336
+ * `allComplete`, a just-completed) gadget body delimited by [bodyStart, bodyEnd).
13337
+ *
13338
+ * Incremental by design: each call resumes the `!!!ARG:` scan near where the last
13339
+ * one stopped (backing off by one marker's worth so a marker split across a chunk
13340
+ * boundary is still found) and only re-touches the in-progress field, so a long
13341
+ * streamed body costs O(new bytes) per feed instead of O(body). Every field except
13342
+ * the in-progress (last) one is complete — a following `!!!ARG:` terminated it; the
13343
+ * last field is tentative unless `allComplete`. The tentative field holds back any
13344
+ * suffix that is a partial prefix of a gadget marker so it never leaks into a value.
13345
+ *
13346
+ * We deliberately do NOT run stripMarkdownFences here: an unbalanced opening fence
13347
+ * sits before the first `!!!ARG:` (never emitted) and the authoritative gadget_call
13348
+ * still strips fences from the full raw parameters.
13349
+ */
13350
+ *emitArgPartials(state, bodyStart, bodyEnd, allComplete) {
13351
+ const argLen = this.argPrefix.length;
13352
+ let searchAbs = bodyStart + Math.max(0, state.bodyScannedLen - (this.maxMarkerLength - 1));
13353
+ if (state.lastArgRelOffset >= 0) {
13354
+ searchAbs = Math.max(searchAbs, bodyStart + state.lastArgRelOffset + argLen);
13355
+ }
13356
+ while (true) {
13357
+ const argAbs = this.buffer.indexOf(this.argPrefix, searchAbs);
13358
+ if (argAbs === -1 || argAbs >= bodyEnd) break;
13359
+ if (state.lastArgRelOffset >= 0) {
13360
+ yield* this.emitFieldRange(state, bodyStart + state.lastArgRelOffset, argAbs, true);
13361
+ }
13362
+ state.lastArgRelOffset = argAbs - bodyStart;
13363
+ searchAbs = argAbs + argLen;
13364
+ }
13365
+ if (state.lastArgRelOffset >= 0) {
13366
+ yield* this.emitFieldRange(state, bodyStart + state.lastArgRelOffset, bodyEnd, allComplete);
13367
+ }
13368
+ state.bodyScannedLen = bodyEnd - bodyStart;
13369
+ }
13370
+ /**
13371
+ * Emit a single field whose `!!!ARG:` marker starts at `markerAbs` and whose value
13372
+ * runs to `valueEndAbs` (the next marker, or the body end). Mirrors the per-field
13373
+ * semantics of the old split-based emitter: field-path line, hold-back for a
13374
+ * tentative value, single trailing-newline strip.
13375
+ */
13376
+ *emitFieldRange(state, markerAbs, valueEndAbs, complete2) {
13377
+ const pathStart = markerAbs + this.argPrefix.length;
13378
+ const newlineAbs = this.buffer.indexOf("\n", pathStart);
13379
+ if (newlineAbs === -1 || newlineAbs >= valueEndAbs) {
13380
+ if (!complete2) return;
13381
+ const pointer = this.buffer.substring(pathStart, valueEndAbs).trim();
13382
+ if (pointer) yield* this.emitFieldDelta(state, pointer, "", true);
13383
+ return;
13384
+ }
13385
+ const fieldPath = this.buffer.substring(pathStart, newlineAbs).trim();
13386
+ if (!fieldPath) return;
13387
+ let value = this.buffer.substring(newlineAbs + 1, valueEndAbs);
13388
+ if (!complete2) {
13389
+ const hold = this.trailingPartialMarkerLength(value);
13390
+ if (hold > 0) value = value.slice(0, value.length - hold);
13391
+ }
13392
+ if (value.endsWith("\n")) value = value.slice(0, -1);
13393
+ yield* this.emitFieldDelta(state, fieldPath, value, complete2);
13394
+ }
13395
+ /**
13396
+ * Emit a single partial for a field, but only when its value grew or it newly
13397
+ * completed — keeping event volume proportional to field growth, not characters.
13398
+ */
13399
+ *emitFieldDelta(state, fieldPath, value, fieldComplete) {
13400
+ const previousLength = state.emittedFieldLengths.get(fieldPath) ?? -1;
13401
+ const grew = value.length > previousLength;
13402
+ const newlyComplete = fieldComplete && !state.completedFields.has(fieldPath);
13403
+ if (!grew && !newlyComplete) return;
13404
+ const delta = previousLength < 0 ? value : value.slice(Math.min(previousLength, value.length));
13405
+ state.emittedFieldLengths.set(fieldPath, value.length);
13406
+ if (fieldComplete) state.completedFields.add(fieldPath);
13407
+ yield {
13408
+ type: "gadget_args_partial",
13409
+ invocationId: state.invocationId,
13410
+ gadgetName: state.gadgetName,
13411
+ fieldPath,
13412
+ value,
13413
+ delta,
13414
+ isFieldComplete: fieldComplete
13415
+ };
13416
+ }
13417
+ /**
13418
+ * Length of the longest suffix of `value` that is a proper prefix of any gadget
13419
+ * marker (start/end/arg). Used to hold back the beginning of an incoming marker
13420
+ * so it never appears inside a streamed value.
13421
+ */
13422
+ trailingPartialMarkerLength(value) {
13423
+ const markers = [this.startPrefix, this.endPrefix, this.argPrefix];
13424
+ const limit = Math.min(this.maxMarkerLength - 1, value.length);
13425
+ for (let len = limit; len >= 1; len--) {
13426
+ const suffix = value.slice(value.length - len);
13427
+ for (const marker of markers) {
13428
+ if (suffix.length < marker.length && marker.startsWith(suffix)) return len;
13429
+ }
13430
+ }
13431
+ return 0;
13432
+ }
13194
13433
  // Finalize parsing and return remaining text or incomplete gadgets
13195
13434
  *finalize() {
13196
13435
  const startIndex = this.buffer.indexOf(this.startPrefix, this.lastEmittedTextOffset);
@@ -13203,9 +13442,18 @@ var init_parser2 = __esm({
13203
13442
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
13204
13443
  if (metadataEndIndex !== -1) {
13205
13444
  const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
13206
- const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
13445
+ const { gadgetName, invocationId, dependencies } = this.currentPartial ? this.currentPartial : this.parseInvocationMetadata(headerLine);
13207
13446
  const contentStartIndex = metadataEndIndex + 1;
13208
- const parametersRaw = this.buffer.substring(contentStartIndex).trim();
13447
+ const contentRaw = this.buffer.substring(contentStartIndex);
13448
+ if (this.currentPartial) {
13449
+ yield* this.emitArgPartials(
13450
+ this.currentPartial,
13451
+ contentStartIndex,
13452
+ this.buffer.length,
13453
+ true
13454
+ );
13455
+ }
13456
+ const parametersRaw = contentRaw.trim();
13209
13457
  const { parameters, parseError } = this.parseParameters(parametersRaw);
13210
13458
  yield {
13211
13459
  type: "gadget_call",
@@ -13218,6 +13466,7 @@ var init_parser2 = __esm({
13218
13466
  dependencies
13219
13467
  }
13220
13468
  };
13469
+ this.currentPartial = null;
13221
13470
  return;
13222
13471
  }
13223
13472
  }
@@ -13230,6 +13479,7 @@ var init_parser2 = __esm({
13230
13479
  reset() {
13231
13480
  this.buffer = "";
13232
13481
  this.lastEmittedTextOffset = 0;
13482
+ this.currentPartial = null;
13233
13483
  }
13234
13484
  };
13235
13485
  }
@@ -14155,6 +14405,29 @@ async function notifyGadgetStart(ctx) {
14155
14405
  await safeObserve(() => hookFn(context), ctx.logger);
14156
14406
  }
14157
14407
  }
14408
+ async function notifyGadgetArgsPartial(ctx) {
14409
+ if (!ctx.hooks?.onGadgetArgsPartial && !ctx.parentObservers?.onGadgetArgsPartial) return;
14410
+ const subagentContext = ctx.tree && ctx.parentNodeId ? getSubagentContextForNode(ctx.tree, ctx.parentNodeId) : void 0;
14411
+ const context = {
14412
+ iteration: ctx.iteration,
14413
+ invocationId: ctx.event.invocationId,
14414
+ gadgetName: ctx.event.gadgetName,
14415
+ fieldPath: ctx.event.fieldPath,
14416
+ value: ctx.event.value,
14417
+ delta: ctx.event.delta,
14418
+ isFieldComplete: ctx.event.isFieldComplete,
14419
+ logger: ctx.logger,
14420
+ subagentContext
14421
+ };
14422
+ if (ctx.hooks?.onGadgetArgsPartial) {
14423
+ const hookFn = ctx.hooks.onGadgetArgsPartial;
14424
+ await safeObserve(() => hookFn(context), ctx.logger);
14425
+ }
14426
+ if (ctx.parentObservers?.onGadgetArgsPartial) {
14427
+ const hookFn = ctx.parentObservers.onGadgetArgsPartial;
14428
+ await safeObserve(() => hookFn(context), ctx.logger);
14429
+ }
14430
+ }
14158
14431
  async function notifyGadgetComplete(ctx) {
14159
14432
  const gadgetNode = ctx.tree?.getNodeByInvocationId(ctx.invocationId);
14160
14433
  const subagentContext = ctx.tree && gadgetNode ? getSubagentContextForNode(ctx.tree, gadgetNode.id) : void 0;
@@ -14963,6 +15236,7 @@ var init_stream_processor = __esm({
14963
15236
  init_gadget_dispatcher();
14964
15237
  init_gadget_hook_lifecycle();
14965
15238
  init_gadget_limit_guard();
15239
+ init_observer_notifier();
14966
15240
  init_safe_observe();
14967
15241
  StreamProcessor = class {
14968
15242
  iteration;
@@ -14971,6 +15245,10 @@ var init_stream_processor = __esm({
14971
15245
  parser;
14972
15246
  // Execution Tree context
14973
15247
  tree;
15248
+ /** LLM-call node these gadgets hang off; used to derive subagentContext for partials. */
15249
+ parentNodeId;
15250
+ /** Parent agent observers (subagent visibility) — also notified for arg partials. */
15251
+ parentObservers;
14974
15252
  responseText = "";
14975
15253
  // Dependency resolution is delegated to GadgetDependencyResolver
14976
15254
  dependencyResolver;
@@ -14984,6 +15262,8 @@ var init_stream_processor = __esm({
14984
15262
  this.hooks = options.hooks ?? {};
14985
15263
  this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
14986
15264
  this.tree = options.tree;
15265
+ this.parentNodeId = options.parentNodeId;
15266
+ this.parentObservers = options.parentObservers;
14987
15267
  this.dependencyResolver = new GadgetDependencyResolver({
14988
15268
  priorCompletedInvocations: options.priorCompletedInvocations,
14989
15269
  priorFailedInvocations: options.priorFailedInvocations
@@ -15185,6 +15465,17 @@ var init_stream_processor = __esm({
15185
15465
  for await (const e of this.dispatcher.dispatch(event.call)) {
15186
15466
  yield e;
15187
15467
  }
15468
+ } else if (event.type === "gadget_args_partial") {
15469
+ await notifyGadgetArgsPartial({
15470
+ tree: this.tree,
15471
+ hooks: this.hooks.observers,
15472
+ parentObservers: this.parentObservers,
15473
+ logger: this.logger,
15474
+ iteration: this.iteration,
15475
+ parentNodeId: this.parentNodeId,
15476
+ event
15477
+ });
15478
+ yield event;
15188
15479
  } else {
15189
15480
  yield event;
15190
15481
  }
@@ -16279,19 +16570,7 @@ init_stream_processor();
16279
16570
  // src/index.ts
16280
16571
  init_client2();
16281
16572
  init_constants();
16282
-
16283
- // src/core/errors.ts
16284
- function isAbortError(error) {
16285
- if (!(error instanceof Error)) return false;
16286
- if (error.name === "AbortError") return true;
16287
- if (error.name === "APIConnectionAbortedError") return true;
16288
- if (error.name === "APIUserAbortError") return true;
16289
- const message = error.message.toLowerCase();
16290
- if (message.includes("abort")) return true;
16291
- if (message.includes("cancelled")) return true;
16292
- if (message.includes("canceled")) return true;
16293
- return false;
16294
- }
16573
+ init_errors2();
16295
16574
 
16296
16575
  // src/core/execution-events.ts
16297
16576
  function isLLMEvent(event) {
@@ -17000,6 +17279,7 @@ export {
17000
17279
  DEFAULT_RATE_LIMIT_CONFIG,
17001
17280
  DEFAULT_RETRY_CONFIG,
17002
17281
  DEFAULT_SUMMARIZATION_PROMPT,
17282
+ EmptyCompletionError,
17003
17283
  ExecutionTree,
17004
17284
  FALLBACK_CHARS_PER_TOKEN,
17005
17285
  GADGET_ARG_PREFIX,