@langchain/langgraph-sdk 1.9.30 → 1.9.31

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.
@@ -155,12 +155,25 @@ var StreamController = class {
155
155
  * SSE replay re-adds historically-requested interrupts that have
156
156
  * since been resolved (no `input.responded` event exists in the
157
157
  * protocol, so the SDK has no other way to tell replay from live
158
- * for an idle thread). `null` outside the hydrate-window so
159
- * genuinely new live interrupts on an active run aren't filtered;
160
- * cleared at the start of `submit()` for the same reason.
158
+ * for an idle thread). After a command is accepted, events newer
159
+ * than {@link #interruptReplayThroughSeq} bypass the allowlist.
161
160
  */
162
161
  #hydratedActiveInterruptIds = null;
163
162
  /**
163
+ * Highest event sequence already applied when the latest run command
164
+ * was accepted. Interrupts outside the hydrate-time allowlist remain
165
+ * filtered through this sequence, while newer events are live output
166
+ * from the newly-started run and must be accepted.
167
+ */
168
+ #interruptReplayThroughSeq;
169
+ /**
170
+ * Unknown interrupt events received between local command dispatch and its
171
+ * response. The response supplies the sequence barrier needed to classify
172
+ * each event as historical replay or live output from the new run.
173
+ */
174
+ #pendingInterruptEvents = [];
175
+ #interruptCommandPending = false;
176
+ /**
164
177
  * Monotonic counter bumped at the start of each `submit()` and used
165
178
  * by {@link hydrate} to skip its post-fetch allowlist write when a
166
179
  * submit started while the state fetch was in flight. Without this
@@ -302,13 +315,20 @@ var StreamController = class {
302
315
  awaitNextTerminal: (signal) => this.#awaitNextTerminal(signal),
303
316
  awaitResumedRunTerminal: (signal) => this.#awaitResumedRunTerminal(signal),
304
317
  onSubmitStart: () => {
305
- this.#hydratedActiveInterruptIds = null;
306
318
  this.#submitGeneration += 1;
319
+ this.#interruptCommandPending = this.#hydratedActiveInterruptIds != null;
320
+ this.#pendingInterruptEvents = [];
307
321
  },
308
322
  onRunStart: () => this.#markLocalRunStart(),
309
- onRunCreated: (runId) => this.#notifyCreated(runId),
323
+ onRunCreated: (runId) => {
324
+ this.#advanceInterruptReplayBarrier();
325
+ this.#notifyCreated(runId);
326
+ },
310
327
  onRunCompleted: (reason, runId) => this.#notifyCompleted(reason, runId),
311
- onRunEnd: () => this.#markLocalRunEnd(),
328
+ onRunEnd: () => {
329
+ this.#interruptCommandPending = false;
330
+ this.#markLocalRunEnd();
331
+ },
312
332
  beginOptimistic: (input) => this.#beginOptimistic(input),
313
333
  settleOptimistic: (handle, event) => this.#settleOptimistic(handle, event)
314
334
  });
@@ -562,7 +582,10 @@ var StreamController = class {
562
582
  interrupts: activeInterrupts,
563
583
  interrupt: activeInterrupts[0]
564
584
  }));
565
- if (this.#submitGeneration === generationAtFetch) this.#hydratedActiveInterruptIds = activeIds;
585
+ if (this.#submitGeneration === generationAtFetch) {
586
+ this.#hydratedActiveInterruptIds = activeIds;
587
+ this.#interruptReplayThroughSeq = void 0;
588
+ }
566
589
  }
567
590
  } catch (error) {
568
591
  if (error?.status !== 404) {
@@ -1009,9 +1032,11 @@ var StreamController = class {
1009
1032
  config: options?.config,
1010
1033
  metadata: options?.metadata
1011
1034
  });
1035
+ this.#advanceInterruptReplayBarrier();
1012
1036
  this.#markInterruptResolvedInRootStore(resolved.interruptId);
1013
1037
  }, prepared?.handle);
1014
1038
  } catch (error) {
1039
+ this.#discardPendingInterruptEvents();
1015
1040
  if (this.#disposed && isAbortLikeError(error)) return;
1016
1041
  throw error;
1017
1042
  }
@@ -1078,9 +1103,11 @@ var StreamController = class {
1078
1103
  config: options?.config,
1079
1104
  metadata: options?.metadata
1080
1105
  });
1106
+ this.#advanceInterruptReplayBarrier();
1081
1107
  for (const { interrupt_id: interruptId } of responses) this.#markInterruptResolvedInRootStore(interruptId);
1082
1108
  }, prepared?.handle);
1083
1109
  } catch (error) {
1110
+ this.#discardPendingInterruptEvents();
1084
1111
  if (this.#disposed && isAbortLikeError(error)) return;
1085
1112
  throw error;
1086
1113
  }
@@ -1288,6 +1315,8 @@ var StreamController = class {
1288
1315
  this.#localRunDepth = 0;
1289
1316
  this.#messageMetadata.reset();
1290
1317
  this.#hydratedActiveInterruptIds = null;
1318
+ this.#interruptReplayThroughSeq = void 0;
1319
+ this.#discardPendingInterruptEvents();
1291
1320
  this.queueStore.setState(() => require_submit_coordinator.EMPTY_QUEUE);
1292
1321
  try {
1293
1322
  await subscription?.unsubscribe();
@@ -1701,7 +1730,13 @@ var StreamController = class {
1701
1730
  const data = event.params.data;
1702
1731
  const interruptId = data?.interrupt_id;
1703
1732
  if (typeof interruptId !== "string" || this.#resolvedInterrupts.has(interruptId)) return;
1704
- if (this.#hydratedActiveInterruptIds != null && !this.#hydratedActiveInterruptIds.has(interruptId)) return;
1733
+ const eventSeq = typeof event.seq === "number" ? event.seq : void 0;
1734
+ const isUnknownInterrupt = this.#hydratedActiveInterruptIds != null && !this.#hydratedActiveInterruptIds.has(interruptId);
1735
+ if (isUnknownInterrupt && this.#interruptCommandPending) {
1736
+ this.#pendingInterruptEvents.push(event);
1737
+ return;
1738
+ }
1739
+ if (isUnknownInterrupt && (this.#interruptReplayThroughSeq == null || eventSeq != null && eventSeq <= this.#interruptReplayThroughSeq)) return;
1705
1740
  const namespace = Array.isArray(event.params.namespace) ? [...event.params.namespace] : [...ROOT_NAMESPACE];
1706
1741
  const interrupt = require_interrupts.normalizeInterruptForClient({
1707
1742
  id: interruptId,
@@ -1719,6 +1754,31 @@ var StreamController = class {
1719
1754
  });
1720
1755
  }
1721
1756
  /**
1757
+ * Convert the hydrate-time allowlist into a replay barrier after a command
1758
+ * is accepted. Built-in transports update `lastAppliedThroughSeq` from the
1759
+ * command response before resolving `submitRun` / `respondInput`.
1760
+ */
1761
+ #advanceInterruptReplayBarrier() {
1762
+ if (this.#hydratedActiveInterruptIds == null) {
1763
+ this.#discardPendingInterruptEvents();
1764
+ return;
1765
+ }
1766
+ const throughSeq = this.#thread?.ordering?.lastAppliedThroughSeq;
1767
+ if (typeof throughSeq === "number") this.#interruptReplayThroughSeq = throughSeq;
1768
+ else {
1769
+ this.#hydratedActiveInterruptIds = null;
1770
+ this.#interruptReplayThroughSeq = void 0;
1771
+ }
1772
+ this.#interruptCommandPending = false;
1773
+ const pendingEvents = this.#pendingInterruptEvents;
1774
+ this.#pendingInterruptEvents = [];
1775
+ for (const event of pendingEvents) this.#recordInterrupt(event);
1776
+ }
1777
+ #discardPendingInterruptEvents() {
1778
+ this.#interruptCommandPending = false;
1779
+ this.#pendingInterruptEvents = [];
1780
+ }
1781
+ /**
1722
1782
  * Resolve the protocol namespace for a targeted resume.
1723
1783
  *
1724
1784
  * Prefer an explicit caller-supplied namespace, then the matching