@langchain/langgraph-sdk 1.9.29 → 1.9.30

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.
@@ -170,6 +170,14 @@ var StreamController = class {
170
170
  */
171
171
  #submitGeneration = 0;
172
172
  /**
173
+ * Monotonic counter bumped at the start of each `#teardownThread`.
174
+ * The root pump captures the generation when it starts and drops
175
+ * any event whose generation no longer matches — including queued
176
+ * events that `SubscriptionHandle.close()` still drains after the
177
+ * snapshot has already been cleared by `hydrate()`.
178
+ */
179
+ #rootPumpGeneration = 0;
180
+ /**
173
181
  * Thread ids this controller minted client-side on first `submit()`.
174
182
  * `hydrate()` skips `threads.getState()` for these — we know there
175
183
  * is nothing checkpointed yet. Cleared once dispatch commits the
@@ -392,13 +400,16 @@ var StreamController = class {
392
400
  * Suspense boundary remounted against the new id suspends again.
393
401
  */
394
402
  this.#resetHydrationPromise();
395
- await this.#teardownThread();
396
403
  /**
397
- * Reset UI-facing snapshot so stale messages/values/tool-calls
398
- * from the previous thread don't bleed into the new one. The
399
- * new thread's state (if any) is then populated below via
400
- * `#applyValues`.
404
+ * Kick teardown so the synchronous abort (unbind, drop onEvent,
405
+ * close the root subscription, reset assemblers) runs before we
406
+ * touch the snapshot. Then reset immediately so the UI does not
407
+ * wait on pump/close. An interrupted thread parks the root pump
408
+ * on `waitForResume()`; awaiting teardown *before* this reset
409
+ * left `threadId` null while `messages` still showed the previous
410
+ * conversation (`useStream` fire-and-forgets `hydrate()`).
401
411
  */
412
+ const teardown = this.#teardownThread();
402
413
  this.rootStore.setState(() => ({
403
414
  ...this.#createInitialSnapshot(),
404
415
  threadId: this.#currentThreadId
@@ -409,6 +420,7 @@ var StreamController = class {
409
420
  * surprising. Mirrors the legacy `StreamOrchestrator` behaviour.
410
421
  */
411
422
  this.queueStore.setState(() => require_submit_coordinator.EMPTY_QUEUE);
423
+ await teardown;
412
424
  }
413
425
  if (this.#currentThreadId == null) {
414
426
  this.rootStore.setState((s) => ({
@@ -1236,6 +1248,14 @@ var StreamController = class {
1236
1248
  * Close the current thread stream and reset per-thread assembly state.
1237
1249
  */
1238
1250
  async #teardownThread() {
1251
+ /**
1252
+ * Invalidate the in-flight root pump before `close()` unparks it.
1253
+ * `SubscriptionHandle.close()` leaves queued events intact and the
1254
+ * iterator drains them before observing `closed`, so without this
1255
+ * bump a parked interrupted pump would still dispatch stale
1256
+ * `values` / `messages` into a snapshot `hydrate()` already cleared.
1257
+ */
1258
+ this.#rootPumpGeneration += 1;
1239
1259
  const thread = this.#thread;
1240
1260
  this.#thread = void 0;
1241
1261
  this.registry.bind(void 0);
@@ -1248,16 +1268,16 @@ var StreamController = class {
1248
1268
  */
1249
1269
  this.#rootEventListeners.delete(this.#lifecycleLoading.listener);
1250
1270
  this.#rootEventListeners.delete(this.#runLifecycleListener);
1251
- try {
1252
- await this.#rootSubscription?.unsubscribe();
1253
- } catch {}
1271
+ /**
1272
+ * Close first so a pump parked on `waitForResume()` (interrupted
1273
+ * run) unparks even if the unsubscribe RPC is slow. `close()` is
1274
+ * idempotent; `unsubscribe()` is then a no-op once closed.
1275
+ */
1276
+ const subscription = this.#rootSubscription;
1254
1277
  this.#rootSubscription = void 0;
1278
+ subscription?.close();
1255
1279
  this.#rootPumpReady = void 0;
1256
1280
  this.#rootPumpDeferred = false;
1257
- try {
1258
- await this.#rootPump;
1259
- } catch {}
1260
- this.#rootPump = void 0;
1261
1281
  this.#rootMessages.reset();
1262
1282
  this.#rootToolAssembler = new require_tools.ToolCallAssembler();
1263
1283
  this.#lifecycleLoading.reset();
@@ -1269,6 +1289,13 @@ var StreamController = class {
1269
1289
  this.#messageMetadata.reset();
1270
1290
  this.#hydratedActiveInterruptIds = null;
1271
1291
  this.queueStore.setState(() => require_submit_coordinator.EMPTY_QUEUE);
1292
+ try {
1293
+ await subscription?.unsubscribe();
1294
+ } catch {}
1295
+ try {
1296
+ await this.#rootPump;
1297
+ } catch {}
1298
+ this.#rootPump = void 0;
1272
1299
  if (thread != null) {
1273
1300
  try {
1274
1301
  await thread.close();
@@ -1292,6 +1319,7 @@ var StreamController = class {
1292
1319
  */
1293
1320
  #startRootPump(thread) {
1294
1321
  if (this.#rootPump != null) return;
1322
+ const pumpGeneration = this.#rootPumpGeneration;
1295
1323
  let resolveReady;
1296
1324
  this.#rootPumpReady = new Promise((resolve) => {
1297
1325
  resolveReady = resolve;
@@ -1350,6 +1378,14 @@ var StreamController = class {
1350
1378
  resolveReady = void 0;
1351
1379
  });
1352
1380
  const subscription = await subscriptionPromise;
1381
+ if (pumpGeneration !== this.#rootPumpGeneration) {
1382
+ resolveReady?.();
1383
+ resolveReady = void 0;
1384
+ try {
1385
+ subscription.close();
1386
+ } catch {}
1387
+ return;
1388
+ }
1353
1389
  resolveReady?.();
1354
1390
  resolveReady = void 0;
1355
1391
  this.#rootSubscription = subscription;
@@ -1363,9 +1399,9 @@ var StreamController = class {
1363
1399
  * for every resumed iteration until the subscription is
1364
1400
  * permanently closed or the controller is disposed.
1365
1401
  */
1366
- while (!this.#disposed) {
1402
+ while (!this.#disposed && pumpGeneration === this.#rootPumpGeneration) {
1367
1403
  for await (const event of subscription) {
1368
- if (this.#disposed) break;
1404
+ if (this.#disposed || pumpGeneration !== this.#rootPumpGeneration) break;
1369
1405
  /**
1370
1406
  * Resilience: isolate per-event dispatch from the pump loop.
1371
1407
  *
@@ -1397,7 +1433,7 @@ var StreamController = class {
1397
1433
  this.#onRootEvent(event);
1398
1434
  } catch {}
1399
1435
  }
1400
- if (this.#disposed) break;
1436
+ if (this.#disposed || pumpGeneration !== this.#rootPumpGeneration) break;
1401
1437
  if (!subscription.isPaused) break;
1402
1438
  await subscription.waitForResume();
1403
1439
  }