@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.
- package/dist/client/stream/types.d.cts +4 -4
- package/dist/client/stream/types.d.ts +4 -4
- package/dist/client/threads/index.cjs +3 -0
- package/dist/client/threads/index.cjs.map +1 -1
- package/dist/client/threads/index.js +3 -0
- package/dist/client/threads/index.js.map +1 -1
- package/dist/schema.d.cts +7 -0
- package/dist/schema.d.cts.map +1 -1
- package/dist/schema.d.ts +7 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/stream/controller.cjs +87 -57
- package/dist/stream/controller.cjs.map +1 -1
- package/dist/stream/controller.d.cts +12 -23
- package/dist/stream/controller.d.cts.map +1 -1
- package/dist/stream/controller.d.ts +12 -23
- package/dist/stream/controller.d.ts.map +1 -1
- package/dist/stream/controller.js +87 -57
- package/dist/stream/controller.js.map +1 -1
- package/dist/stream/projections/channel.cjs +4 -6
- package/dist/stream/projections/channel.cjs.map +1 -1
- package/dist/stream/projections/channel.d.cts.map +1 -1
- package/dist/stream/projections/channel.d.ts.map +1 -1
- package/dist/stream/projections/channel.js +4 -6
- package/dist/stream/projections/channel.js.map +1 -1
- package/dist/stream/submit-coordinator.cjs +7 -5
- package/dist/stream/submit-coordinator.cjs.map +1 -1
- package/dist/stream/submit-coordinator.js +7 -5
- package/dist/stream/submit-coordinator.js.map +1 -1
- package/dist/stream/types.d.cts +3 -3
- package/dist/stream/types.d.ts +3 -3
- package/dist/utils/async_caller.cjs +1 -1
- package/dist/utils/async_caller.cjs.map +1 -1
- package/dist/utils/async_caller.d.cts.map +1 -1
- package/dist/utils/async_caller.d.ts.map +1 -1
- package/dist/utils/async_caller.js +1 -1
- package/dist/utils/async_caller.js.map +1 -1
- package/package.json +10 -10
|
@@ -170,13 +170,23 @@ var StreamController = class {
|
|
|
170
170
|
*/
|
|
171
171
|
#submitGeneration = 0;
|
|
172
172
|
/**
|
|
173
|
-
* Thread ids
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
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(
|
|
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
|
-
*
|
|
898
|
-
* `
|
|
899
|
-
*
|
|
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`
|
|
904
|
-
*
|
|
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: []
|
|
913
|
-
*
|
|
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
|
|
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
|
-
* @
|
|
943
|
-
*
|
|
944
|
-
*
|
|
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
|
|
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:
|
|
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
|
|
1205
|
-
* `
|
|
1206
|
-
*
|
|
1207
|
-
* `#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
|
-
*
|
|
1415
|
-
* subgraph) are not observable via the narrow content pump.
|
|
1416
|
-
* `
|
|
1417
|
-
*
|
|
1418
|
-
*
|
|
1419
|
-
*
|
|
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.#
|
|
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.#
|
|
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
|
|
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
|
-
#
|
|
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
|
*/
|