@langchain/langgraph-sdk 1.9.27 → 1.9.29

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.
Files changed (37) hide show
  1. package/dist/client/stream/types.d.cts +4 -4
  2. package/dist/client/stream/types.d.ts +4 -4
  3. package/dist/client/threads/index.cjs +3 -0
  4. package/dist/client/threads/index.cjs.map +1 -1
  5. package/dist/client/threads/index.js +3 -0
  6. package/dist/client/threads/index.js.map +1 -1
  7. package/dist/schema.d.cts +7 -0
  8. package/dist/schema.d.cts.map +1 -1
  9. package/dist/schema.d.ts +7 -0
  10. package/dist/schema.d.ts.map +1 -1
  11. package/dist/stream/controller.cjs +87 -57
  12. package/dist/stream/controller.cjs.map +1 -1
  13. package/dist/stream/controller.d.cts +12 -23
  14. package/dist/stream/controller.d.cts.map +1 -1
  15. package/dist/stream/controller.d.ts +12 -23
  16. package/dist/stream/controller.d.ts.map +1 -1
  17. package/dist/stream/controller.js +87 -57
  18. package/dist/stream/controller.js.map +1 -1
  19. package/dist/stream/projections/channel.cjs +4 -6
  20. package/dist/stream/projections/channel.cjs.map +1 -1
  21. package/dist/stream/projections/channel.d.cts.map +1 -1
  22. package/dist/stream/projections/channel.d.ts.map +1 -1
  23. package/dist/stream/projections/channel.js +4 -6
  24. package/dist/stream/projections/channel.js.map +1 -1
  25. package/dist/stream/submit-coordinator.cjs +7 -5
  26. package/dist/stream/submit-coordinator.cjs.map +1 -1
  27. package/dist/stream/submit-coordinator.js +7 -5
  28. package/dist/stream/submit-coordinator.js.map +1 -1
  29. package/dist/stream/types.d.cts +3 -3
  30. package/dist/stream/types.d.ts +3 -3
  31. package/dist/utils/async_caller.cjs +1 -1
  32. package/dist/utils/async_caller.cjs.map +1 -1
  33. package/dist/utils/async_caller.d.cts.map +1 -1
  34. package/dist/utils/async_caller.d.ts.map +1 -1
  35. package/dist/utils/async_caller.js +1 -1
  36. package/dist/utils/async_caller.js.map +1 -1
  37. package/package.json +10 -10
