llmist 18.2.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
@@ -3258,6 +3258,18 @@ async function runWithHandlers(agentGenerator, handlers) {
3258
3258
  });
3259
3259
  }
3260
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;
3261
3273
  case "gadget_result":
3262
3274
  if (handlers.onGadgetResult) {
3263
3275
  await handlers.onGadgetResult(event.result);
@@ -14570,13 +14582,22 @@ var init_parser2 = __esm({
14570
14582
  GadgetCallParser = class {
14571
14583
  buffer = "";
14572
14584
  lastEmittedTextOffset = 0;
14585
+ /** Non-null only while a single trailing gadget block is mid-stream. */
14586
+ currentPartial = null;
14573
14587
  startPrefix;
14574
14588
  endPrefix;
14575
14589
  argPrefix;
14590
+ /** Length of the longest marker; `maxMarkerLength - 1` is the scan-resume overlap. */
14591
+ maxMarkerLength;
14576
14592
  constructor(options = {}) {
14577
14593
  this.startPrefix = options.startPrefix ?? GADGET_START_PREFIX;
14578
14594
  this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
14579
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
+ );
14580
14601
  }
14581
14602
  /**
14582
14603
  * Extract and consume text up to the given index.
@@ -14663,12 +14684,13 @@ var init_parser2 = __esm({
14663
14684
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
14664
14685
  if (metadataEndIndex === -1) break;
14665
14686
  const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
14666
- const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
14687
+ const { gadgetName, invocationId, dependencies } = this.currentPartial ? this.currentPartial : this.parseInvocationMetadata(headerLine);
14667
14688
  const contentStartIndex = metadataEndIndex + 1;
14668
14689
  let partEndIndex;
14669
14690
  let endMarkerLength = 0;
14670
- const nextStartPos = this.buffer.indexOf(this.startPrefix, contentStartIndex);
14671
- 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);
14672
14694
  if (nextStartPos !== -1 && (endPos === -1 || nextStartPos < endPos)) {
14673
14695
  partEndIndex = nextStartPos;
14674
14696
  endMarkerLength = 0;
@@ -14676,9 +14698,22 @@ var init_parser2 = __esm({
14676
14698
  partEndIndex = endPos;
14677
14699
  endMarkerLength = this.endPrefix.length;
14678
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
+ );
14679
14710
  break;
14680
14711
  }
14681
- 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();
14682
14717
  const { parameters, parseError } = this.parseParameters(parametersRaw);
14683
14718
  yield {
14684
14719
  type: "gadget_call",
@@ -14691,6 +14726,7 @@ var init_parser2 = __esm({
14691
14726
  dependencies
14692
14727
  }
14693
14728
  };
14729
+ this.currentPartial = null;
14694
14730
  startIndex = partEndIndex + endMarkerLength;
14695
14731
  this.lastEmittedTextOffset = startIndex;
14696
14732
  }
@@ -14699,6 +14735,117 @@ var init_parser2 = __esm({
14699
14735
  this.lastEmittedTextOffset = 0;
14700
14736
  }
14701
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
+ }
14702
14849
  // Finalize parsing and return remaining text or incomplete gadgets
14703
14850
  *finalize() {
14704
14851
  const startIndex = this.buffer.indexOf(this.startPrefix, this.lastEmittedTextOffset);
@@ -14711,9 +14858,18 @@ var init_parser2 = __esm({
14711
14858
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
14712
14859
  if (metadataEndIndex !== -1) {
14713
14860
  const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
14714
- const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
14861
+ const { gadgetName, invocationId, dependencies } = this.currentPartial ? this.currentPartial : this.parseInvocationMetadata(headerLine);
14715
14862
  const contentStartIndex = metadataEndIndex + 1;
14716
- 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();
14717
14873
  const { parameters, parseError } = this.parseParameters(parametersRaw);
14718
14874
  yield {
14719
14875
  type: "gadget_call",
@@ -14726,6 +14882,7 @@ var init_parser2 = __esm({
14726
14882
  dependencies
14727
14883
  }
14728
14884
  };
14885
+ this.currentPartial = null;
14729
14886
  return;
14730
14887
  }
14731
14888
  }
@@ -14738,6 +14895,7 @@ var init_parser2 = __esm({
14738
14895
  reset() {
14739
14896
  this.buffer = "";
14740
14897
  this.lastEmittedTextOffset = 0;
14898
+ this.currentPartial = null;
14741
14899
  }
14742
14900
  };
14743
14901
  }
@@ -15663,6 +15821,29 @@ async function notifyGadgetStart(ctx) {
15663
15821
  await safeObserve(() => hookFn(context), ctx.logger);
15664
15822
  }
15665
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
+ }
15666
15847
  async function notifyGadgetComplete(ctx) {
15667
15848
  const gadgetNode = ctx.tree?.getNodeByInvocationId(ctx.invocationId);
15668
15849
  const subagentContext = ctx.tree && gadgetNode ? getSubagentContextForNode(ctx.tree, gadgetNode.id) : void 0;
@@ -16471,6 +16652,7 @@ var init_stream_processor = __esm({
16471
16652
  init_gadget_dispatcher();
16472
16653
  init_gadget_hook_lifecycle();
16473
16654
  init_gadget_limit_guard();
16655
+ init_observer_notifier();
16474
16656
  init_safe_observe();
16475
16657
  StreamProcessor = class {
16476
16658
  iteration;
@@ -16479,6 +16661,10 @@ var init_stream_processor = __esm({
16479
16661
  parser;
16480
16662
  // Execution Tree context
16481
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;
16482
16668
  responseText = "";
16483
16669
  // Dependency resolution is delegated to GadgetDependencyResolver
16484
16670
  dependencyResolver;
@@ -16492,6 +16678,8 @@ var init_stream_processor = __esm({
16492
16678
  this.hooks = options.hooks ?? {};
16493
16679
  this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
16494
16680
  this.tree = options.tree;
16681
+ this.parentNodeId = options.parentNodeId;
16682
+ this.parentObservers = options.parentObservers;
16495
16683
  this.dependencyResolver = new GadgetDependencyResolver({
16496
16684
  priorCompletedInvocations: options.priorCompletedInvocations,
16497
16685
  priorFailedInvocations: options.priorFailedInvocations
@@ -16693,6 +16881,17 @@ var init_stream_processor = __esm({
16693
16881
  for await (const e of this.dispatcher.dispatch(event.call)) {
16694
16882
  yield e;
16695
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;
16696
16895
  } else {
16697
16896
  yield event;
16698
16897
  }