@langchain/langgraph-sdk 1.9.29 → 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.
- package/dist/stream/controller.cjs +119 -23
- package/dist/stream/controller.cjs.map +1 -1
- package/dist/stream/controller.d.cts.map +1 -1
- package/dist/stream/controller.d.ts.map +1 -1
- package/dist/stream/controller.js +119 -23
- package/dist/stream/controller.js.map +1 -1
- package/dist/stream/lifecycle-loading-tracker.cjs +12 -2
- package/dist/stream/lifecycle-loading-tracker.cjs.map +1 -1
- package/dist/stream/lifecycle-loading-tracker.js +12 -2
- package/dist/stream/lifecycle-loading-tracker.js.map +1 -1
- package/dist/stream/message-reconciliation.cjs +80 -4
- package/dist/stream/message-reconciliation.cjs.map +1 -1
- package/dist/stream/message-reconciliation.js +80 -4
- package/dist/stream/message-reconciliation.js.map +1 -1
- package/dist/stream/projections/messages.cjs +1 -1
- package/dist/stream/projections/messages.cjs.map +1 -1
- package/dist/stream/projections/messages.js +2 -2
- package/dist/stream/projections/messages.js.map +1 -1
- package/dist/stream/root-message-projection.cjs +12 -6
- package/dist/stream/root-message-projection.cjs.map +1 -1
- package/dist/stream/root-message-projection.js +13 -7
- package/dist/stream/root-message-projection.js.map +1 -1
- package/dist/stream/submit-coordinator.cjs +1 -1
- package/dist/stream/submit-coordinator.cjs.map +1 -1
- package/dist/stream/submit-coordinator.js +1 -1
- package/dist/stream/submit-coordinator.js.map +1 -1
- package/package.json +1 -1
|
@@ -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).
|
|
159
|
-
*
|
|
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
|
|
@@ -170,6 +183,14 @@ var StreamController = class {
|
|
|
170
183
|
*/
|
|
171
184
|
#submitGeneration = 0;
|
|
172
185
|
/**
|
|
186
|
+
* Monotonic counter bumped at the start of each `#teardownThread`.
|
|
187
|
+
* The root pump captures the generation when it starts and drops
|
|
188
|
+
* any event whose generation no longer matches — including queued
|
|
189
|
+
* events that `SubscriptionHandle.close()` still drains after the
|
|
190
|
+
* snapshot has already been cleared by `hydrate()`.
|
|
191
|
+
*/
|
|
192
|
+
#rootPumpGeneration = 0;
|
|
193
|
+
/**
|
|
173
194
|
* Thread ids this controller minted client-side on first `submit()`.
|
|
174
195
|
* `hydrate()` skips `threads.getState()` for these — we know there
|
|
175
196
|
* is nothing checkpointed yet. Cleared once dispatch commits the
|
|
@@ -294,13 +315,20 @@ var StreamController = class {
|
|
|
294
315
|
awaitNextTerminal: (signal) => this.#awaitNextTerminal(signal),
|
|
295
316
|
awaitResumedRunTerminal: (signal) => this.#awaitResumedRunTerminal(signal),
|
|
296
317
|
onSubmitStart: () => {
|
|
297
|
-
this.#hydratedActiveInterruptIds = null;
|
|
298
318
|
this.#submitGeneration += 1;
|
|
319
|
+
this.#interruptCommandPending = this.#hydratedActiveInterruptIds != null;
|
|
320
|
+
this.#pendingInterruptEvents = [];
|
|
299
321
|
},
|
|
300
322
|
onRunStart: () => this.#markLocalRunStart(),
|
|
301
|
-
onRunCreated: (runId) =>
|
|
323
|
+
onRunCreated: (runId) => {
|
|
324
|
+
this.#advanceInterruptReplayBarrier();
|
|
325
|
+
this.#notifyCreated(runId);
|
|
326
|
+
},
|
|
302
327
|
onRunCompleted: (reason, runId) => this.#notifyCompleted(reason, runId),
|
|
303
|
-
onRunEnd: () =>
|
|
328
|
+
onRunEnd: () => {
|
|
329
|
+
this.#interruptCommandPending = false;
|
|
330
|
+
this.#markLocalRunEnd();
|
|
331
|
+
},
|
|
304
332
|
beginOptimistic: (input) => this.#beginOptimistic(input),
|
|
305
333
|
settleOptimistic: (handle, event) => this.#settleOptimistic(handle, event)
|
|
306
334
|
});
|
|
@@ -392,13 +420,16 @@ var StreamController = class {
|
|
|
392
420
|
* Suspense boundary remounted against the new id suspends again.
|
|
393
421
|
*/
|
|
394
422
|
this.#resetHydrationPromise();
|
|
395
|
-
await this.#teardownThread();
|
|
396
423
|
/**
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
424
|
+
* Kick teardown so the synchronous abort (unbind, drop onEvent,
|
|
425
|
+
* close the root subscription, reset assemblers) runs before we
|
|
426
|
+
* touch the snapshot. Then reset immediately so the UI does not
|
|
427
|
+
* wait on pump/close. An interrupted thread parks the root pump
|
|
428
|
+
* on `waitForResume()`; awaiting teardown *before* this reset
|
|
429
|
+
* left `threadId` null while `messages` still showed the previous
|
|
430
|
+
* conversation (`useStream` fire-and-forgets `hydrate()`).
|
|
401
431
|
*/
|
|
432
|
+
const teardown = this.#teardownThread();
|
|
402
433
|
this.rootStore.setState(() => ({
|
|
403
434
|
...this.#createInitialSnapshot(),
|
|
404
435
|
threadId: this.#currentThreadId
|
|
@@ -409,6 +440,7 @@ var StreamController = class {
|
|
|
409
440
|
* surprising. Mirrors the legacy `StreamOrchestrator` behaviour.
|
|
410
441
|
*/
|
|
411
442
|
this.queueStore.setState(() => require_submit_coordinator.EMPTY_QUEUE);
|
|
443
|
+
await teardown;
|
|
412
444
|
}
|
|
413
445
|
if (this.#currentThreadId == null) {
|
|
414
446
|
this.rootStore.setState((s) => ({
|
|
@@ -550,7 +582,10 @@ var StreamController = class {
|
|
|
550
582
|
interrupts: activeInterrupts,
|
|
551
583
|
interrupt: activeInterrupts[0]
|
|
552
584
|
}));
|
|
553
|
-
if (this.#submitGeneration === generationAtFetch)
|
|
585
|
+
if (this.#submitGeneration === generationAtFetch) {
|
|
586
|
+
this.#hydratedActiveInterruptIds = activeIds;
|
|
587
|
+
this.#interruptReplayThroughSeq = void 0;
|
|
588
|
+
}
|
|
554
589
|
}
|
|
555
590
|
} catch (error) {
|
|
556
591
|
if (error?.status !== 404) {
|
|
@@ -997,9 +1032,11 @@ var StreamController = class {
|
|
|
997
1032
|
config: options?.config,
|
|
998
1033
|
metadata: options?.metadata
|
|
999
1034
|
});
|
|
1035
|
+
this.#advanceInterruptReplayBarrier();
|
|
1000
1036
|
this.#markInterruptResolvedInRootStore(resolved.interruptId);
|
|
1001
1037
|
}, prepared?.handle);
|
|
1002
1038
|
} catch (error) {
|
|
1039
|
+
this.#discardPendingInterruptEvents();
|
|
1003
1040
|
if (this.#disposed && isAbortLikeError(error)) return;
|
|
1004
1041
|
throw error;
|
|
1005
1042
|
}
|
|
@@ -1066,9 +1103,11 @@ var StreamController = class {
|
|
|
1066
1103
|
config: options?.config,
|
|
1067
1104
|
metadata: options?.metadata
|
|
1068
1105
|
});
|
|
1106
|
+
this.#advanceInterruptReplayBarrier();
|
|
1069
1107
|
for (const { interrupt_id: interruptId } of responses) this.#markInterruptResolvedInRootStore(interruptId);
|
|
1070
1108
|
}, prepared?.handle);
|
|
1071
1109
|
} catch (error) {
|
|
1110
|
+
this.#discardPendingInterruptEvents();
|
|
1072
1111
|
if (this.#disposed && isAbortLikeError(error)) return;
|
|
1073
1112
|
throw error;
|
|
1074
1113
|
}
|
|
@@ -1236,6 +1275,14 @@ var StreamController = class {
|
|
|
1236
1275
|
* Close the current thread stream and reset per-thread assembly state.
|
|
1237
1276
|
*/
|
|
1238
1277
|
async #teardownThread() {
|
|
1278
|
+
/**
|
|
1279
|
+
* Invalidate the in-flight root pump before `close()` unparks it.
|
|
1280
|
+
* `SubscriptionHandle.close()` leaves queued events intact and the
|
|
1281
|
+
* iterator drains them before observing `closed`, so without this
|
|
1282
|
+
* bump a parked interrupted pump would still dispatch stale
|
|
1283
|
+
* `values` / `messages` into a snapshot `hydrate()` already cleared.
|
|
1284
|
+
*/
|
|
1285
|
+
this.#rootPumpGeneration += 1;
|
|
1239
1286
|
const thread = this.#thread;
|
|
1240
1287
|
this.#thread = void 0;
|
|
1241
1288
|
this.registry.bind(void 0);
|
|
@@ -1248,16 +1295,16 @@ var StreamController = class {
|
|
|
1248
1295
|
*/
|
|
1249
1296
|
this.#rootEventListeners.delete(this.#lifecycleLoading.listener);
|
|
1250
1297
|
this.#rootEventListeners.delete(this.#runLifecycleListener);
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1298
|
+
/**
|
|
1299
|
+
* Close first so a pump parked on `waitForResume()` (interrupted
|
|
1300
|
+
* run) unparks even if the unsubscribe RPC is slow. `close()` is
|
|
1301
|
+
* idempotent; `unsubscribe()` is then a no-op once closed.
|
|
1302
|
+
*/
|
|
1303
|
+
const subscription = this.#rootSubscription;
|
|
1254
1304
|
this.#rootSubscription = void 0;
|
|
1305
|
+
subscription?.close();
|
|
1255
1306
|
this.#rootPumpReady = void 0;
|
|
1256
1307
|
this.#rootPumpDeferred = false;
|
|
1257
|
-
try {
|
|
1258
|
-
await this.#rootPump;
|
|
1259
|
-
} catch {}
|
|
1260
|
-
this.#rootPump = void 0;
|
|
1261
1308
|
this.#rootMessages.reset();
|
|
1262
1309
|
this.#rootToolAssembler = new require_tools.ToolCallAssembler();
|
|
1263
1310
|
this.#lifecycleLoading.reset();
|
|
@@ -1268,7 +1315,16 @@ var StreamController = class {
|
|
|
1268
1315
|
this.#localRunDepth = 0;
|
|
1269
1316
|
this.#messageMetadata.reset();
|
|
1270
1317
|
this.#hydratedActiveInterruptIds = null;
|
|
1318
|
+
this.#interruptReplayThroughSeq = void 0;
|
|
1319
|
+
this.#discardPendingInterruptEvents();
|
|
1271
1320
|
this.queueStore.setState(() => require_submit_coordinator.EMPTY_QUEUE);
|
|
1321
|
+
try {
|
|
1322
|
+
await subscription?.unsubscribe();
|
|
1323
|
+
} catch {}
|
|
1324
|
+
try {
|
|
1325
|
+
await this.#rootPump;
|
|
1326
|
+
} catch {}
|
|
1327
|
+
this.#rootPump = void 0;
|
|
1272
1328
|
if (thread != null) {
|
|
1273
1329
|
try {
|
|
1274
1330
|
await thread.close();
|
|
@@ -1292,6 +1348,7 @@ var StreamController = class {
|
|
|
1292
1348
|
*/
|
|
1293
1349
|
#startRootPump(thread) {
|
|
1294
1350
|
if (this.#rootPump != null) return;
|
|
1351
|
+
const pumpGeneration = this.#rootPumpGeneration;
|
|
1295
1352
|
let resolveReady;
|
|
1296
1353
|
this.#rootPumpReady = new Promise((resolve) => {
|
|
1297
1354
|
resolveReady = resolve;
|
|
@@ -1350,6 +1407,14 @@ var StreamController = class {
|
|
|
1350
1407
|
resolveReady = void 0;
|
|
1351
1408
|
});
|
|
1352
1409
|
const subscription = await subscriptionPromise;
|
|
1410
|
+
if (pumpGeneration !== this.#rootPumpGeneration) {
|
|
1411
|
+
resolveReady?.();
|
|
1412
|
+
resolveReady = void 0;
|
|
1413
|
+
try {
|
|
1414
|
+
subscription.close();
|
|
1415
|
+
} catch {}
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1353
1418
|
resolveReady?.();
|
|
1354
1419
|
resolveReady = void 0;
|
|
1355
1420
|
this.#rootSubscription = subscription;
|
|
@@ -1363,9 +1428,9 @@ var StreamController = class {
|
|
|
1363
1428
|
* for every resumed iteration until the subscription is
|
|
1364
1429
|
* permanently closed or the controller is disposed.
|
|
1365
1430
|
*/
|
|
1366
|
-
while (!this.#disposed) {
|
|
1431
|
+
while (!this.#disposed && pumpGeneration === this.#rootPumpGeneration) {
|
|
1367
1432
|
for await (const event of subscription) {
|
|
1368
|
-
if (this.#disposed) break;
|
|
1433
|
+
if (this.#disposed || pumpGeneration !== this.#rootPumpGeneration) break;
|
|
1369
1434
|
/**
|
|
1370
1435
|
* Resilience: isolate per-event dispatch from the pump loop.
|
|
1371
1436
|
*
|
|
@@ -1397,7 +1462,7 @@ var StreamController = class {
|
|
|
1397
1462
|
this.#onRootEvent(event);
|
|
1398
1463
|
} catch {}
|
|
1399
1464
|
}
|
|
1400
|
-
if (this.#disposed) break;
|
|
1465
|
+
if (this.#disposed || pumpGeneration !== this.#rootPumpGeneration) break;
|
|
1401
1466
|
if (!subscription.isPaused) break;
|
|
1402
1467
|
await subscription.waitForResume();
|
|
1403
1468
|
}
|
|
@@ -1665,7 +1730,13 @@ var StreamController = class {
|
|
|
1665
1730
|
const data = event.params.data;
|
|
1666
1731
|
const interruptId = data?.interrupt_id;
|
|
1667
1732
|
if (typeof interruptId !== "string" || this.#resolvedInterrupts.has(interruptId)) return;
|
|
1668
|
-
|
|
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;
|
|
1669
1740
|
const namespace = Array.isArray(event.params.namespace) ? [...event.params.namespace] : [...ROOT_NAMESPACE];
|
|
1670
1741
|
const interrupt = require_interrupts.normalizeInterruptForClient({
|
|
1671
1742
|
id: interruptId,
|
|
@@ -1683,6 +1754,31 @@ var StreamController = class {
|
|
|
1683
1754
|
});
|
|
1684
1755
|
}
|
|
1685
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
|
+
/**
|
|
1686
1782
|
* Resolve the protocol namespace for a targeted resume.
|
|
1687
1783
|
*
|
|
1688
1784
|
* Prefer an explicit caller-supplied namespace, then the matching
|