@@ -170,13 +170,23 @@ var StreamController = class {
170
170
  */
171
171
  #submitGeneration = 0;
172
172
  /**
173
- * Thread ids we minted client-side on first `submit()`. Keeping them
174
- * here lets `hydrate()` skip the `threads.getState()` round-trip —
175
- * we know there is nothing checkpointed server-side yet (and the
176
- * request would 404 and surface a spurious error to the UI).
173
+ * Thread ids this controller minted client-side on first `submit()`.
174
+ * `hydrate()` skips `threads.getState()` for these — we know there
175
+ * is nothing checkpointed yet. Cleared once dispatch commits the
176
+ * thread server-side.
177
177
  */
178
178
  #selfCreatedThreadIds = /* @__PURE__ */ new Set();
179
179
  /**
180
+ * Caller-supplied thread ids that 404'd on hydrate. Unlike
181
+ * {@link #selfCreatedThreadIds}, these are revalidated on every
182
+ * hydrate: another client may create the same id between visits.
183
+ * While marked missing, `submit()` still defers the root SSE pump
184
+ * until `run.start` / `commands` commits the row — joining
185
+ * `/stream/events` beforehand leaves a dead subscription on
186
+ * langgraph_api's in-mem runtime.
187
+ */
188
+ #missingThreadIds = /* @__PURE__ */ new Set();
189
+ /**
180
190
  * In-flight per-subagent namespace resolutions, keyed by tool-call
181
191
  * id. De-dupes concurrent {@link resolveSubagentNamespace} calls so
182
192
  * re-renders / multiple consumers of the same subagent don't issue
@@ -271,12 +281,14 @@ var StreamController = class {
271
281
  rememberSelfCreatedThreadId: (threadId) => {
272
282
  this.#selfCreatedThreadIds.add(threadId);
273
283
  },
284
+ isSelfCreatedThreadId: (threadId) => this.#selfCreatedThreadIds.has(threadId) || this.#missingThreadIds.has(threadId),
274
285
  hydrate: (threadId) => this.hydrate(threadId),
275
286
  ensureThread: (threadId, deferRootPump) => this.#ensureThread(threadId, deferRootPump),
276
287
  startDeferredRootPump: () => this.#startDeferredRootPump(),
277
288
  abandonDeferredRootPump: () => this.#abandonDeferredRootPump(),
278
289
  forgetSelfCreatedThreadId: (threadId) => {
279
290
  this.#selfCreatedThreadIds.delete(threadId);
291
+ this.#missingThreadIds.delete(threadId);
280
292
  },
281
293
  waitForRootPumpReady: () => this.#rootPumpReady,
282
294
  awaitNextTerminal: (signal) => this.#awaitNextTerminal(signal),
@@ -424,13 +436,17 @@ var StreamController = class {
424
436
  ...s,
425
437
  isThreadLoading: true
426
438
  }));
439
+ const hydratedThreadId = this.#currentThreadId;
427
440
  let hydrationError;
428
441
  let threadExists = false;
442
+ let threadMissing = false;
429
443
  let threadActive = true;
430
444
  try {
431
445
  const state = await this.#fetchHydrationState();
446
+ if (this.#disposed || this.#currentThreadId !== hydratedThreadId) return;
432
447
  threadExists = state != null;
433
448
  threadActive = isThreadStateActive(state);
449
+ this.#missingThreadIds.delete(hydratedThreadId);
434
450
  if (state?.values != null) {
435
451
  /**
436
452
  * `threads.getState()` returns the legacy `ThreadState` shape
@@ -515,6 +531,8 @@ var StreamController = class {
515
531
  const activeIds = /* @__PURE__ */ new Set();
516
532
  for (const task of state.tasks) {
517
533
  if (!Array.isArray(task?.interrupts)) continue;
534
+ const checkpointNs = task?.checkpoint?.checkpoint_ns;
535
+ const namespace = typeof checkpointNs === "string" && checkpointNs.length > 0 ? checkpointNs.split("|").filter((segment) => segment.length > 0) : [...ROOT_NAMESPACE];
518
536
  for (const interrupt of task.interrupts) {
519
537
  const typed = interrupt;
520
538
  const id = typed?.id;
@@ -522,7 +540,8 @@ var StreamController = class {
522
540
  activeIds.add(id);
523
541
  activeInterrupts.push(require_interrupts.normalizeInterruptForClient({
524
542
  id,
525
- value: typed?.value
543
+ value: typed?.value,
544
+ namespace: Array.isArray(typed?.namespace) ? [...typed.namespace] : namespace
526
545
  }));
527
546
  }
528
547
  }
@@ -540,7 +559,7 @@ var StreamController = class {
540
559
  ...s,
541
560
  error
542
561
  }));
