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.js CHANGED
@@ -2467,6 +2467,18 @@ async function runWithHandlers(agentGenerator, handlers) {
2467
2467
  });
2468
2468
  }
2469
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;
2470
2482
  case "gadget_result":
2471
2483
  if (handlers.onGadgetResult) {
2472
2484
  await handlers.onGadgetResult(event.result);
@@ -13154,13 +13166,22 @@ var init_parser2 = __esm({
13154
13166
  GadgetCallParser = class {
13155
13167
  buffer = "";
13156
13168
  lastEmittedTextOffset = 0;
13169
+ /** Non-null only while a single trailing gadget block is mid-stream. */
13170
+ currentPartial = null;
13157
13171
  startPrefix;
13158
13172
  endPrefix;
13159
13173
  argPrefix;
13174
+ /** Length of the longest marker; `maxMarkerLength - 1` is the scan-resume overlap. */
13175
+ maxMarkerLength;
13160
13176
  constructor(options = {}) {
13161
13177
  this.startPrefix = options.startPrefix ?? GADGET_START_PREFIX;
13162
13178
  this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
13163
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
+ );
13164
13185
  }
13165
13186
  /**
13166
13187
  * Extract and consume text up to the given index.
@@ -13247,12 +13268,13 @@ var init_parser2 = __esm({
13247
13268
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
13248
13269
  if (metadataEndIndex === -1) break;
13249
13270
  const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
13250
- const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
13271
+ const { gadgetName, invocationId, dependencies } = this.currentPartial ? this.currentPartial : this.parseInvocationMetadata(headerLine);
13251
13272
  const contentStartIndex = metadataEndIndex + 1;
13252
13273
  let partEndIndex;
13253
13274
  let endMarkerLength = 0;
13254
- const nextStartPos = this.buffer.indexOf(this.startPrefix, contentStartIndex);
13255
- 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);
13256
13278
  if (nextStartPos !== -1 && (endPos === -1 || nextStartPos < endPos)) {
13257
13279
  partEndIndex = nextStartPos;
13258
13280
  endMarkerLength = 0;
@@ -13260,9 +13282,22 @@ var init_parser2 = __esm({
13260
13282
  partEndIndex = endPos;
13261
13283
  endMarkerLength = this.endPrefix.length;
13262
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
+ );
13263
13294
  break;
13264
13295
  }
13265
- 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();
13266
13301
  const { parameters, parseError } = this.parseParameters(parametersRaw);
13267
13302
  yield {
13268
13303
  type: "gadget_call",
@@ -13275,6 +13310,7 @@ var init_parser2 = __esm({
13275
13310
  dependencies
13276
13311
  }
13277
13312
  };
13313
+ this.currentPartial = null;
13278
13314
  startIndex = partEndIndex + endMarkerLength;
13279
13315
  this.lastEmittedTextOffset = startIndex;
13280
13316
  }
@@ -13283,6 +13319,117 @@ var init_parser2 = __esm({
13283
13319
  this.lastEmittedTextOffset = 0;
13284
13320
  }
13285
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
+ }
13286
13433
  // Finalize parsing and return remaining text or incomplete gadgets
13287
13434
  *finalize() {
13288
13435
  const startIndex = this.buffer.indexOf(this.startPrefix, this.lastEmittedTextOffset);
@@ -13295,9 +13442,18 @@ var init_parser2 = __esm({
13295
13442
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
13296
13443
  if (metadataEndIndex !== -1) {
13297
13444
  const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
13298
- const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
13445
+ const { gadgetName, invocationId, dependencies } = this.currentPartial ? this.currentPartial : this.parseInvocationMetadata(headerLine);
13299
13446
  const contentStartIndex = metadataEndIndex + 1;
13300
- 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();
13301
13457
  const { parameters, parseError } = this.parseParameters(parametersRaw);
13302
13458
  yield {
13303
13459
  type: "gadget_call",
@@ -13310,6 +13466,7 @@ var init_parser2 = __esm({
13310
13466
  dependencies
13311
13467
  }
13312
13468
  };
13469
+ this.currentPartial = null;
13313
13470
  return;
13314
13471
  }
13315
13472
  }
@@ -13322,6 +13479,7 @@ var init_parser2 = __esm({
13322
13479
  reset() {
13323
13480
  this.buffer = "";
13324
13481
  this.lastEmittedTextOffset = 0;
13482
+ this.currentPartial = null;
13325
13483
  }
13326
13484
  };
13327
13485
  }
@@ -14247,6 +14405,29 @@ async function notifyGadgetStart(ctx) {
14247
14405
  await safeObserve(() => hookFn(context), ctx.logger);
14248
14406
  }
14249
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
+ }
14250
14431
  async function notifyGadgetComplete(ctx) {
14251
14432
  const gadgetNode = ctx.tree?.getNodeByInvocationId(ctx.invocationId);
14252
14433
  const subagentContext = ctx.tree && gadgetNode ? getSubagentContextForNode(ctx.tree, gadgetNode.id) : void 0;
@@ -15055,6 +15236,7 @@ var init_stream_processor = __esm({
15055
15236
  init_gadget_dispatcher();
15056
15237
  init_gadget_hook_lifecycle();
15057
15238
  init_gadget_limit_guard();
15239
+ init_observer_notifier();
15058
15240
  init_safe_observe();
15059
15241
  StreamProcessor = class {
15060
15242
  iteration;
@@ -15063,6 +15245,10 @@ var init_stream_processor = __esm({
15063
15245
  parser;
15064
15246
  // Execution Tree context
15065
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;
15066
15252
  responseText = "";
15067
15253
  // Dependency resolution is delegated to GadgetDependencyResolver
15068
15254
  dependencyResolver;
@@ -15076,6 +15262,8 @@ var init_stream_processor = __esm({
15076
15262
  this.hooks = options.hooks ?? {};
15077
15263
  this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
15078
15264
  this.tree = options.tree;
15265
+ this.parentNodeId = options.parentNodeId;
15266
+ this.parentObservers = options.parentObservers;
15079
15267
  this.dependencyResolver = new GadgetDependencyResolver({
15080
15268
  priorCompletedInvocations: options.priorCompletedInvocations,
15081
15269
  priorFailedInvocations: options.priorFailedInvocations
@@ -15277,6 +15465,17 @@ var init_stream_processor = __esm({
15277
15465
  for await (const e of this.dispatcher.dispatch(event.call)) {
15278
15466
  yield e;
15279
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;
15280
15479
  } else {
15281
15480
  yield event;
15282
15481
  }