543
- }
562
+ } else threadMissing = true;
544
563
  } finally {
545
564
  this.rootStore.setState((s) => ({
546
565
  ...s,
@@ -550,6 +569,20 @@ var StreamController = class {
550
569
  else this.#resolveHydration();
551
570
  }
552
571
  /**
572
+ * Missing threads must not open `/stream/events`. The in-mem
573
+ * runtime accepts the join against a not-yet-created id but the
574
+ * subscription never attaches to the live channel — the first run
575
+ * then delivers 0 bytes until idle reconnect. Park the pump until
576
+ * `submit()`'s `run.start` creates the row (see
577
+ * {@link #startDeferredRootPump}). Tracked in `#missingThreadIds`
578
+ * (not `#selfCreatedThreadIds`) so a later hydrate revalidates —
579
+ * another client may create the same id between visits.
580
+ */
581
+ if (threadMissing) {
582
+ this.#missingThreadIds.add(hydratedThreadId);
583
+ return;
584
+ }
585
+ /**
553
586
  * Open the shared subscription on mount so in-flight server-side
554
587
  * runs are observed even when no local `submit()` is active — BUT
555
588
  * only when the thread is actually active (see
@@ -562,7 +595,7 @@ var StreamController = class {
562
595
  * The transport replays from `seq=0` on the deferred subscribe, so
563
596
  * nothing is missed.
564
597
  */
565
- const thread = this.#ensureThread(this.#currentThreadId, !threadActive);
598
+ const thread = this.#ensureThread(hydratedThreadId, !threadActive);
566
599
  /**
567
600
  * Start the wildcard lifecycle watcher up-front for existing,
568
601
  * active threads. The root content pump runs at `depth: 1`, which
@@ -686,7 +719,7 @@ var StreamController = class {
686
719
  */
687
720
  async #trySeedProjectionFromHistory(params) {
688
721
  const threadId = this.#currentThreadId;
689
- if (this.#disposed || threadId == null || params.namespace.length === 0 || !this.#rootPumpDeferred || this.#selfCreatedThreadIds.has(threadId)) return false;
722
+ if (this.#disposed || threadId == null || params.namespace.length === 0 || !this.#rootPumpDeferred || this.#selfCreatedThreadIds.has(threadId) || this.#missingThreadIds.has(threadId)) return false;
690
723
  if (await this.#skipDefaultSubagentProjection(params.namespace, threadId)) return true;
691
724
  if (this.#disposed || this.#currentThreadId !== threadId || !this.#rootPumpDeferred) return false;
692
725
  const seed = await this.#getScopedHistorySeed(threadId, params.namespace);
@@ -894,14 +927,15 @@ var StreamController = class {
894
927
  * oldest and picks the first entry whose `interruptId` has not already
895
928
  * been resolved by a prior `respond()` call. That entry may be at the
896
929
  * root (`namespace: []`) or inside a subgraph (non-empty `namespace`).
897
- * This is **not** the same as {@link RootSnapshot.interrupts
898
- * `rootStore.interrupts[0]`} / framework `stream.interrupt`, which only
899
- * mirrors root-namespace interrupts for UI convenience.
930
+ * {@link RootSnapshot.interrupts `rootStore.interrupts`} /
931
+ * framework `stream.interrupts` mirrors the same pending interrupts
932
+ * (including nested namespaces) for UI rendering.
900
933
  *
901
934
  * Omitting `interruptId` is fine when exactly one interrupt is pending.
902
935
  * When several can be active (parallel subagents, fan-out, nested
903
- * graphs), pass an explicit `interruptId` (and `namespace` for subgraph
904
- * interrupts) so you resume the interrupt the user acted on.
936
+ * graphs), pass an explicit `interruptId` so you resume the interrupt
937
+ * the user acted on. `namespace` is resolved automatically from
938
+ * `thread.interrupts` / `stream.interrupts` when omitted.
905
939
  *
906
940
  * To resume several interrupts pending at the same checkpoint in one
907
941
  * command, use {@link respondAll} — sequential single `respond()` calls
@@ -909,9 +943,8 @@ var StreamController = class {
909
943
  * others with no interrupted run to respond to.
910
944
  *
911
945
  * The server validates `namespace` against the pending interrupt. Root
912
- * interrupts use `namespace: []` (the default when `namespace` is
913
- * omitted). Subgraph interrupts require the exact tuple from
914
- * `getThread()?.interrupts` — see the example below.
946
+ * interrupts use `namespace: []`. Subgraph interrupts carry the exact
947
+ * tuple on each {@link Interrupt} / {@link InterruptPayload} entry.
915
948
  *
916
949
  * @param response - Payload sent back to the interrupted namespace.
917
950
  * @param options - Optional target (`interruptId` / `namespace`) and
@@ -932,33 +965,22 @@ var StreamController = class {
932
965
  * );
933
966
  * ```
934
967
  *
935
- * @example Multiple root interrupts — target by id
968
+ * @example Multiple / nested interrupts — target by id
936
969
  * ```tsx
937
970
  * for (const intr of stream.interrupts) {
938
971
  * await stream.respond(decide(intr.value), { interruptId: intr.id! });
939
972
  * }
940
973
  * ```
941
974
  *
942
- * @example Subgraph interrupt — read `namespace` from the thread stream
943
- * ```tsx
944
- * const thread = stream.getThread();
945
- * for (const entry of thread?.interrupts ?? []) {
946
- * await stream.respond(buildResponse(entry.payload), {
947
- * interruptId: entry.interruptId,
948
- * namespace: entry.namespace,
949
- * });
950
- * }
951
- * ```
952
- *
953
- * Each {@link InterruptPayload} on `thread.interrupts` mirrors an
954
- * `input.requested` event: `{ interruptId, payload, namespace }`.
955
- * Nested interrupts may appear here but not on `stream.interrupts`.
975
+ * Each {@link InterruptPayload} on `thread.interrupts` and each
976
+ * {@link Interrupt} on `stream.interrupts` mirrors an
977
+ * `input.requested` event with its protocol `namespace`.
956
978
  */
957
979
  async respond(response, options) {
958
980
  if (this.#disposed || this.#thread == null) throw new Error("No active thread to respond to.");
959
981
  const resolved = options?.interruptId != null ? {
960
982
  interruptId: options.interruptId,
961
- namespace: options.namespace ?? [...ROOT_NAMESPACE]
983
+ namespace: this.#resolveNamespaceForInterruptId(options.interruptId, options.namespace)
962
984
  } : this.#resolveInterruptForResume();
963
985
  if (resolved == null) throw new Error("No pending interrupt to respond to.");
964
986
  const thread = this.#thread;
@@ -1028,11 +1050,10 @@ var StreamController = class {
1028
1050
  const entries = Object.entries(responsesById);
1029
1051
  if (entries.length === 0) throw new Error("respondAll() requires at least one response.");
1030
1052
  const thread = this.#thread;
1031
- const pending = thread.interrupts;
1032
1053
  const responses = entries.map(([interruptId, response]) => ({
1033
1054
  interrupt_id: interruptId,
1034
1055
  response: require_hitl_interrupt_payload.normalizeHitlResponseForServer(response),
1035
- namespace: pending.find((entry) => entry.interruptId === interruptId)?.namespace ?? [...ROOT_NAMESPACE]
1056
+ namespace: this.#resolveNamespaceForInterruptId(interruptId)
1036
1057
  }));
1037
1058
  const prepared = options?.update != null ? this.#beginOptimistic(options.update) : void 0;
1038
1059
  const dispatchUpdate = this.#resolveDispatchUpdate(options?.update, prepared);
@@ -1199,16 +1220,11 @@ var StreamController = class {
1199
1220
  * Without this, the controller would be wedged in a state where:
1200
1221
  * - `#thread` is wired but no content pump is open
1201
1222
  * - `#rootPumpDeferred` stays `true`
1202
- * - `selfCreatedThreadIds` still holds the id
1203
1223
  *
1204
- * A retry submit on the same controller would see
1205
- * `wasSelfCreated=false` (because `currentThreadId` is no longer
1206
- * null), `#ensureThread(id, false)` would early-return because
1207
- * `#thread != null`, and the pump would never start. The thread
1208
- * would have an id committed to the URL but no live subscription.
1209
- *
1210
- * Tearing down `#thread` so the next submit re-runs `#ensureThread`
1211
- * from scratch is the simplest recovery — the failed dispatch
1224
+ * A retry submit must re-run `#ensureThread` from scratch (with
1225
+ * `deferRootPump` still true — the id stays in
1226
+ * `#selfCreatedThreadIds` until a successful dispatch). Tearing
1227
+ * down `#thread` is the simplest recovery; the failed dispatch
1212
1228
  * means there was nothing to subscribe to anyway.
1213
1229
  */
1214
1230
  #abandonDeferredRootPump() {
@@ -1411,16 +1427,14 @@ var StreamController = class {
1411
1427
  this.#subgraphs.push(event);
1412
1428
  this.#lifecycleLoading.handle(event);
1413
1429
  /**
1414
- * Nested `input.requested` events (HITL inside a subagent /
1415
- * subgraph) are not observable via the narrow content pump. The
1416
- * `ThreadStream` itself already records them into
1417
- * `thread.interrupts`, which `#latestUnresolvedInterrupt()`
1418
- * consults — so HITL respond() works for any depth. Root-level
1419
- * interrupts are also mirrored into `rootStore.interrupts` here so
1420
- * UI state does not depend on the narrower content pump being the
1421
- * first consumer to see the event.
1430
+ * `input.requested` events (including HITL inside a subagent /
1431
+ * subgraph) are not always observable via the narrow content pump.
1432
+ * Mirror every namespace into `rootStore.interrupts` here so UI
1433
+ * state does not depend on the content pump being the first
1434
+ * consumer. `ThreadStream.interrupts` is updated in parallel for
1435
+ * resume targeting.
1422
1436
  */
1423
- this.#recordRootInterrupt(event);
1437
+ this.#recordInterrupt(event);
1424
1438
  }
1425
1439
  /**
1426
1440
  * Process one root-pump event and update all root projections.
@@ -1513,7 +1527,7 @@ var StreamController = class {
1513
1527
  return;
1514
1528
  }
1515
1529
  if (event.method === "input.requested") {
1516
- this.#recordRootInterrupt(event);
1530
+ this.#recordInterrupt(event);
1517
1531
  return;
1518
1532
  }
1519
1533
  if (event.method === "lifecycle") event.params.data;
@@ -1641,21 +1655,22 @@ var StreamController = class {
1641
1655
  }
1642
1656
  }
1643
1657
  /**
1644
- * Mirror root protocol interrupts into the root snapshot.
1658
+ * Mirror protocol interrupts (any namespace) into the root snapshot.
1645
1659
  *
1646
1660
  * This can be called from both the wildcard lifecycle/input watcher and the
1647
1661
  * root content pump. Store-level dedup keeps the user-facing list stable.
1648
1662
  */
1649
- #recordRootInterrupt(event) {
1663
+ #recordInterrupt(event) {
1650
1664
  if (event.method !== "input.requested") return;
1651
- if (!require_namespace.isRootNamespace(event.params.namespace)) return;
1652
1665
  const data = event.params.data;
1653
1666
  const interruptId = data?.interrupt_id;
1654
1667
  if (typeof interruptId !== "string" || this.#resolvedInterrupts.has(interruptId)) return;
1655
1668
  if (this.#hydratedActiveInterruptIds != null && !this.#hydratedActiveInterruptIds.has(interruptId)) return;
1669
+ const namespace = Array.isArray(event.params.namespace) ? [...event.params.namespace] : [...ROOT_NAMESPACE];
1656
1670
  const interrupt = require_interrupts.normalizeInterruptForClient({
1657
1671
  id: interruptId,
1658
- value: data.payload
1672
+ value: data.payload,
1673
+ namespace
1659
1674
  });
1660
1675
  this.rootStore.setState((s) => {
1661
1676
  if (s.interrupts.some((entry) => entry.id === interruptId)) return s;
@@ -1668,6 +1683,21 @@ var StreamController = class {
1668
1683
  });
1669
1684
  }
1670
1685
  /**
1686
+ * Resolve the protocol namespace for a targeted resume.
1687
+ *
1688
+ * Prefer an explicit caller-supplied namespace, then the matching
1689
+ * {@link ThreadStream.interrupts} entry, then the UI mirror on
1690
+ * `rootStore.interrupts`, finally root (`[]`).
1691
+ */
1692
+ #resolveNamespaceForInterruptId(interruptId, explicitNamespace) {
1693
+ if (explicitNamespace != null) return [...explicitNamespace];
1694
+ const fromThread = this.#thread?.interrupts.find((entry) => entry.interruptId === interruptId)?.namespace;
1695
+ if (fromThread != null) return [...fromThread];
1696
+ const fromRoot = this.rootStore.getSnapshot().interrupts.find((entry) => entry.id === interruptId)?.namespace;
1697
+ if (fromRoot != null) return [...fromRoot];
1698
+ return [...ROOT_NAMESPACE];
1699
+ }
1700
+ /**
1671
1701
  * Mark an interrupt resolved for replay filtering and mirror the
1672
1702
  * removal into the root snapshot the framework hooks read.
1673
1703
  */