@langchain/langgraph-sdk 1.9.3 → 1.9.5
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/README.md +20 -20
- package/dist/client/base.cjs.map +1 -1
- package/dist/client/base.d.cts +37 -0
- package/dist/client/base.d.cts.map +1 -1
- package/dist/client/base.d.ts +37 -0
- package/dist/client/base.d.ts.map +1 -1
- package/dist/client/base.js.map +1 -1
- package/dist/client/index.d.cts +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/stream/handles/index.d.ts +1 -1
- package/dist/client/stream/handles/subagents.cjs +1 -1
- package/dist/client/stream/handles/subagents.cjs.map +1 -1
- package/dist/client/stream/handles/subagents.d.cts +2 -2
- package/dist/client/stream/handles/subagents.d.cts.map +1 -1
- package/dist/client/stream/handles/subagents.d.ts +2 -2
- package/dist/client/stream/handles/subagents.d.ts.map +1 -1
- package/dist/client/stream/handles/subagents.js +2 -2
- package/dist/client/stream/handles/subagents.js.map +1 -1
- package/dist/client/stream/handles/subgraphs.cjs +1 -1
- package/dist/client/stream/handles/subgraphs.cjs.map +1 -1
- package/dist/client/stream/handles/subgraphs.d.cts +2 -2
- package/dist/client/stream/handles/subgraphs.d.cts.map +1 -1
- package/dist/client/stream/handles/subgraphs.d.ts +2 -2
- package/dist/client/stream/handles/subgraphs.d.ts.map +1 -1
- package/dist/client/stream/handles/subgraphs.js +2 -2
- package/dist/client/stream/handles/subgraphs.js.map +1 -1
- package/dist/client/stream/handles/tools.cjs +124 -52
- package/dist/client/stream/handles/tools.cjs.map +1 -1
- package/dist/client/stream/handles/tools.d.cts +59 -13
- package/dist/client/stream/handles/tools.d.cts.map +1 -1
- package/dist/client/stream/handles/tools.d.ts +59 -13
- package/dist/client/stream/handles/tools.d.ts.map +1 -1
- package/dist/client/stream/handles/tools.js +122 -53
- package/dist/client/stream/handles/tools.js.map +1 -1
- package/dist/client/stream/index.cjs +2 -2
- package/dist/client/stream/index.cjs.map +1 -1
- package/dist/client/stream/index.d.cts +3 -3
- package/dist/client/stream/index.d.cts.map +1 -1
- package/dist/client/stream/index.d.ts +3 -3
- package/dist/client/stream/index.d.ts.map +1 -1
- package/dist/client/stream/index.js +3 -3
- package/dist/client/stream/index.js.map +1 -1
- package/dist/headless-tools.cjs +3 -0
- package/dist/headless-tools.cjs.map +1 -1
- package/dist/headless-tools.js +3 -0
- package/dist/headless-tools.js.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/stream/controller.cjs +51 -1
- 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 +51 -1
- package/dist/stream/controller.js.map +1 -1
- package/dist/stream/index.cjs +3 -0
- package/dist/stream/index.d.cts +5 -5
- package/dist/stream/index.d.ts +5 -5
- package/dist/stream/index.js +2 -1
- package/dist/stream/lifecycle-loading-tracker.cjs +1 -1
- package/dist/stream/lifecycle-loading-tracker.cjs.map +1 -1
- package/dist/stream/lifecycle-loading-tracker.js +1 -1
- package/dist/stream/lifecycle-loading-tracker.js.map +1 -1
- package/dist/stream/projections/tool-calls.cjs.map +1 -1
- package/dist/stream/projections/tool-calls.js.map +1 -1
- package/dist/stream/submit-coordinator.cjs +37 -4
- package/dist/stream/submit-coordinator.cjs.map +1 -1
- package/dist/stream/submit-coordinator.d.cts.map +1 -1
- package/dist/stream/submit-coordinator.d.ts.map +1 -1
- package/dist/stream/submit-coordinator.js +37 -4
- package/dist/stream/submit-coordinator.js.map +1 -1
- package/dist/stream/types-inference.d.cts +65 -7
- package/dist/stream/types-inference.d.cts.map +1 -1
- package/dist/stream/types-inference.d.ts +65 -7
- package/dist/stream/types-inference.d.ts.map +1 -1
- package/dist/stream/types.d.cts +40 -21
- package/dist/stream/types.d.cts.map +1 -1
- package/dist/stream/types.d.ts +40 -21
- package/dist/stream/types.d.ts.map +1 -1
- package/dist/types.messages.d.cts +38 -1
- package/dist/types.messages.d.cts.map +1 -1
- package/dist/types.messages.d.ts +38 -1
- package/dist/types.messages.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.cts","names":[],"sources":["../../src/stream/controller.ts"],"mappings":";;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"controller.d.cts","names":[],"sources":["../../src/stream/controller.ts"],"mappings":";;;;;;;;;;;;;;AAkGA;;cAAa,kBAAA,WAA6B,OAAA;;AAkC1C;;;;;;;;;;;;cAAa,gBAAA,4BACgB,MAAA,8EAEO,MAAA;EAAA;WAEzB,SAAA,EAAW,WAAA,CAAY,YAAA,CAAa,SAAA,EAAW,aAAA;EAAA,SAC/C,aAAA,EAAe,WAAA,CAAY,WAAA;EAAA,SAC3B,aAAA,EAAe,WAAA,CAAY,WAAA;EAAA,SAC3B,mBAAA,EAAqB,WAAA,CAAY,iBAAA;EAAA,SACjC,oBAAA,EAAsB,WAAA,CAAY,kBAAA;EAAA,SAClC,UAAA,EAAY,WAAA,CAAY,uBAAA,CAAwB,SAAA;EAAA,SAChD,QAAA,EAAU,eAAA;EAyF0B;;;;;EAA7C,WAAA,CAAY,OAAA,EAAS,uBAAA,CAAwB,SAAA;EAqUjC;;;;;;;EAAA,IA9NR,gBAAA,CAAA,GAAoB,OAAA;EAyZH;;;;;;;;EApXf,OAAA,CAAQ,QAAA,mBAA2B,OAAA;EA3OhC;;;;;;EAkaH,MAAA,CACJ,KAAA,WACA,OAAA,GAAU,mBAAA,CAAoB,SAAA,EAAW,gBAAA,IACxC,OAAA;EApaiC;;;EA2a9B,IAAA,CAAA,GAAQ,OAAA;EAzaL;;;;;;;;;;;EAyeH,YAAA,CAAa,EAAA,WAAa,OAAA;EA7YhC;;;EAoZM,UAAA,CAAA,GAAc,OAAA;EA7ShB;;;;;;EAuTE,OAAA,CACJ,QAAA,WACA,MAAA;IAAW,WAAA;IAAqB,SAAA;EAAA,IAC/B,OAAA;EA5FD;;;EA4HI,OAAA,CAAA,GAAW,OAAA;EApDX;;;;;;;;;;;;;;;EA6EN,QAAA,CAAA;EAqCA;;;EAXA,SAAA,CAAA,GAAa,YAAA;EAYyC;;;;;;;EADtD,eAAA,CACE,QAAA,GAAW,MAAA,EAAQ,YAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.ts","names":[],"sources":["../../src/stream/controller.ts"],"mappings":";;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"controller.d.ts","names":[],"sources":["../../src/stream/controller.ts"],"mappings":";;;;;;;;;;;;;;;AAkGA;cAAa,kBAAA,WAA6B,OAAA;;;AAkC1C;;;;;;;;;;;cAAa,gBAAA,4BACgB,MAAA,8EAEO,MAAA;EAAA;WAEzB,SAAA,EAAW,WAAA,CAAY,YAAA,CAAa,SAAA,EAAW,aAAA;EAAA,SAC/C,aAAA,EAAe,WAAA,CAAY,WAAA;EAAA,SAC3B,aAAA,EAAe,WAAA,CAAY,WAAA;EAAA,SAC3B,mBAAA,EAAqB,WAAA,CAAY,iBAAA;EAAA,SACjC,oBAAA,EAAsB,WAAA,CAAY,kBAAA;EAAA,SAClC,UAAA,EAAY,WAAA,CAAY,uBAAA,CAAwB,SAAA;EAAA,SAChD,QAAA,EAAU,eAAA;EAAA;;;;;EAyFnB,WAAA,CAAY,OAAA,EAAS,uBAAA,CAAwB,SAAA;EAqUF;;;;;;;EAAA,IA9NvC,gBAAA,CAAA,GAAoB,OAAA;EA6YX;;;;;;;;EAxWP,OAAA,CAAQ,QAAA,mBAA2B,OAAA;;;;;;;EAuLnC,MAAA,CACJ,KAAA,WACA,OAAA,GAAU,mBAAA,CAAoB,SAAA,EAAW,gBAAA,IACxC,OAAA;EApaqB;;;EA2alB,IAAA,CAAA,GAAQ,OAAA;EA1asB;;;;;;;;;;;EA0e9B,YAAA,CAAa,EAAA,WAAa,OAAA;EAteb;;;EA6eb,UAAA,CAAA,GAAc,OAAA;EApZR;;;;;;EA8ZN,OAAA,CACJ,QAAA,WACA,MAAA;IAAW,WAAA;IAAqB,SAAA;EAAA,IAC/B,OAAA;EA5FwC;;;EA4HrC,OAAA,CAAA,GAAW,OAAA;EApHH;;;;;;;;;;;;;;;EA6Id,QAAA,CAAA;EA0Ba;;;EAAb,SAAA,CAAA,GAAa,YAAA;EAYX;;;;;;;EADF,eAAA,CACE,QAAA,GAAW,MAAA,EAAQ,YAAA;AAAA"}
|
|
@@ -17,6 +17,12 @@ function isAbortLikeError(error) {
|
|
|
17
17
|
const maybeError = error;
|
|
18
18
|
return maybeError.name === "AbortError" || typeof maybeError.message === "string" && maybeError.message.includes("aborted");
|
|
19
19
|
}
|
|
20
|
+
function lifecycleReason(event) {
|
|
21
|
+
if (event === "completed") return "success";
|
|
22
|
+
if (event === "failed") return "error";
|
|
23
|
+
if (event === "interrupted") return "interrupt";
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
20
26
|
const ROOT_NAMESPACE = [];
|
|
21
27
|
/**
|
|
22
28
|
* Channel set covered by the always-on root subscription. Exported so
|
|
@@ -104,6 +110,8 @@ var StreamController = class {
|
|
|
104
110
|
#selfCreatedThreadIds = /* @__PURE__ */ new Set();
|
|
105
111
|
#rootEventListeners = /* @__PURE__ */ new Set();
|
|
106
112
|
#rootBus;
|
|
113
|
+
#activeRunId;
|
|
114
|
+
#localRunDepth = 0;
|
|
107
115
|
/**
|
|
108
116
|
* Single-shot hydration promise. Exposed via `hydrationPromise`
|
|
109
117
|
* so Suspense wrappers can throw it until the first hydrate
|
|
@@ -184,7 +192,11 @@ var StreamController = class {
|
|
|
184
192
|
onSubmitStart: () => {
|
|
185
193
|
this.#hydratedActiveInterruptIds = null;
|
|
186
194
|
this.#submitGeneration += 1;
|
|
187
|
-
}
|
|
195
|
+
},
|
|
196
|
+
onRunStart: () => this.#markLocalRunStart(),
|
|
197
|
+
onRunCreated: (runId) => this.#notifyCreated(runId),
|
|
198
|
+
onRunCompleted: (reason, runId) => this.#notifyCompleted(reason, runId),
|
|
199
|
+
onRunEnd: () => this.#markLocalRunEnd()
|
|
188
200
|
});
|
|
189
201
|
this.#hydrationPromise = this.#createHydrationPromise();
|
|
190
202
|
/**
|
|
@@ -416,6 +428,40 @@ var StreamController = class {
|
|
|
416
428
|
async stop() {
|
|
417
429
|
await this.#submitter.stop();
|
|
418
430
|
}
|
|
431
|
+
#markLocalRunStart() {
|
|
432
|
+
this.#localRunDepth += 1;
|
|
433
|
+
}
|
|
434
|
+
#markLocalRunEnd() {
|
|
435
|
+
this.#localRunDepth = Math.max(0, this.#localRunDepth - 1);
|
|
436
|
+
}
|
|
437
|
+
#notifyCreated(runId) {
|
|
438
|
+
this.#activeRunId = runId;
|
|
439
|
+
try {
|
|
440
|
+
this.#options.onCreated?.({ runId });
|
|
441
|
+
} catch {}
|
|
442
|
+
}
|
|
443
|
+
#notifyCompleted(reason, runId = this.#activeRunId) {
|
|
444
|
+
if (runId != null && runId === this.#activeRunId) this.#activeRunId = void 0;
|
|
445
|
+
setTimeout(() => {
|
|
446
|
+
if (this.#disposed) return;
|
|
447
|
+
try {
|
|
448
|
+
this.#options.onCompleted?.(runId == null ? { reason } : {
|
|
449
|
+
runId,
|
|
450
|
+
reason
|
|
451
|
+
});
|
|
452
|
+
} catch {}
|
|
453
|
+
}, 0);
|
|
454
|
+
}
|
|
455
|
+
#runLifecycleListener = (event) => {
|
|
456
|
+
if (this.#localRunDepth > 0) return;
|
|
457
|
+
if (event.method !== "lifecycle") return;
|
|
458
|
+
if (!isRootNamespace(event.params.namespace)) return;
|
|
459
|
+
if (!this.rootStore.getSnapshot().isLoading) return;
|
|
460
|
+
const lifecycle = event.params.data;
|
|
461
|
+
const reason = lifecycleReason(lifecycle?.event);
|
|
462
|
+
if (reason == null) return;
|
|
463
|
+
this.#notifyCompleted(reason);
|
|
464
|
+
};
|
|
419
465
|
/**
|
|
420
466
|
* Cancel a queued submission by id. Returns `true` when the entry
|
|
421
467
|
* was found and removed, `false` otherwise.
|
|
@@ -634,6 +680,7 @@ var StreamController = class {
|
|
|
634
680
|
* listener set (a new one is installed in `#startRootPump`).
|
|
635
681
|
*/
|
|
636
682
|
this.#rootEventListeners.delete(this.#lifecycleLoading.listener);
|
|
683
|
+
this.#rootEventListeners.delete(this.#runLifecycleListener);
|
|
637
684
|
try {
|
|
638
685
|
await this.#rootSubscription?.unsubscribe();
|
|
639
686
|
} catch {}
|
|
@@ -647,6 +694,8 @@ var StreamController = class {
|
|
|
647
694
|
this.#rootMessages.reset();
|
|
648
695
|
this.#rootToolAssembler = new ToolCallAssembler();
|
|
649
696
|
this.#lifecycleLoading.reset();
|
|
697
|
+
this.#activeRunId = void 0;
|
|
698
|
+
this.#localRunDepth = 0;
|
|
650
699
|
this.#messageMetadata.reset();
|
|
651
700
|
this.#hydratedActiveInterruptIds = null;
|
|
652
701
|
this.queueStore.setState(() => EMPTY_QUEUE);
|
|
@@ -695,6 +744,7 @@ var StreamController = class {
|
|
|
695
744
|
* that fires before any subscription event arrives.
|
|
696
745
|
*/
|
|
697
746
|
this.#rootEventListeners.add(this.#lifecycleLoading.listener);
|
|
747
|
+
this.#rootEventListeners.add(this.#runLifecycleListener);
|
|
698
748
|
this.#rootPump = (async () => {
|
|
699
749
|
try {
|
|
700
750
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.js","names":["#options","#messagesKey","#subagents","#subgraphs","#messageMetadata","#resolvedInterrupts","#selfCreatedThreadIds","#rootEventListeners","#rootBus","#threadListeners","#currentThreadId","#createInitialSnapshot","#rootMessages","#lifecycleLoading","#disposed","#submitter","#ensureThread","#startDeferredRootPump","#abandonDeferredRootPump","#rootPumpReady","#awaitNextTerminal","#latestUnresolvedInterrupt","#hydratedActiveInterruptIds","#submitGeneration","#hydrationPromise","#createHydrationPromise","#resolveHydration","#rejectHydration","#resetHydrationPromise","#teardownThread","#applyValues","#thread","#cancelPendingDispose","#pendingDisposeTimer","#rootPumpDeferred","#startRootPump","#notifyThreadListeners","#threadEventUnsubscribe","#rootSubscription","#rootPump","#rootToolAssembler","#onWildcardEvent","#usesEventStreamTransport","#onRootEvent","#recordRootInterrupt"],"sources":["../../src/stream/controller.ts"],"sourcesContent":["/**\n * Framework-agnostic controller for the experimental v2 stream.\n *\n * Responsibilities:\n * - Owns at most one {@link ThreadStream} at a time (swapped on\n * `hydrate(newThreadId)` or `dispose`).\n * - Exposes three always-on observable surfaces via {@link StreamStore}:\n * - `rootStore` : root values/messages/toolCalls/interrupts/…\n * - `subagentStore` : discovery map of subagents (no content)\n * - `subgraphStore` : discovery map of subgraphs (no content)\n * - Owns a {@link ChannelRegistry} that framework selector hooks\n * (`useMessages`, `useToolCalls`, `useExtension`, `useChannel`)\n * use to lazily open per-namespace subscriptions.\n * - Imperative run surface: `submit`, `stop`, `respond`, `joinStream`.\n *\n * A single multi-channel subscription (`values`, `lifecycle`, `input`,\n * `messages`, `tools`) powers every always-on projection and both\n * discovery runners. Selector hooks add their own (deduped)\n * subscriptions on top — so even a UI with many subagents only opens\n * one extra subscription per `(channels, namespace)` actually\n * rendered on screen.\n */\nimport { AIMessage, type BaseMessage } from \"@langchain/core/messages\";\nimport type {\n Channel,\n Event,\n LifecycleEvent,\n MessagesEvent,\n ToolsEvent,\n ValuesEvent,\n} from \"@langchain/protocol\";\nimport type { Interrupt } from \"../schema.js\";\nimport type { ThreadStream } from \"../client/stream/index.js\";\nimport type { SubscriptionHandle } from \"../client/stream/index.js\";\nimport { ToolCallAssembler } from \"../client/stream/handles/tools.js\";\nimport { ensureMessageInstances } from \"../ui/messages.js\";\nimport type { Message } from \"../types.messages.js\";\nimport { StreamStore } from \"./store.js\";\nimport { ChannelRegistry } from \"./channel-registry.js\";\nimport {\n SubagentDiscovery,\n type SubagentMap,\n SubgraphDiscovery,\n type SubgraphMap,\n type SubgraphByNodeMap,\n} from \"./discovery/index.js\";\nimport {\n isInternalWorkNamespace,\n isLegacySubagentNamespace,\n isRootNamespace,\n} from \"./namespace.js\";\nimport {\n MessageMetadataTracker,\n type CheckpointEnvelope,\n type MessageMetadata,\n type MessageMetadataMap,\n} from \"./message-metadata-tracker.js\";\nimport { LifecycleLoadingTracker } from \"./lifecycle-loading-tracker.js\";\nimport { RootMessageProjection } from \"./root-message-projection.js\";\nimport {\n EMPTY_QUEUE,\n SubmitCoordinator,\n type SubmissionQueueEntry,\n type SubmissionQueueSnapshot,\n} from \"./submit-coordinator.js\";\nimport { upsertToolCall } from \"./tool-calls.js\";\nimport type {\n RootEventBus,\n RootSnapshot,\n StreamControllerOptions,\n StreamSubmitOptions,\n} from \"./types.js\";\n\nfunction isAbortLikeError(error: unknown): boolean {\n if (error == null || typeof error !== \"object\") return false;\n const maybeError = error as { name?: unknown; message?: unknown };\n return (\n maybeError.name === \"AbortError\" ||\n (typeof maybeError.message === \"string\" &&\n maybeError.message.includes(\"aborted\"))\n );\n}\n\nconst ROOT_NAMESPACE: readonly string[] = [];\n\n/**\n * Channel set covered by the always-on root subscription. Exported so\n * projections (and transports) can reason about what the root pump\n * already delivers before opening additional server subscriptions.\n */\nexport const ROOT_PUMP_CHANNELS: readonly Channel[] = [\n \"values\",\n \"checkpoints\",\n \"lifecycle\",\n \"input\",\n \"messages\",\n \"tools\",\n];\n\ninterface ResolvedInterrupt {\n interruptId: string;\n namespace: string[];\n}\n\nexport type {\n MessageMetadata,\n MessageMetadataMap,\n SubmissionQueueEntry,\n SubmissionQueueSnapshot,\n};\n\n/**\n * Coordinates one thread's protocol-v2 stream and exposes stable\n * observable projections for framework bindings.\n *\n * The controller owns the root subscription, lazily binds scoped\n * projections through {@link ChannelRegistry}, and normalizes protocol\n * events into class-message, tool-call, discovery, interrupt, and queue\n * stores.\n *\n * @typeParam StateType - Shape of the graph state exposed on `values`.\n * @typeParam InterruptType - Shape of protocol interrupt payloads.\n * @typeParam ConfigurableType - Shape of `config.configurable` accepted by submit.\n */\nexport class StreamController<\n StateType extends object = Record<string, unknown>,\n InterruptType = unknown,\n ConfigurableType extends object = Record<string, unknown>,\n> {\n readonly rootStore: StreamStore<RootSnapshot<StateType, InterruptType>>;\n readonly subagentStore: StreamStore<SubagentMap>;\n readonly subgraphStore: StreamStore<SubgraphMap>;\n readonly subgraphByNodeStore: StreamStore<SubgraphByNodeMap>;\n readonly messageMetadataStore: StreamStore<MessageMetadataMap>;\n readonly queueStore: StreamStore<SubmissionQueueSnapshot<StateType>>;\n readonly registry: ChannelRegistry;\n\n readonly #options: StreamControllerOptions<StateType>;\n readonly #messagesKey: string;\n readonly #subagents = new SubagentDiscovery();\n readonly #subgraphs = new SubgraphDiscovery();\n readonly #messageMetadata = new MessageMetadataTracker();\n\n #thread: ThreadStream | undefined;\n #currentThreadId: string | null;\n #rootSubscription: SubscriptionHandle<Event> | undefined;\n #rootPump: Promise<void> | undefined;\n #rootPumpReady: Promise<void> | undefined;\n /**\n * `true` while a self-created thread has its root pump deferred until\n * the first `submitRun` / `respondInput` commits the thread row\n * server-side. See `#ensureThread` and `#startDeferredRootPump`.\n */\n #rootPumpDeferred = false;\n #threadEventUnsubscribe: (() => void) | undefined;\n #disposed = false;\n #pendingDisposeTimer: ReturnType<typeof setTimeout> | null = null;\n readonly #resolvedInterrupts = new Set<string>();\n /**\n * Set of interrupt IDs the server reports as currently *active* on\n * the thread (from `state.tasks[].interrupts`). Populated during\n * {@link hydrate} from `client.threads.getState()` and used as a\n * strict allowlist when processing replayed `input.requested`\n * events from the persistent SSE subscription. Without this guard,\n * SSE replay re-adds historically-requested interrupts that have\n * since been resolved (no `input.responded` event exists in the\n * protocol, so the SDK has no other way to tell replay from live\n * for an idle thread). `null` outside the hydrate-window so\n * genuinely new live interrupts on an active run aren't filtered;\n * cleared at the start of `submit()` for the same reason.\n */\n #hydratedActiveInterruptIds: Set<string> | null = null;\n /**\n * Monotonic counter bumped at the start of each `submit()` and used\n * by {@link hydrate} to skip its post-fetch allowlist write when a\n * submit started while the state fetch was in flight. Without this\n * guard, a submit-then-hydrate race could re-install a stale\n * allowlist that filters out genuinely-new live interrupts emitted\n * by the just-started run.\n */\n #submitGeneration = 0;\n /**\n * Thread ids we minted client-side on first `submit()`. Keeping them\n * here lets `hydrate()` skip the `threads.getState()` round-trip —\n * we know there is nothing checkpointed server-side yet (and the\n * request would 404 and surface a spurious error to the UI).\n */\n readonly #selfCreatedThreadIds = new Set<string>();\n readonly #rootEventListeners = new Set<(event: Event) => void>();\n readonly #rootBus: RootEventBus;\n\n /**\n * Single-shot hydration promise. Exposed via `hydrationPromise`\n * so Suspense wrappers can throw it until the first hydrate\n * completes (resolve) or fails (reject). Reset whenever a new\n * hydrate cycle begins so `<Suspense>` boundaries re-suspend on\n * thread switch.\n */\n #hydrationPromise: Promise<void>;\n #resolveHydration!: () => void;\n #rejectHydration!: (error: unknown) => void;\n\n /**\n * Tool assembler lives for the lifetime of a thread; reset on\n * rebind so a fresh thread starts with a clean slate.\n */\n #rootToolAssembler = new ToolCallAssembler();\n #rootMessages!: RootMessageProjection<StateType, InterruptType>;\n #lifecycleLoading!: LifecycleLoadingTracker<\n RootSnapshot<StateType, InterruptType>\n >;\n #submitter!: SubmitCoordinator<StateType, InterruptType, ConfigurableType>;\n\n readonly #threadListeners = new Set<\n (thread: ThreadStream | undefined) => void\n >();\n\n /**\n * Create a controller around a LangGraph client and optional initial thread.\n *\n * @param options - Runtime configuration, client, thread id, and initial state.\n */\n constructor(options: StreamControllerOptions<StateType>) {\n this.#options = options;\n this.#messagesKey = options.messagesKey ?? \"messages\";\n this.#currentThreadId = options.threadId ?? null;\n this.#rootBus = {\n channels: ROOT_PUMP_CHANNELS,\n subscribe: (listener) => {\n this.#rootEventListeners.add(listener);\n return () => {\n this.#rootEventListeners.delete(listener);\n };\n },\n };\n this.registry = new ChannelRegistry(this.#rootBus);\n this.subagentStore = this.#subagents.store;\n this.subgraphStore = this.#subgraphs.store;\n this.subgraphByNodeStore = this.#subgraphs.byNodeStore;\n this.rootStore = new StreamStore<RootSnapshot<StateType, InterruptType>>(\n this.#createInitialSnapshot()\n );\n this.#rootMessages = new RootMessageProjection({\n messagesKey: this.#messagesKey,\n store: this.rootStore,\n });\n this.#lifecycleLoading = new LifecycleLoadingTracker({\n store: this.rootStore,\n isDisposed: () => this.#disposed,\n });\n this.messageMetadataStore = this.#messageMetadata.store;\n this.queueStore = new StreamStore<SubmissionQueueSnapshot<StateType>>(\n EMPTY_QUEUE as SubmissionQueueSnapshot<StateType>\n );\n this.#submitter = new SubmitCoordinator({\n options: this.#options,\n rootStore: this.rootStore,\n queueStore: this.queueStore,\n getDisposed: () => this.#disposed,\n getCurrentThreadId: () => this.#currentThreadId,\n setCurrentThreadId: (threadId) => {\n this.#currentThreadId = threadId;\n },\n rememberSelfCreatedThreadId: (threadId) => {\n this.#selfCreatedThreadIds.add(threadId);\n },\n hydrate: (threadId) => this.hydrate(threadId),\n ensureThread: (threadId, deferRootPump) =>\n this.#ensureThread(threadId, deferRootPump),\n startDeferredRootPump: () => this.#startDeferredRootPump(),\n abandonDeferredRootPump: () => this.#abandonDeferredRootPump(),\n forgetSelfCreatedThreadId: (threadId) => {\n this.#selfCreatedThreadIds.delete(threadId);\n },\n waitForRootPumpReady: () => this.#rootPumpReady,\n awaitNextTerminal: (signal) => this.#awaitNextTerminal(signal),\n latestUnresolvedInterrupt: () => this.#latestUnresolvedInterrupt(),\n markInterruptResolved: (interruptId) => {\n this.#resolvedInterrupts.add(interruptId);\n },\n onSubmitStart: () => {\n // Clear the hydrate-window allowlist so genuinely-new live\n // interrupts on the just-started run aren't filtered. Bump\n // the generation so any in-flight hydrate skips its\n // allowlist write on return (see #hydratedActiveInterruptIds).\n this.#hydratedActiveInterruptIds = null;\n this.#submitGeneration += 1;\n },\n });\n this.#hydrationPromise = this.#createHydrationPromise();\n /**\n * Attach a default no-op catch so orphaned hydrationPromise\n * rejections (e.g. controllers spawned during Suspense retries\n * whose promise never gets subscribed to because the suspense\n * cache already holds an earlier one) don't surface as unhandled\n * rejections. Callers that attach their own handlers via .then()\n * still receive the rejection on their derived promise.\n */\n this.#hydrationPromise.catch(() => undefined);\n /**\n * Kick off the initial hydrate eagerly when a caller-supplied\n * thread id is present. Suspense consumers throw\n * `hydrationPromise` on the very first render, which unmounts the\n * subtree before any `useEffect` can run — so if we waited for an\n * effect to drive the hydrate we'd deadlock. Firing here makes\n * the promise settle independently of the component lifecycle.\n */\n if (this.#currentThreadId != null) {\n void this.hydrate(this.#currentThreadId);\n } else {\n this.#resolveHydration();\n }\n }\n\n /**\n * Promise that settles the first time {@link hydrate} finishes on\n * the current thread. Resolves on a clean hydration, rejects when\n * the thread-state fetch errors. A fresh promise is installed on\n * every thread swap so `<Suspense>` wrappers re-suspend on\n * `switchThread`.\n */\n get hydrationPromise(): Promise<void> {\n return this.#hydrationPromise;\n }\n\n /**\n * Create the deferred promise backing the current hydration cycle.\n */\n #createHydrationPromise(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n this.#resolveHydration = resolve;\n this.#rejectHydration = reject;\n });\n }\n\n /**\n * Replace the current hydration promise before a thread swap.\n */\n #resetHydrationPromise(): void {\n /**\n * Swallow rejection on the orphaned promise so Node doesn't\n * flag it as unhandled; Suspense callers that still hold a\n * reference see the rejection they subscribed to.\n */\n this.#hydrationPromise.catch(() => undefined);\n this.#hydrationPromise = this.#createHydrationPromise();\n }\n\n // ---------- public imperatives ----------\n\n /**\n * Fetch the checkpointed thread state and seed the root snapshot.\n * Re-calling with a different `threadId` swaps the underlying\n * {@link ThreadStream}, rewires the registry to the new thread, and\n * resets assemblers.\n *\n * @param threadId - Optional replacement thread id; `null` clears the active thread.\n */\n async hydrate(threadId?: string | null): Promise<void> {\n if (this.#disposed) return;\n const target = threadId === undefined ? this.#currentThreadId : threadId;\n const changed = target !== this.#currentThreadId;\n this.#currentThreadId = target ?? null;\n this.rootStore.setState((s) => ({ ...s, threadId: this.#currentThreadId }));\n\n if (changed) {\n /**\n * Swap to a new thread: re-arm the hydration promise so any\n * Suspense boundary remounted against the new id suspends again.\n */\n this.#resetHydrationPromise();\n await this.#teardownThread();\n /**\n * Reset UI-facing snapshot so stale messages/values/tool-calls\n * from the previous thread don't bleed into the new one. The\n * new thread's state (if any) is then populated below via\n * `#applyValues`.\n */\n this.rootStore.setState(() => ({\n ...this.#createInitialSnapshot(),\n threadId: this.#currentThreadId,\n }));\n /**\n * Drop queued submissions — they were targeted at the previous\n * thread so dispatching them against the new thread would be\n * surprising. Mirrors the legacy `StreamOrchestrator` behaviour.\n */\n this.queueStore.setState(\n () => EMPTY_QUEUE as SubmissionQueueSnapshot<StateType>\n );\n }\n\n if (this.#currentThreadId == null) {\n this.rootStore.setState((s) => ({ ...s, isThreadLoading: false }));\n this.#resolveHydration();\n return;\n }\n\n /**\n * Self-generated thread ids have nothing to fetch server-side yet\n * — the thread is created lazily by the first `run.start`. Calling\n * `threads.getState()` here would return a 404 and surface a\n * spurious error to the UI.\n */\n if (this.#selfCreatedThreadIds.has(this.#currentThreadId)) {\n this.rootStore.setState((s) => ({ ...s, isThreadLoading: false }));\n this.#resolveHydration();\n return;\n }\n\n this.rootStore.setState((s) => ({ ...s, isThreadLoading: true }));\n let hydrationError: unknown;\n let threadExists = false;\n try {\n const state = await this.#options.client.threads.getState<StateType>(\n this.#currentThreadId\n );\n threadExists = state != null;\n if (state?.values != null) {\n /**\n * `threads.getState()` returns the legacy `ThreadState` shape\n * where `parent_checkpoint` is an object (`{ thread_id,\n * checkpoint_id, checkpoint_ns }`). Synthesize the v2\n * `Checkpoint` envelope (matching the `checkpoints` channel\n * payload) so hydrated messages also get their\n * `parentCheckpointId` populated for fork / edit flows.\n */\n const checkpointId = state.checkpoint?.checkpoint_id;\n const parentCheckpointId =\n state.parent_checkpoint?.checkpoint_id ?? undefined;\n const syntheticCheckpoint =\n typeof checkpointId === \"string\"\n ? {\n id: checkpointId,\n ...(parentCheckpointId != null\n ? { parent_id: parentCheckpointId }\n : {}),\n }\n : undefined;\n this.#applyValues(state.values as unknown, syntheticCheckpoint);\n }\n /**\n * Sync the visible interrupt list to the server's authoritative\n * `state.tasks[].interrupts`. Without this, SSE replay of past\n * `input.requested` events would re-add resolved interrupts to\n * the UI on every page navigation back to the thread.\n *\n * Only runs when `state.tasks` is an array — runtimes that don't\n * surface tasks in `threads.getState()` are left untouched (an\n * unconditional overwrite would wipe any in-flight interrupt\n * state the wildcard watcher may already have recorded).\n */\n if (Array.isArray(state?.tasks)) {\n const generationAtFetch = this.#submitGeneration;\n const activeInterrupts: Interrupt<InterruptType>[] = [];\n const activeIds = new Set<string>();\n for (const task of state.tasks) {\n if (!Array.isArray(task?.interrupts)) continue;\n for (const interrupt of task.interrupts) {\n const typed = interrupt as\n | { id?: string; value?: unknown }\n | null\n | undefined;\n const id = typed?.id;\n if (typeof id !== \"string\" || activeIds.has(id)) continue;\n activeIds.add(id);\n activeInterrupts.push({\n id,\n value: typed?.value as InterruptType,\n });\n }\n }\n this.rootStore.setState((s) => ({\n ...s,\n interrupts: activeInterrupts,\n interrupt: activeInterrupts[0],\n }));\n // Only seed the allowlist when no submit started while the\n // state fetch was in flight. If one did, the cleared\n // (null) allowlist must stay null so the new run's live\n // interrupts are not filtered.\n if (this.#submitGeneration === generationAtFetch) {\n this.#hydratedActiveInterruptIds = activeIds;\n }\n }\n } catch (error) {\n /**\n * A 404 on hydrate means the thread does not exist server-side\n * yet (most commonly because the caller supplied a brand-new,\n * externally-minted thread id and is about to create it via the\n * first `submit()`). Treat it as \"empty state\" rather than a\n * fatal hydration error so Suspense boundaries resolve cleanly\n * and no spurious error renders in the UI.\n */\n const status = (error as { status?: number } | null)?.status;\n if (status !== 404) {\n hydrationError = error;\n this.rootStore.setState((s) => ({ ...s, error }));\n }\n } finally {\n this.rootStore.setState((s) => ({ ...s, isThreadLoading: false }));\n if (hydrationError != null) {\n this.#rejectHydration(hydrationError);\n } else {\n this.#resolveHydration();\n }\n }\n\n /**\n * P0 fix: open the shared subscription on mount so in-flight\n * server-side runs are observed even when no local `submit()` is\n * active. The transport replays the run from `seq=0` on a rotating\n * subscribe, so late-joining is free once the subscription exists.\n * `isLoading` transitions are driven by the persistent root\n * lifecycle listener registered in `#startRootPump`.\n */\n const thread = this.#ensureThread(this.#currentThreadId);\n\n /**\n * Start the wildcard lifecycle watcher up-front for existing\n * threads. The root content pump runs at `depth: 1`, which covers\n * root-namespace and one-deep events but not arbitrarily-nested\n * subagent / subgraph lifecycle — the dedicated watcher handles\n * those.\n *\n * For self-created (new) threads we skip — the watcher would 404\n * against a not-yet-existent thread. `submitRun` / `respondInput`\n * call `startLifecycleWatcher` on first submission to cover that\n * case.\n */\n if (threadExists) {\n thread.startLifecycleWatcher();\n }\n }\n\n /**\n * Submit input or a resume command to the active thread.\n *\n * @param input - Input payload for a new run; `null`/`undefined` submits no input.\n * @param options - Per-run config, metadata, multitask behavior, and callbacks.\n */\n async submit(\n input: unknown,\n options?: StreamSubmitOptions<StateType, ConfigurableType>\n ): Promise<void> {\n await this.#submitter.submit(input, options);\n }\n\n /**\n * Abort the currently tracked run and mark the controller idle.\n */\n async stop(): Promise<void> {\n await this.#submitter.stop();\n }\n\n /**\n * Cancel a queued submission by id. Returns `true` when the entry\n * was found and removed, `false` otherwise.\n *\n * Today this only removes the entry from the client-side mirror —\n * once the server exposes queue cancel (roadmap A0.3) the\n * controller will additionally issue a cancel call against the\n * active transport.\n *\n * @param id - Client-side queue entry id to remove.\n */\n async cancelQueued(id: string): Promise<boolean> {\n return this.#submitter.cancelQueued(id);\n }\n\n /**\n * Drop every queued submission. Server-side cancel arrives with A0.3.\n */\n async clearQueue(): Promise<void> {\n await this.#submitter.clearQueue();\n }\n\n /**\n * Respond to a pending protocol interrupt.\n *\n * @param response - Payload to send back to the interrupted namespace.\n * @param target - Optional explicit interrupt id and namespace; defaults to the latest unresolved interrupt.\n */\n async respond(\n response: unknown,\n target?: { interruptId: string; namespace?: string[] }\n ): Promise<void> {\n if (this.#disposed || this.#thread == null) {\n throw new Error(\"No active thread to respond to.\");\n }\n const resolved =\n target != null\n ? {\n interruptId: target.interruptId,\n namespace: target.namespace ?? [...ROOT_NAMESPACE],\n }\n : this.#latestUnresolvedInterrupt();\n if (resolved == null) {\n throw new Error(\"No pending interrupt to respond to.\");\n }\n try {\n await this.#thread.respondInput({\n namespace: resolved.namespace,\n interrupt_id: resolved.interruptId,\n response,\n });\n this.#resolvedInterrupts.add(resolved.interruptId);\n } catch (error) {\n if (this.#disposed && isAbortLikeError(error)) {\n return;\n }\n throw error;\n }\n }\n\n /**\n * Dispose the active thread, subscriptions, registry entries, and listeners.\n */\n async dispose(): Promise<void> {\n if (this.#disposed) return;\n this.#cancelPendingDispose();\n this.#disposed = true;\n this.#submitter.abortActiveRun();\n await this.#teardownThread();\n await this.registry.dispose();\n this.#threadListeners.clear();\n }\n\n /**\n * StrictMode-safe lifecycle hook for framework bindings.\n *\n * React 18+ `StrictMode` intentionally mounts → unmounts → remounts\n * components in dev to surface effect-cleanup bugs. A naive\n * `useEffect(() => () => controller.dispose())` would permanently\n * tear the controller down on that first synthetic unmount, leaving\n * every subsequent `submit()` a silent no-op.\n *\n * Call {@link activate} from the bind site's effect and return the\n * result as the effect's cleanup. The controller uses deferred\n * disposal: a `release()` only schedules a dispose on the next\n * microtask, which is cancelled if another `activate()` arrives\n * before it fires (the normal StrictMode remount path).\n */\n activate(): () => void {\n this.#cancelPendingDispose();\n return () => {\n if (this.#disposed) return;\n this.#pendingDisposeTimer = setTimeout(() => {\n this.#pendingDisposeTimer = null;\n void this.dispose().catch(() => undefined);\n }, 0);\n };\n }\n\n /**\n * Cancel a deferred dispose scheduled by {@link activate}.\n */\n #cancelPendingDispose(): void {\n if (this.#pendingDisposeTimer != null) {\n clearTimeout(this.#pendingDisposeTimer);\n this.#pendingDisposeTimer = null;\n }\n }\n\n // ---------- escape hatches ----------\n\n /**\n * Current underlying {@link ThreadStream} (v2 escape hatch).\n */\n getThread(): ThreadStream | undefined {\n return this.#thread;\n }\n\n /**\n * Listen for `ThreadStream` lifecycle (swap on thread-id change,\n * detach on dispose). The listener fires immediately with the\n * current thread (may be `undefined`).\n *\n * @param listener - Callback invoked immediately and on every thread swap.\n */\n subscribeThread(\n listener: (thread: ThreadStream | undefined) => void\n ): () => void {\n this.#threadListeners.add(listener);\n listener(this.#thread);\n return () => {\n this.#threadListeners.delete(listener);\n };\n }\n\n // ---------- internals ----------\n\n /**\n * Build the initial root snapshot from configured initial values.\n */\n #createInitialSnapshot(): RootSnapshot<StateType, InterruptType> {\n const values = (this.#options.initialValues ??\n ({} as StateType)) as StateType;\n const messages = extractAndCoerceMessages(\n values as unknown as Record<string, unknown>,\n this.#messagesKey\n );\n /**\n * Seed `isThreadLoading: true` synchronously when a caller-supplied\n * threadId is on the controller at construction/swap time. Without\n * this Suspense wrappers would render their fallback for a tick\n * because `isThreadLoading` flips false → true → false once the\n * deferred `hydrate()` starts, and the synchronous render observes\n * the initial `false`.\n */\n const willHydrate =\n this.#currentThreadId != null &&\n !this.#selfCreatedThreadIds.has(this.#currentThreadId);\n return {\n values,\n messages,\n toolCalls: [],\n interrupts: [],\n interrupt: undefined,\n isLoading: false,\n isThreadLoading: willHydrate,\n error: undefined,\n threadId: this.#currentThreadId,\n };\n }\n\n /**\n * Return the active thread stream, creating and binding one when needed.\n *\n * @param threadId - Thread id used when constructing the stream.\n * @param deferRootPump - When `true`, build the ThreadStream and bind\n * the registry but skip starting the persistent root SSE pump. Used\n * for client-self-created thread ids whose server-side thread row\n * doesn't exist yet — opening the pump's `subscription.subscribe`\n * against a not-yet-existent thread produces a `404: Thread not\n * found` protocol error that strands terminal lifecycle events and\n * leaves the UI showing nothing until the user reloads. The pump is\n * started later via {@link #startDeferredRootPump} after `submitRun`\n * / `respondInput` commits the thread server-side.\n *\n * Note: PR 2381's `#runStartReady` gate covers the analogous race\n * for the in-flight `run.start` send, but only when that send is\n * already pending. `#ensureThread` runs *before* `submitRun` is\n * called (and thus before the gate is armed), so on transports\n * that subscribe synchronously (WebSocket) the deferred path is\n * still required.\n */\n #ensureThread(threadId: string, deferRootPump = false): ThreadStream {\n if (this.#thread != null) return this.#thread;\n this.#thread = this.#options.client.threads.stream(threadId, {\n assistantId: this.#options.assistantId,\n transport: this.#options.transport,\n fetch: this.#options.fetch,\n webSocketFactory: this.#options.webSocketFactory,\n });\n this.registry.bind(this.#thread);\n if (deferRootPump) {\n // Resolve `#rootPumpReady` immediately so `submit()`'s `await\n // this.#rootPumpReady` doesn't block — the dispatch path only\n // needs the ThreadStream wired up to call `submitRun`, not the\n // persistent subscription.\n this.#rootPumpReady = Promise.resolve();\n this.#rootPumpDeferred = true;\n } else {\n this.#startRootPump(this.#thread);\n }\n this.#notifyThreadListeners();\n return this.#thread;\n }\n\n /**\n * Start the previously-deferred root SSE pump after the first\n * `submitRun` / `respondInput` has committed the thread server-side.\n *\n * No-op when the pump was started eagerly in {@link #ensureThread}\n * (i.e. for hydrated existing threads, or for any thread whose pump\n * has already been brought up).\n */\n #startDeferredRootPump(): void {\n if (!this.#rootPumpDeferred) return;\n if (this.#thread == null) return;\n this.#rootPumpDeferred = false;\n this.#startRootPump(this.#thread);\n }\n\n /**\n * Abandon a deferred root pump that never started because its\n * triggering dispatch (`submitRun` / `respondInput`) failed.\n *\n * Without this, the controller would be wedged in a state where:\n * - `#thread` is wired but no content pump is open\n * - `#rootPumpDeferred` stays `true`\n * - `selfCreatedThreadIds` still holds the id\n *\n * A retry submit on the same controller would see\n * `wasSelfCreated=false` (because `currentThreadId` is no longer\n * null), `#ensureThread(id, false)` would early-return because\n * `#thread != null`, and the pump would never start. The thread\n * would have an id committed to the URL but no live subscription.\n *\n * Tearing down `#thread` so the next submit re-runs `#ensureThread`\n * from scratch is the simplest recovery — the failed dispatch\n * means there was nothing to subscribe to anyway.\n */\n #abandonDeferredRootPump(): void {\n if (!this.#rootPumpDeferred) return;\n this.#rootPumpDeferred = false;\n void this.#teardownThread();\n }\n\n /**\n * Close the current thread stream and reset per-thread assembly state.\n */\n async #teardownThread(): Promise<void> {\n const thread = this.#thread;\n this.#thread = undefined;\n this.registry.bind(undefined);\n this.#threadEventUnsubscribe?.();\n this.#threadEventUnsubscribe = undefined;\n /**\n * Persistent lifecycle driver is scoped to the current thread\n * stream. Remove it so a swap to a new thread starts with a clean\n * listener set (a new one is installed in `#startRootPump`).\n */\n this.#rootEventListeners.delete(this.#lifecycleLoading.listener);\n try {\n await this.#rootSubscription?.unsubscribe();\n } catch {\n /* already closed */\n }\n this.#rootSubscription = undefined;\n this.#rootPumpReady = undefined;\n // Reset so a swap to a new thread doesn't carry over a stale\n // deferred flag — `#ensureThread` will set it again if the new\n // thread is self-created.\n this.#rootPumpDeferred = false;\n try {\n await this.#rootPump;\n } catch {\n /* ignore */\n }\n this.#rootPump = undefined;\n\n // Reset per-thread assembly state.\n this.#rootMessages.reset();\n this.#rootToolAssembler = new ToolCallAssembler();\n this.#lifecycleLoading.reset();\n this.#messageMetadata.reset();\n // Drop the hydrate-window allowlist — the next thread's hydrate\n // will repopulate it from that thread's `state.tasks[].interrupts`.\n this.#hydratedActiveInterruptIds = null;\n this.queueStore.setState(\n () => EMPTY_QUEUE as SubmissionQueueSnapshot<StateType>\n );\n\n if (thread != null) {\n try {\n await thread.close();\n } catch {\n /* already closed */\n }\n this.#notifyThreadListeners();\n }\n }\n\n /**\n * Determine whether the configured transport uses the resumable event-stream path.\n */\n #usesEventStreamTransport(): boolean {\n const transport = this.#options.transport;\n if (transport === \"websocket\") return false;\n if (transport == null || transport === \"sse\") return true;\n return typeof transport.openEventStream === \"function\";\n }\n\n /**\n * Start the always-on root subscription pump for the provided thread.\n *\n * @param thread - Thread stream to subscribe to and fan out from.\n */\n #startRootPump(thread: ThreadStream): void {\n if (this.#rootPump != null) return;\n let resolveReady: (() => void) | undefined;\n this.#rootPumpReady = new Promise<void>((resolve) => {\n resolveReady = resolve;\n });\n\n /**\n * Wildcard discovery + interrupt tracking is delivered via the\n * thread's dedicated lifecycle watcher (see `ThreadStream.onEvent`).\n * This callback fires once per globally-unique event across both\n * the content pump AND the watcher, so we can drive discovery\n * runners and nested HITL capture without widening the content\n * pump's narrow filter.\n */\n this.#threadEventUnsubscribe = thread.onEvent((event) =>\n this.#onWildcardEvent(event)\n );\n\n /**\n * Persistent isLoading driver. Drives `isLoading` from\n * root-namespace lifecycle events so that in-flight runs observed\n * via `hydrate()` (not initiated by a local `submit()`) still flip\n * the UI to loading. `running` → true; terminals → false. The\n * optimistic `isLoading = true` inside `submit()` stays because\n * that fires before any subscription event arrives.\n */\n this.#rootEventListeners.add(this.#lifecycleLoading.listener);\n\n this.#rootPump = (async () => {\n try {\n /**\n * Root content pump: depth 1 is required because the controller\n * classifies tool events at namespace length ≤ 1 as root-level\n * (see `#onWildcardEvent`'s `isRootLevelTool` check). The deep-\n * agent `task` dispatcher fires `tools.tool-started` at\n * `[\"tools:<id>\"]` (length 1), so a depth-0 filter would drop\n * those events server-side before they reached `root.toolCalls`.\n *\n * Deeper content (subagent message tokens, values snapshots) is\n * pulled in on demand by per-namespace selector projections\n * (e.g. `useMessages(sub)`).\n */\n const subscriptionPromise = thread.subscribe({\n channels: [...ROOT_PUMP_CHANNELS] as Channel[],\n namespaces: [[] as string[]],\n depth: 1,\n });\n if (this.#usesEventStreamTransport()) {\n /**\n * SSE streams can legitimately withhold response headers until\n * the first event is available. Waiting for `subscribe()` here\n * would deadlock new-thread submits: the run is not dispatched\n * until the root pump is \"ready\", but the pump does not become\n * ready until the run emits. `thread.subscribe()` has already\n * registered the local subscription and scheduled the stream\n * rotation by this point; waiting one microtask lets the fetch\n * get kicked off without requiring headers to arrive.\n */\n queueMicrotask(() => {\n resolveReady?.();\n resolveReady = undefined;\n });\n }\n const subscription = await subscriptionPromise;\n resolveReady?.();\n resolveReady = undefined;\n this.#rootSubscription = subscription;\n /**\n * The SSE transport pauses the underlying subscription when\n * a terminal root lifecycle event arrives (so `for await`\n * loops observing a single run exit cleanly) and re-opens\n * the next run's server stream on `#prepareForNextRun`,\n * resuming the subscription handle. The root pump needs to\n * survive that hand-off: we re-enter the inner `for await`\n * for every resumed iteration until the subscription is\n * permanently closed or the controller is disposed.\n */\n while (!this.#disposed) {\n for await (const event of subscription) {\n if (this.#disposed) {\n break;\n }\n /**\n * Resilience: isolate per-event dispatch from the pump loop.\n *\n * `#onRootEvent` runs synchronously and, transitively,\n * invokes every root-bus listener (selector projections that\n * opted into the shared stream) plus every `rootStore`\n * subscriber. Some of those subscribers live in a React\n * render tree — `useStream` drives\n * `useSyncExternalStore`, so a misbehaving component can\n * surface a render-phase error (\"Maximum update depth\n * exceeded\", \"The result of getSnapshot should be cached\",\n * etc.) that propagates out here.\n *\n * Without this guard, a single throw bubbles through the\n * `for await` loop and terminates the root pump permanently.\n * That is catastrophic: no more root events get processed —\n * the terminal `lifecycle: completed` never lands, so\n * `#awaitNextTerminal` never resolves, `isLoading` stays\n * `true`, composers stay disabled, and the final assistant\n * turn never commits to `stream.messages`. The UI looks\n * hung even though the server is still emitting events\n * (and `ThreadStream.onEvent` keeps firing).\n *\n * We therefore swallow the error and keep pumping. The\n * the pump's correctness guarantees do not depend on any\n * consumer behaving well.\n */\n try {\n this.#onRootEvent(event);\n } catch {\n /**\n * Best-effort — a consumer-facing store subscriber should not\n * terminate the root pump. Store mutations happen before\n * listeners are notified, so continuing with later events keeps\n * the controller's authoritative state moving forward.\n */\n }\n }\n if (this.#disposed) break;\n if (!subscription.isPaused) {\n break;\n }\n await subscription.waitForResume();\n }\n } catch {\n resolveReady?.();\n resolveReady = undefined;\n /* thread closed or errored */\n }\n })();\n }\n\n /**\n * Handle an event delivered via {@link ThreadStream.onEvent}.\n *\n * `onEvent` fires once per globally-unique event across the content\n * pump and the wildcard lifecycle watcher, so this is the single\n * entry point for wildcard discovery / interrupt tracking. It does\n * NOT fan events out to the root bus (that's driven by the content\n * pump iterator so root-bus short-circuits stay depth-1 scoped) and\n * it does NOT process root content — messages/tools/values at root\n * are handled by `#onRootEvent` off the content pump.\n *\n * @param event - Raw protocol event observed by the thread-wide listener.\n */\n #onWildcardEvent(event: Event): void {\n try {\n this.#subagents.push(event);\n } catch {\n /**\n * Discovery store subscribers are user/UI code. If one throws, still\n * let the wildcard watcher update subgraphs, loading, and interrupts.\n */\n }\n this.#subgraphs.push(event);\n this.#lifecycleLoading.handle(event);\n\n /**\n * Nested `input.requested` events (HITL inside a subagent /\n * subgraph) are not observable via the narrow content pump. The\n * `ThreadStream` itself already records them into\n * `thread.interrupts`, which `#latestUnresolvedInterrupt()`\n * consults — so HITL respond() works for any depth. Root-level\n * interrupts are also mirrored into `rootStore.interrupts` here so\n * UI state does not depend on the narrower content pump being the\n * first consumer to see the event.\n */\n this.#recordRootInterrupt(event);\n }\n\n /**\n * Process one root-pump event and update all root projections.\n *\n * @param event - Event yielded by the root subscription.\n */\n #onRootEvent(event: Event): void {\n try {\n this.#subagents.push(event);\n } catch {\n /**\n * Discovery store subscribers are user/UI code. If one throws, still\n * process the root event below so orchestrator messages and terminal\n * state continue to advance.\n */\n }\n\n /**\n * Fan root-pump events out to every root-bus listener (selector\n * projections that opted into the shared stream,\n * `#awaitTerminal`, etc.). The root bus mirrors the content\n * pump's narrow scope (depth 1 at root) so projections that\n * short-circuit via the bus stay bounded.\n */\n if (this.#rootEventListeners.size > 0) {\n for (const listener of this.#rootEventListeners) {\n try {\n listener(event);\n } catch {\n /**\n * Best-effort — a bad listener should not wedge other\n * projections or the root pump itself.\n */\n }\n }\n }\n\n /**\n * `messages` and `tools` events are emitted under a node's\n * namespace — for a typical StateGraph the LLM's token deltas\n * land on `[\"model:<uuid>\"]`, tool executions on\n * `[\"tools:<uuid>\"]`, etc. The orchestrator's own turns (root\n * agent, or an orchestrator-scoped subgraph like `model:*` /\n * `model_request:*`) belong in `root.messages` and\n * `root.toolCalls`.\n *\n * Subagent / tool-internal branches do NOT:\n * - `task:*` segment — legacy subagent convention.\n * - `tools:*` segment — every tool execution is wrapped in a\n * `tools` subgraph. For simple tools its only content is\n * the eventual tool result (also echoed verbatim by\n * `values.messages` so we don't lose anything). For the\n * deep-agent `task` tool its content IS the spawned\n * subagent's full message + tool stream, which is surfaced\n * separately via `useMessages(stream, subagent)` /\n * `useToolCalls(stream, subagent)`.\n *\n * We therefore drop `messages` events from any namespace that\n * contains a `task:*` or `tools:*` segment; the authoritative\n * tool-result text lands in `root.messages` via the root\n * `values.messages` snapshot merge in `#applyValues`.\n */\n const isInternalNamespace = isInternalWorkNamespace(event.params.namespace);\n const hasLegacySubagentNamespace = isLegacySubagentNamespace(\n event.params.namespace\n );\n\n if (event.method === \"messages\") {\n if (!isInternalNamespace) {\n this.#rootMessages.handleMessage(event as MessagesEvent);\n }\n return;\n }\n\n if (event.method === \"tools\") {\n /**\n * Root-level tool events (both for simple orchestrator tools\n * and the deep-agent `task` dispatcher) fire at a\n * single-segment `[\"tools:<id>\"]` namespace. Anything deeper\n * (e.g. `[tools:<outer>, tools:<inner>]`) is a subagent's own\n * tool call and belongs to that subagent's `useToolCalls`\n * view, not the orchestrator's `root.toolCalls`.\n */\n const isRootLevelTool =\n event.params.namespace.length <= 1 && !hasLegacySubagentNamespace;\n if (isRootLevelTool) {\n /**\n * Record the `namespace → tool_call_id` association so that\n * the ensuing `message-start` (role: \"tool\") at the same\n * namespace can recover the `tool_call_id` (the `messages`\n * channel's start event doesn't carry it directly).\n */\n const toolData = event.params.data as {\n event?: string;\n tool_call_id?: string;\n };\n if (\n toolData.event === \"tool-started\" &&\n typeof toolData.tool_call_id === \"string\"\n ) {\n this.#rootMessages.recordToolCallNamespace(\n event.params.namespace,\n toolData.tool_call_id\n );\n }\n const tc = this.#rootToolAssembler.consume(event as ToolsEvent);\n if (tc != null) {\n this.rootStore.setState((s) => ({\n ...s,\n toolCalls: upsertToolCall(s.toolCalls, tc),\n }));\n }\n }\n return;\n }\n\n /**\n * The `checkpoints` channel carries the lightweight envelope\n * (`id`, `parent_id`, `step`, `source`) emitted immediately\n * before its companion `values` event on the same superstep.\n * Buffer the envelope per-namespace so the ensuing `values`\n * event at the same namespace can pair with it in `#applyValues`.\n * The buffer is read-and-cleared on consumption so a subsequent\n * `values` event without a new checkpoint doesn't reuse stale\n * metadata.\n */\n if (event.method === \"checkpoints\") {\n const data = event.params.data as {\n id?: unknown;\n parent_id?: unknown;\n } | null;\n this.#messageMetadata.bufferCheckpoint(event.params.namespace, data);\n return;\n }\n\n // Channels below are only meaningful at the root namespace.\n const isRoot = isRootNamespace(event.params.namespace);\n if (!isRoot) return;\n\n if (event.method === \"values\") {\n const valuesEvent = event as ValuesEvent;\n const bufferedCheckpoint = this.#messageMetadata.consumeCheckpoint(\n event.params.namespace\n );\n this.#applyValues(valuesEvent.params.data, bufferedCheckpoint);\n return;\n }\n\n if (event.method === \"input.requested\") {\n this.#recordRootInterrupt(event);\n return;\n }\n\n if (event.method === \"lifecycle\") {\n /**\n * Root lifecycle transitions are observed elsewhere\n * (#awaitTerminal) to unblock `submit`.\n */\n const lifecycle = (event as LifecycleEvent).params.data as {\n event?: string;\n };\n void lifecycle;\n }\n }\n\n /**\n * Merge a `values` payload into root values and root messages.\n *\n * @param raw - Raw `values` channel payload.\n * @param checkpoint - Optional checkpoint envelope paired with the values event.\n */\n #applyValues(raw: unknown, checkpoint?: CheckpointEnvelope): void {\n if (raw == null || typeof raw !== \"object\" || Array.isArray(raw)) {\n return;\n }\n const state = raw as Record<string, unknown>;\n /**\n * Surface parent_checkpoint per-message when the values event\n * carries the lightweight checkpoint envelope (populated by\n * `@langchain/langgraph-core`'s `_emitValuesWithCheckpointMeta` and\n * forwarded through `convertToProtocolEvent`). Consumers surface\n * this as `useMessageMetadata(stream, msg.id).parentCheckpointId`\n * for fork / edit flows.\n */\n const parentCheckpointId = checkpoint?.parent_id;\n if (parentCheckpointId != null && Array.isArray(state[this.#messagesKey])) {\n this.#messageMetadata.recordMessages(\n state[this.#messagesKey] as Array<{ id?: string }>,\n { parentCheckpointId }\n );\n }\n const maybeMessages = state[this.#messagesKey];\n let nextValues: StateType;\n let nextMessages: BaseMessage[];\n if (Array.isArray(maybeMessages)) {\n const coerced = ensureMessageInstances(\n maybeMessages as (Message | BaseMessage)[]\n );\n nextValues = {\n ...(state as StateType),\n [this.#messagesKey]: coerced,\n } as StateType;\n nextMessages = coerced;\n } else {\n nextValues = state as StateType;\n nextMessages = [];\n }\n this.#rootMessages.applyValues(nextValues, nextMessages);\n }\n\n /**\n * Mirror root protocol interrupts into the root snapshot.\n *\n * This can be called from both the wildcard lifecycle/input watcher and the\n * root content pump. Store-level dedup keeps the user-facing list stable.\n */\n #recordRootInterrupt(event: Event): void {\n if (event.method !== \"input.requested\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n const data = event.params.data as {\n interrupt_id?: string;\n payload?: unknown;\n };\n const interruptId = data?.interrupt_id;\n if (\n typeof interruptId !== \"string\" ||\n this.#resolvedInterrupts.has(interruptId)\n ) {\n return;\n }\n // Strict allowlist when populated by the most-recent hydrate: SSE\n // replay of `input.requested` carries no signal distinguishing\n // historical (already-resolved) interrupts from live ones, so we\n // accept only ids the server reported as currently active in\n // `state.tasks[].interrupts`. `null` (outside the hydrate window\n // / after a submit clears it) disables filtering entirely so new\n // live interrupts on an active run pass through.\n if (\n this.#hydratedActiveInterruptIds != null &&\n !this.#hydratedActiveInterruptIds.has(interruptId)\n ) {\n return;\n }\n const interrupt: Interrupt<InterruptType> = {\n id: interruptId,\n value: data.payload as InterruptType,\n };\n this.rootStore.setState((s) => {\n if (s.interrupts.some((entry) => entry.id === interruptId)) return s;\n const interrupts = [...s.interrupts, interrupt];\n return { ...s, interrupts, interrupt: interrupts[0] };\n });\n }\n\n /**\n * Resolve on the next root-namespace terminal lifecycle event\n * (`completed` / `failed` / `interrupted`) or on abort.\n *\n * Attaches to the controller's root event bus instead of opening\n * a second server subscription. Callers should register the\n * returned promise **before** dispatching the command that\n * triggers the run (`thread.run.start` / `thread.input.respond`)\n * — the root pump fans events out synchronously on arrival, so a\n * late registration would miss the terminal for fast runs.\n *\n * @param signal - Abort signal for the local submit lifecycle.\n */\n #awaitNextTerminal(signal: AbortSignal): Promise<{\n event: \"completed\" | \"failed\" | \"interrupted\" | \"aborted\";\n error?: string;\n }> {\n return new Promise((resolve) => {\n let settled = false;\n function finish(result: {\n event: \"completed\" | \"failed\" | \"interrupted\" | \"aborted\";\n error?: string;\n }) {\n if (settled) return;\n settled = true;\n unsubscribeRoot?.();\n unsubscribeThread?.();\n signal.removeEventListener(\"abort\", finishAborted);\n resolve(result);\n }\n const finishAborted = () => finish({ event: \"aborted\" });\n const onEvent = (event: Event) => {\n if (settled) return;\n if (event.method !== \"lifecycle\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n const lifecycle = (event as LifecycleEvent).params.data as {\n event?: string;\n error?: string;\n };\n if (lifecycle?.event === \"completed\") {\n setTimeout(() => finish({ event: \"completed\" }), 0);\n } else if (lifecycle?.event === \"failed\") {\n setTimeout(\n () => finish({ event: \"failed\", error: lifecycle.error }),\n 0\n );\n } else if (lifecycle?.event === \"interrupted\") {\n setTimeout(() => finish({ event: \"interrupted\" }), 0);\n }\n };\n const unsubscribeRoot = this.#rootBus.subscribe(onEvent);\n const unsubscribeThread = this.#thread?.onEvent(onEvent);\n if (signal.aborted) {\n finishAborted();\n } else {\n signal.addEventListener(\"abort\", finishAborted, { once: true });\n }\n });\n }\n\n /**\n * Find the newest unresolved interrupt recorded on the active thread.\n */\n #latestUnresolvedInterrupt(): ResolvedInterrupt | null {\n const thread = this.#thread;\n if (thread == null) return null;\n for (let i = thread.interrupts.length - 1; i >= 0; i -= 1) {\n const entry = thread.interrupts[i];\n if (entry == null) continue;\n if (this.#resolvedInterrupts.has(entry.interruptId)) continue;\n return {\n interruptId: entry.interruptId,\n namespace: entry.namespace,\n };\n }\n return null;\n }\n\n /**\n * Notify listeners that the underlying thread stream changed.\n */\n #notifyThreadListeners(): void {\n for (const listener of this.#threadListeners) listener(this.#thread);\n }\n}\n\n// ---------- helpers ----------\n\n/**\n * Extract and coerce the configured messages key from a values object.\n *\n * @param values - State values object to read from.\n * @param messagesKey - Key that contains the message array.\n */\nfunction extractAndCoerceMessages(\n values: Record<string, unknown>,\n messagesKey: string\n): BaseMessage[] {\n const raw = values[messagesKey];\n if (!Array.isArray(raw)) return [];\n return ensureMessageInstances(\n raw as (Message | BaseMessage)[]\n ) as BaseMessage[];\n}\n\n// Unused import guard — `AIMessage` is only referenced by type tests.\nvoid AIMessage;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"controller.js","names":["#options","#messagesKey","#subagents","#subgraphs","#messageMetadata","#resolvedInterrupts","#selfCreatedThreadIds","#rootEventListeners","#rootBus","#threadListeners","#currentThreadId","#createInitialSnapshot","#rootMessages","#lifecycleLoading","#disposed","#submitter","#ensureThread","#startDeferredRootPump","#abandonDeferredRootPump","#rootPumpReady","#awaitNextTerminal","#latestUnresolvedInterrupt","#hydratedActiveInterruptIds","#submitGeneration","#markLocalRunStart","#notifyCreated","#notifyCompleted","#markLocalRunEnd","#hydrationPromise","#createHydrationPromise","#resolveHydration","#rejectHydration","#resetHydrationPromise","#teardownThread","#applyValues","#localRunDepth","#activeRunId","#runLifecycleListener","#thread","#cancelPendingDispose","#pendingDisposeTimer","#rootPumpDeferred","#startRootPump","#notifyThreadListeners","#threadEventUnsubscribe","#rootSubscription","#rootPump","#rootToolAssembler","#onWildcardEvent","#usesEventStreamTransport","#onRootEvent","#recordRootInterrupt"],"sources":["../../src/stream/controller.ts"],"sourcesContent":["/**\n * Framework-agnostic controller for the experimental v2 stream.\n *\n * Responsibilities:\n * - Owns at most one {@link ThreadStream} at a time (swapped on\n * `hydrate(newThreadId)` or `dispose`).\n * - Exposes three always-on observable surfaces via {@link StreamStore}:\n * - `rootStore` : root values/messages/toolCalls/interrupts/…\n * - `subagentStore` : discovery map of subagents (no content)\n * - `subgraphStore` : discovery map of subgraphs (no content)\n * - Owns a {@link ChannelRegistry} that framework selector hooks\n * (`useMessages`, `useToolCalls`, `useExtension`, `useChannel`)\n * use to lazily open per-namespace subscriptions.\n * - Imperative run surface: `submit`, `stop`, `respond`, `joinStream`.\n *\n * A single multi-channel subscription (`values`, `lifecycle`, `input`,\n * `messages`, `tools`) powers every always-on projection and both\n * discovery runners. Selector hooks add their own (deduped)\n * subscriptions on top — so even a UI with many subagents only opens\n * one extra subscription per `(channels, namespace)` actually\n * rendered on screen.\n */\nimport { AIMessage, type BaseMessage } from \"@langchain/core/messages\";\nimport type {\n Channel,\n Event,\n LifecycleEvent,\n MessagesEvent,\n ToolsEvent,\n ValuesEvent,\n} from \"@langchain/protocol\";\nimport type { Interrupt } from \"../schema.js\";\nimport type { ThreadStream } from \"../client/stream/index.js\";\nimport type { SubscriptionHandle } from \"../client/stream/index.js\";\nimport { ToolCallAssembler } from \"../client/stream/handles/tools.js\";\nimport { ensureMessageInstances } from \"../ui/messages.js\";\nimport type { Message } from \"../types.messages.js\";\nimport { StreamStore } from \"./store.js\";\nimport { ChannelRegistry } from \"./channel-registry.js\";\nimport {\n SubagentDiscovery,\n type SubagentMap,\n SubgraphDiscovery,\n type SubgraphMap,\n type SubgraphByNodeMap,\n} from \"./discovery/index.js\";\nimport {\n isInternalWorkNamespace,\n isLegacySubagentNamespace,\n isRootNamespace,\n} from \"./namespace.js\";\nimport {\n MessageMetadataTracker,\n type CheckpointEnvelope,\n type MessageMetadata,\n type MessageMetadataMap,\n} from \"./message-metadata-tracker.js\";\nimport { LifecycleLoadingTracker } from \"./lifecycle-loading-tracker.js\";\nimport { RootMessageProjection } from \"./root-message-projection.js\";\nimport {\n EMPTY_QUEUE,\n SubmitCoordinator,\n type SubmissionQueueEntry,\n type SubmissionQueueSnapshot,\n} from \"./submit-coordinator.js\";\nimport { upsertToolCall } from \"./tool-calls.js\";\nimport type {\n RootEventBus,\n RootSnapshot,\n RunExecutionReason,\n StreamControllerOptions,\n StreamSubmitOptions,\n} from \"./types.js\";\n\nfunction isAbortLikeError(error: unknown): boolean {\n if (error == null || typeof error !== \"object\") return false;\n const maybeError = error as { name?: unknown; message?: unknown };\n return (\n maybeError.name === \"AbortError\" ||\n (typeof maybeError.message === \"string\" &&\n maybeError.message.includes(\"aborted\"))\n );\n}\n\nfunction lifecycleReason(event: string | undefined): RunExecutionReason | null {\n if (event === \"completed\") return \"success\";\n if (event === \"failed\") return \"error\";\n if (event === \"interrupted\") return \"interrupt\";\n return null;\n}\n\nconst ROOT_NAMESPACE: readonly string[] = [];\n\n/**\n * Channel set covered by the always-on root subscription. Exported so\n * projections (and transports) can reason about what the root pump\n * already delivers before opening additional server subscriptions.\n */\nexport const ROOT_PUMP_CHANNELS: readonly Channel[] = [\n \"values\",\n \"checkpoints\",\n \"lifecycle\",\n \"input\",\n \"messages\",\n \"tools\",\n];\n\ninterface ResolvedInterrupt {\n interruptId: string;\n namespace: string[];\n}\n\nexport type {\n MessageMetadata,\n MessageMetadataMap,\n SubmissionQueueEntry,\n SubmissionQueueSnapshot,\n};\n\n/**\n * Coordinates one thread's protocol-v2 stream and exposes stable\n * observable projections for framework bindings.\n *\n * The controller owns the root subscription, lazily binds scoped\n * projections through {@link ChannelRegistry}, and normalizes protocol\n * events into class-message, tool-call, discovery, interrupt, and queue\n * stores.\n *\n * @typeParam StateType - Shape of the graph state exposed on `values`.\n * @typeParam InterruptType - Shape of protocol interrupt payloads.\n * @typeParam ConfigurableType - Shape of `config.configurable` accepted by submit.\n */\nexport class StreamController<\n StateType extends object = Record<string, unknown>,\n InterruptType = unknown,\n ConfigurableType extends object = Record<string, unknown>,\n> {\n readonly rootStore: StreamStore<RootSnapshot<StateType, InterruptType>>;\n readonly subagentStore: StreamStore<SubagentMap>;\n readonly subgraphStore: StreamStore<SubgraphMap>;\n readonly subgraphByNodeStore: StreamStore<SubgraphByNodeMap>;\n readonly messageMetadataStore: StreamStore<MessageMetadataMap>;\n readonly queueStore: StreamStore<SubmissionQueueSnapshot<StateType>>;\n readonly registry: ChannelRegistry;\n\n readonly #options: StreamControllerOptions<StateType>;\n readonly #messagesKey: string;\n readonly #subagents = new SubagentDiscovery();\n readonly #subgraphs = new SubgraphDiscovery();\n readonly #messageMetadata = new MessageMetadataTracker();\n\n #thread: ThreadStream | undefined;\n #currentThreadId: string | null;\n #rootSubscription: SubscriptionHandle<Event> | undefined;\n #rootPump: Promise<void> | undefined;\n #rootPumpReady: Promise<void> | undefined;\n /**\n * `true` while a self-created thread has its root pump deferred until\n * the first `submitRun` / `respondInput` commits the thread row\n * server-side. See `#ensureThread` and `#startDeferredRootPump`.\n */\n #rootPumpDeferred = false;\n #threadEventUnsubscribe: (() => void) | undefined;\n #disposed = false;\n #pendingDisposeTimer: ReturnType<typeof setTimeout> | null = null;\n readonly #resolvedInterrupts = new Set<string>();\n /**\n * Set of interrupt IDs the server reports as currently *active* on\n * the thread (from `state.tasks[].interrupts`). Populated during\n * {@link hydrate} from `client.threads.getState()` and used as a\n * strict allowlist when processing replayed `input.requested`\n * events from the persistent SSE subscription. Without this guard,\n * SSE replay re-adds historically-requested interrupts that have\n * since been resolved (no `input.responded` event exists in the\n * protocol, so the SDK has no other way to tell replay from live\n * for an idle thread). `null` outside the hydrate-window so\n * genuinely new live interrupts on an active run aren't filtered;\n * cleared at the start of `submit()` for the same reason.\n */\n #hydratedActiveInterruptIds: Set<string> | null = null;\n /**\n * Monotonic counter bumped at the start of each `submit()` and used\n * by {@link hydrate} to skip its post-fetch allowlist write when a\n * submit started while the state fetch was in flight. Without this\n * guard, a submit-then-hydrate race could re-install a stale\n * allowlist that filters out genuinely-new live interrupts emitted\n * by the just-started run.\n */\n #submitGeneration = 0;\n /**\n * Thread ids we minted client-side on first `submit()`. Keeping them\n * here lets `hydrate()` skip the `threads.getState()` round-trip —\n * we know there is nothing checkpointed server-side yet (and the\n * request would 404 and surface a spurious error to the UI).\n */\n readonly #selfCreatedThreadIds = new Set<string>();\n readonly #rootEventListeners = new Set<(event: Event) => void>();\n readonly #rootBus: RootEventBus;\n #activeRunId: string | undefined;\n #localRunDepth = 0;\n\n /**\n * Single-shot hydration promise. Exposed via `hydrationPromise`\n * so Suspense wrappers can throw it until the first hydrate\n * completes (resolve) or fails (reject). Reset whenever a new\n * hydrate cycle begins so `<Suspense>` boundaries re-suspend on\n * thread switch.\n */\n #hydrationPromise: Promise<void>;\n #resolveHydration!: () => void;\n #rejectHydration!: (error: unknown) => void;\n\n /**\n * Tool assembler lives for the lifetime of a thread; reset on\n * rebind so a fresh thread starts with a clean slate.\n */\n #rootToolAssembler = new ToolCallAssembler();\n #rootMessages!: RootMessageProjection<StateType, InterruptType>;\n #lifecycleLoading!: LifecycleLoadingTracker<\n RootSnapshot<StateType, InterruptType>\n >;\n #submitter!: SubmitCoordinator<StateType, InterruptType, ConfigurableType>;\n\n readonly #threadListeners = new Set<\n (thread: ThreadStream | undefined) => void\n >();\n\n /**\n * Create a controller around a LangGraph client and optional initial thread.\n *\n * @param options - Runtime configuration, client, thread id, and initial state.\n */\n constructor(options: StreamControllerOptions<StateType>) {\n this.#options = options;\n this.#messagesKey = options.messagesKey ?? \"messages\";\n this.#currentThreadId = options.threadId ?? null;\n this.#rootBus = {\n channels: ROOT_PUMP_CHANNELS,\n subscribe: (listener) => {\n this.#rootEventListeners.add(listener);\n return () => {\n this.#rootEventListeners.delete(listener);\n };\n },\n };\n this.registry = new ChannelRegistry(this.#rootBus);\n this.subagentStore = this.#subagents.store;\n this.subgraphStore = this.#subgraphs.store;\n this.subgraphByNodeStore = this.#subgraphs.byNodeStore;\n this.rootStore = new StreamStore<RootSnapshot<StateType, InterruptType>>(\n this.#createInitialSnapshot()\n );\n this.#rootMessages = new RootMessageProjection({\n messagesKey: this.#messagesKey,\n store: this.rootStore,\n });\n this.#lifecycleLoading = new LifecycleLoadingTracker({\n store: this.rootStore,\n isDisposed: () => this.#disposed,\n });\n this.messageMetadataStore = this.#messageMetadata.store;\n this.queueStore = new StreamStore<SubmissionQueueSnapshot<StateType>>(\n EMPTY_QUEUE as SubmissionQueueSnapshot<StateType>\n );\n this.#submitter = new SubmitCoordinator({\n options: this.#options,\n rootStore: this.rootStore,\n queueStore: this.queueStore,\n getDisposed: () => this.#disposed,\n getCurrentThreadId: () => this.#currentThreadId,\n setCurrentThreadId: (threadId) => {\n this.#currentThreadId = threadId;\n },\n rememberSelfCreatedThreadId: (threadId) => {\n this.#selfCreatedThreadIds.add(threadId);\n },\n hydrate: (threadId) => this.hydrate(threadId),\n ensureThread: (threadId, deferRootPump) =>\n this.#ensureThread(threadId, deferRootPump),\n startDeferredRootPump: () => this.#startDeferredRootPump(),\n abandonDeferredRootPump: () => this.#abandonDeferredRootPump(),\n forgetSelfCreatedThreadId: (threadId) => {\n this.#selfCreatedThreadIds.delete(threadId);\n },\n waitForRootPumpReady: () => this.#rootPumpReady,\n awaitNextTerminal: (signal) => this.#awaitNextTerminal(signal),\n latestUnresolvedInterrupt: () => this.#latestUnresolvedInterrupt(),\n markInterruptResolved: (interruptId) => {\n this.#resolvedInterrupts.add(interruptId);\n },\n onSubmitStart: () => {\n // Clear the hydrate-window allowlist so genuinely-new live\n // interrupts on the just-started run aren't filtered. Bump\n // the generation so any in-flight hydrate skips its\n // allowlist write on return (see #hydratedActiveInterruptIds).\n this.#hydratedActiveInterruptIds = null;\n this.#submitGeneration += 1;\n },\n onRunStart: () => this.#markLocalRunStart(),\n onRunCreated: (runId) => this.#notifyCreated(runId),\n onRunCompleted: (reason, runId) => this.#notifyCompleted(reason, runId),\n onRunEnd: () => this.#markLocalRunEnd(),\n });\n this.#hydrationPromise = this.#createHydrationPromise();\n /**\n * Attach a default no-op catch so orphaned hydrationPromise\n * rejections (e.g. controllers spawned during Suspense retries\n * whose promise never gets subscribed to because the suspense\n * cache already holds an earlier one) don't surface as unhandled\n * rejections. Callers that attach their own handlers via .then()\n * still receive the rejection on their derived promise.\n */\n this.#hydrationPromise.catch(() => undefined);\n /**\n * Kick off the initial hydrate eagerly when a caller-supplied\n * thread id is present. Suspense consumers throw\n * `hydrationPromise` on the very first render, which unmounts the\n * subtree before any `useEffect` can run — so if we waited for an\n * effect to drive the hydrate we'd deadlock. Firing here makes\n * the promise settle independently of the component lifecycle.\n */\n if (this.#currentThreadId != null) {\n void this.hydrate(this.#currentThreadId);\n } else {\n this.#resolveHydration();\n }\n }\n\n /**\n * Promise that settles the first time {@link hydrate} finishes on\n * the current thread. Resolves on a clean hydration, rejects when\n * the thread-state fetch errors. A fresh promise is installed on\n * every thread swap so `<Suspense>` wrappers re-suspend on\n * `switchThread`.\n */\n get hydrationPromise(): Promise<void> {\n return this.#hydrationPromise;\n }\n\n /**\n * Create the deferred promise backing the current hydration cycle.\n */\n #createHydrationPromise(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n this.#resolveHydration = resolve;\n this.#rejectHydration = reject;\n });\n }\n\n /**\n * Replace the current hydration promise before a thread swap.\n */\n #resetHydrationPromise(): void {\n /**\n * Swallow rejection on the orphaned promise so Node doesn't\n * flag it as unhandled; Suspense callers that still hold a\n * reference see the rejection they subscribed to.\n */\n this.#hydrationPromise.catch(() => undefined);\n this.#hydrationPromise = this.#createHydrationPromise();\n }\n\n // ---------- public imperatives ----------\n\n /**\n * Fetch the checkpointed thread state and seed the root snapshot.\n * Re-calling with a different `threadId` swaps the underlying\n * {@link ThreadStream}, rewires the registry to the new thread, and\n * resets assemblers.\n *\n * @param threadId - Optional replacement thread id; `null` clears the active thread.\n */\n async hydrate(threadId?: string | null): Promise<void> {\n if (this.#disposed) return;\n const target = threadId === undefined ? this.#currentThreadId : threadId;\n const changed = target !== this.#currentThreadId;\n this.#currentThreadId = target ?? null;\n this.rootStore.setState((s) => ({ ...s, threadId: this.#currentThreadId }));\n\n if (changed) {\n /**\n * Swap to a new thread: re-arm the hydration promise so any\n * Suspense boundary remounted against the new id suspends again.\n */\n this.#resetHydrationPromise();\n await this.#teardownThread();\n /**\n * Reset UI-facing snapshot so stale messages/values/tool-calls\n * from the previous thread don't bleed into the new one. The\n * new thread's state (if any) is then populated below via\n * `#applyValues`.\n */\n this.rootStore.setState(() => ({\n ...this.#createInitialSnapshot(),\n threadId: this.#currentThreadId,\n }));\n /**\n * Drop queued submissions — they were targeted at the previous\n * thread so dispatching them against the new thread would be\n * surprising. Mirrors the legacy `StreamOrchestrator` behaviour.\n */\n this.queueStore.setState(\n () => EMPTY_QUEUE as SubmissionQueueSnapshot<StateType>\n );\n }\n\n if (this.#currentThreadId == null) {\n this.rootStore.setState((s) => ({ ...s, isThreadLoading: false }));\n this.#resolveHydration();\n return;\n }\n\n /**\n * Self-generated thread ids have nothing to fetch server-side yet\n * — the thread is created lazily by the first `run.start`. Calling\n * `threads.getState()` here would return a 404 and surface a\n * spurious error to the UI.\n */\n if (this.#selfCreatedThreadIds.has(this.#currentThreadId)) {\n this.rootStore.setState((s) => ({ ...s, isThreadLoading: false }));\n this.#resolveHydration();\n return;\n }\n\n this.rootStore.setState((s) => ({ ...s, isThreadLoading: true }));\n let hydrationError: unknown;\n let threadExists = false;\n try {\n const state = await this.#options.client.threads.getState<StateType>(\n this.#currentThreadId\n );\n threadExists = state != null;\n if (state?.values != null) {\n /**\n * `threads.getState()` returns the legacy `ThreadState` shape\n * where `parent_checkpoint` is an object (`{ thread_id,\n * checkpoint_id, checkpoint_ns }`). Synthesize the v2\n * `Checkpoint` envelope (matching the `checkpoints` channel\n * payload) so hydrated messages also get their\n * `parentCheckpointId` populated for fork / edit flows.\n */\n const checkpointId = state.checkpoint?.checkpoint_id;\n const parentCheckpointId =\n state.parent_checkpoint?.checkpoint_id ?? undefined;\n const syntheticCheckpoint =\n typeof checkpointId === \"string\"\n ? {\n id: checkpointId,\n ...(parentCheckpointId != null\n ? { parent_id: parentCheckpointId }\n : {}),\n }\n : undefined;\n this.#applyValues(state.values as unknown, syntheticCheckpoint);\n }\n /**\n * Sync the visible interrupt list to the server's authoritative\n * `state.tasks[].interrupts`. Without this, SSE replay of past\n * `input.requested` events would re-add resolved interrupts to\n * the UI on every page navigation back to the thread.\n *\n * Only runs when `state.tasks` is an array — runtimes that don't\n * surface tasks in `threads.getState()` are left untouched (an\n * unconditional overwrite would wipe any in-flight interrupt\n * state the wildcard watcher may already have recorded).\n */\n if (Array.isArray(state?.tasks)) {\n const generationAtFetch = this.#submitGeneration;\n const activeInterrupts: Interrupt<InterruptType>[] = [];\n const activeIds = new Set<string>();\n for (const task of state.tasks) {\n if (!Array.isArray(task?.interrupts)) continue;\n for (const interrupt of task.interrupts) {\n const typed = interrupt as\n | { id?: string; value?: unknown }\n | null\n | undefined;\n const id = typed?.id;\n if (typeof id !== \"string\" || activeIds.has(id)) continue;\n activeIds.add(id);\n activeInterrupts.push({\n id,\n value: typed?.value as InterruptType,\n });\n }\n }\n this.rootStore.setState((s) => ({\n ...s,\n interrupts: activeInterrupts,\n interrupt: activeInterrupts[0],\n }));\n // Only seed the allowlist when no submit started while the\n // state fetch was in flight. If one did, the cleared\n // (null) allowlist must stay null so the new run's live\n // interrupts are not filtered.\n if (this.#submitGeneration === generationAtFetch) {\n this.#hydratedActiveInterruptIds = activeIds;\n }\n }\n } catch (error) {\n /**\n * A 404 on hydrate means the thread does not exist server-side\n * yet (most commonly because the caller supplied a brand-new,\n * externally-minted thread id and is about to create it via the\n * first `submit()`). Treat it as \"empty state\" rather than a\n * fatal hydration error so Suspense boundaries resolve cleanly\n * and no spurious error renders in the UI.\n */\n const status = (error as { status?: number } | null)?.status;\n if (status !== 404) {\n hydrationError = error;\n this.rootStore.setState((s) => ({ ...s, error }));\n }\n } finally {\n this.rootStore.setState((s) => ({ ...s, isThreadLoading: false }));\n if (hydrationError != null) {\n this.#rejectHydration(hydrationError);\n } else {\n this.#resolveHydration();\n }\n }\n\n /**\n * P0 fix: open the shared subscription on mount so in-flight\n * server-side runs are observed even when no local `submit()` is\n * active. The transport replays the run from `seq=0` on a rotating\n * subscribe, so late-joining is free once the subscription exists.\n * `isLoading` transitions are driven by the persistent root\n * lifecycle listener registered in `#startRootPump`.\n */\n const thread = this.#ensureThread(this.#currentThreadId);\n\n /**\n * Start the wildcard lifecycle watcher up-front for existing\n * threads. The root content pump runs at `depth: 1`, which covers\n * root-namespace and one-deep events but not arbitrarily-nested\n * subagent / subgraph lifecycle — the dedicated watcher handles\n * those.\n *\n * For self-created (new) threads we skip — the watcher would 404\n * against a not-yet-existent thread. `submitRun` / `respondInput`\n * call `startLifecycleWatcher` on first submission to cover that\n * case.\n */\n if (threadExists) {\n thread.startLifecycleWatcher();\n }\n }\n\n /**\n * Submit input or a resume command to the active thread.\n *\n * @param input - Input payload for a new run; `null`/`undefined` submits no input.\n * @param options - Per-run config, metadata, multitask behavior, and callbacks.\n */\n async submit(\n input: unknown,\n options?: StreamSubmitOptions<StateType, ConfigurableType>\n ): Promise<void> {\n await this.#submitter.submit(input, options);\n }\n\n /**\n * Abort the currently tracked run and mark the controller idle.\n */\n async stop(): Promise<void> {\n await this.#submitter.stop();\n }\n\n #markLocalRunStart(): void {\n this.#localRunDepth += 1;\n }\n\n #markLocalRunEnd(): void {\n this.#localRunDepth = Math.max(0, this.#localRunDepth - 1);\n }\n\n #notifyCreated(runId: string): void {\n this.#activeRunId = runId;\n try {\n this.#options.onCreated?.({ runId });\n } catch {\n /* caller-supplied callback errors must not crash the stream */\n }\n }\n\n #notifyCompleted(\n reason: RunExecutionReason,\n runId = this.#activeRunId\n ): void {\n if (runId != null && runId === this.#activeRunId) {\n this.#activeRunId = undefined;\n }\n setTimeout(() => {\n if (this.#disposed) return;\n try {\n this.#options.onCompleted?.(\n runId == null ? { reason } : { runId, reason }\n );\n } catch {\n /* caller-supplied callback errors must not crash the stream */\n }\n }, 0);\n }\n\n readonly #runLifecycleListener = (event: Event): void => {\n if (this.#localRunDepth > 0) return;\n if (event.method !== \"lifecycle\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n if (!this.rootStore.getSnapshot().isLoading) return;\n const lifecycle = (event as LifecycleEvent).params.data as {\n event?: string;\n };\n const reason = lifecycleReason(lifecycle?.event);\n if (reason == null) return;\n this.#notifyCompleted(reason);\n };\n\n /**\n * Cancel a queued submission by id. Returns `true` when the entry\n * was found and removed, `false` otherwise.\n *\n * Today this only removes the entry from the client-side mirror —\n * once the server exposes queue cancel (roadmap A0.3) the\n * controller will additionally issue a cancel call against the\n * active transport.\n *\n * @param id - Client-side queue entry id to remove.\n */\n async cancelQueued(id: string): Promise<boolean> {\n return this.#submitter.cancelQueued(id);\n }\n\n /**\n * Drop every queued submission. Server-side cancel arrives with A0.3.\n */\n async clearQueue(): Promise<void> {\n await this.#submitter.clearQueue();\n }\n\n /**\n * Respond to a pending protocol interrupt.\n *\n * @param response - Payload to send back to the interrupted namespace.\n * @param target - Optional explicit interrupt id and namespace; defaults to the latest unresolved interrupt.\n */\n async respond(\n response: unknown,\n target?: { interruptId: string; namespace?: string[] }\n ): Promise<void> {\n if (this.#disposed || this.#thread == null) {\n throw new Error(\"No active thread to respond to.\");\n }\n const resolved =\n target != null\n ? {\n interruptId: target.interruptId,\n namespace: target.namespace ?? [...ROOT_NAMESPACE],\n }\n : this.#latestUnresolvedInterrupt();\n if (resolved == null) {\n throw new Error(\"No pending interrupt to respond to.\");\n }\n try {\n await this.#thread.respondInput({\n namespace: resolved.namespace,\n interrupt_id: resolved.interruptId,\n response,\n });\n this.#resolvedInterrupts.add(resolved.interruptId);\n } catch (error) {\n if (this.#disposed && isAbortLikeError(error)) {\n return;\n }\n throw error;\n }\n }\n\n /**\n * Dispose the active thread, subscriptions, registry entries, and listeners.\n */\n async dispose(): Promise<void> {\n if (this.#disposed) return;\n this.#cancelPendingDispose();\n this.#disposed = true;\n this.#submitter.abortActiveRun();\n await this.#teardownThread();\n await this.registry.dispose();\n this.#threadListeners.clear();\n }\n\n /**\n * StrictMode-safe lifecycle hook for framework bindings.\n *\n * React 18+ `StrictMode` intentionally mounts → unmounts → remounts\n * components in dev to surface effect-cleanup bugs. A naive\n * `useEffect(() => () => controller.dispose())` would permanently\n * tear the controller down on that first synthetic unmount, leaving\n * every subsequent `submit()` a silent no-op.\n *\n * Call {@link activate} from the bind site's effect and return the\n * result as the effect's cleanup. The controller uses deferred\n * disposal: a `release()` only schedules a dispose on the next\n * microtask, which is cancelled if another `activate()` arrives\n * before it fires (the normal StrictMode remount path).\n */\n activate(): () => void {\n this.#cancelPendingDispose();\n return () => {\n if (this.#disposed) return;\n this.#pendingDisposeTimer = setTimeout(() => {\n this.#pendingDisposeTimer = null;\n void this.dispose().catch(() => undefined);\n }, 0);\n };\n }\n\n /**\n * Cancel a deferred dispose scheduled by {@link activate}.\n */\n #cancelPendingDispose(): void {\n if (this.#pendingDisposeTimer != null) {\n clearTimeout(this.#pendingDisposeTimer);\n this.#pendingDisposeTimer = null;\n }\n }\n\n // ---------- escape hatches ----------\n\n /**\n * Current underlying {@link ThreadStream} (v2 escape hatch).\n */\n getThread(): ThreadStream | undefined {\n return this.#thread;\n }\n\n /**\n * Listen for `ThreadStream` lifecycle (swap on thread-id change,\n * detach on dispose). The listener fires immediately with the\n * current thread (may be `undefined`).\n *\n * @param listener - Callback invoked immediately and on every thread swap.\n */\n subscribeThread(\n listener: (thread: ThreadStream | undefined) => void\n ): () => void {\n this.#threadListeners.add(listener);\n listener(this.#thread);\n return () => {\n this.#threadListeners.delete(listener);\n };\n }\n\n // ---------- internals ----------\n\n /**\n * Build the initial root snapshot from configured initial values.\n */\n #createInitialSnapshot(): RootSnapshot<StateType, InterruptType> {\n const values = (this.#options.initialValues ??\n ({} as StateType)) as StateType;\n const messages = extractAndCoerceMessages(\n values as unknown as Record<string, unknown>,\n this.#messagesKey\n );\n /**\n * Seed `isThreadLoading: true` synchronously when a caller-supplied\n * threadId is on the controller at construction/swap time. Without\n * this Suspense wrappers would render their fallback for a tick\n * because `isThreadLoading` flips false → true → false once the\n * deferred `hydrate()` starts, and the synchronous render observes\n * the initial `false`.\n */\n const willHydrate =\n this.#currentThreadId != null &&\n !this.#selfCreatedThreadIds.has(this.#currentThreadId);\n return {\n values,\n messages,\n toolCalls: [],\n interrupts: [],\n interrupt: undefined,\n isLoading: false,\n isThreadLoading: willHydrate,\n error: undefined,\n threadId: this.#currentThreadId,\n };\n }\n\n /**\n * Return the active thread stream, creating and binding one when needed.\n *\n * @param threadId - Thread id used when constructing the stream.\n * @param deferRootPump - When `true`, build the ThreadStream and bind\n * the registry but skip starting the persistent root SSE pump. Used\n * for client-self-created thread ids whose server-side thread row\n * doesn't exist yet — opening the pump's `subscription.subscribe`\n * against a not-yet-existent thread produces a `404: Thread not\n * found` protocol error that strands terminal lifecycle events and\n * leaves the UI showing nothing until the user reloads. The pump is\n * started later via {@link #startDeferredRootPump} after `submitRun`\n * / `respondInput` commits the thread server-side.\n *\n * Note: PR 2381's `#runStartReady` gate covers the analogous race\n * for the in-flight `run.start` send, but only when that send is\n * already pending. `#ensureThread` runs *before* `submitRun` is\n * called (and thus before the gate is armed), so on transports\n * that subscribe synchronously (WebSocket) the deferred path is\n * still required.\n */\n #ensureThread(threadId: string, deferRootPump = false): ThreadStream {\n if (this.#thread != null) return this.#thread;\n this.#thread = this.#options.client.threads.stream(threadId, {\n assistantId: this.#options.assistantId,\n transport: this.#options.transport,\n fetch: this.#options.fetch,\n webSocketFactory: this.#options.webSocketFactory,\n });\n this.registry.bind(this.#thread);\n if (deferRootPump) {\n // Resolve `#rootPumpReady` immediately so `submit()`'s `await\n // this.#rootPumpReady` doesn't block — the dispatch path only\n // needs the ThreadStream wired up to call `submitRun`, not the\n // persistent subscription.\n this.#rootPumpReady = Promise.resolve();\n this.#rootPumpDeferred = true;\n } else {\n this.#startRootPump(this.#thread);\n }\n this.#notifyThreadListeners();\n return this.#thread;\n }\n\n /**\n * Start the previously-deferred root SSE pump after the first\n * `submitRun` / `respondInput` has committed the thread server-side.\n *\n * No-op when the pump was started eagerly in {@link #ensureThread}\n * (i.e. for hydrated existing threads, or for any thread whose pump\n * has already been brought up).\n */\n #startDeferredRootPump(): void {\n if (!this.#rootPumpDeferred) return;\n if (this.#thread == null) return;\n this.#rootPumpDeferred = false;\n this.#startRootPump(this.#thread);\n }\n\n /**\n * Abandon a deferred root pump that never started because its\n * triggering dispatch (`submitRun` / `respondInput`) failed.\n *\n * Without this, the controller would be wedged in a state where:\n * - `#thread` is wired but no content pump is open\n * - `#rootPumpDeferred` stays `true`\n * - `selfCreatedThreadIds` still holds the id\n *\n * A retry submit on the same controller would see\n * `wasSelfCreated=false` (because `currentThreadId` is no longer\n * null), `#ensureThread(id, false)` would early-return because\n * `#thread != null`, and the pump would never start. The thread\n * would have an id committed to the URL but no live subscription.\n *\n * Tearing down `#thread` so the next submit re-runs `#ensureThread`\n * from scratch is the simplest recovery — the failed dispatch\n * means there was nothing to subscribe to anyway.\n */\n #abandonDeferredRootPump(): void {\n if (!this.#rootPumpDeferred) return;\n this.#rootPumpDeferred = false;\n void this.#teardownThread();\n }\n\n /**\n * Close the current thread stream and reset per-thread assembly state.\n */\n async #teardownThread(): Promise<void> {\n const thread = this.#thread;\n this.#thread = undefined;\n this.registry.bind(undefined);\n this.#threadEventUnsubscribe?.();\n this.#threadEventUnsubscribe = undefined;\n /**\n * Persistent lifecycle driver is scoped to the current thread\n * stream. Remove it so a swap to a new thread starts with a clean\n * listener set (a new one is installed in `#startRootPump`).\n */\n this.#rootEventListeners.delete(this.#lifecycleLoading.listener);\n this.#rootEventListeners.delete(this.#runLifecycleListener);\n try {\n await this.#rootSubscription?.unsubscribe();\n } catch {\n /* already closed */\n }\n this.#rootSubscription = undefined;\n this.#rootPumpReady = undefined;\n // Reset so a swap to a new thread doesn't carry over a stale\n // deferred flag — `#ensureThread` will set it again if the new\n // thread is self-created.\n this.#rootPumpDeferred = false;\n try {\n await this.#rootPump;\n } catch {\n /* ignore */\n }\n this.#rootPump = undefined;\n\n // Reset per-thread assembly state.\n this.#rootMessages.reset();\n this.#rootToolAssembler = new ToolCallAssembler();\n this.#lifecycleLoading.reset();\n this.#activeRunId = undefined;\n this.#localRunDepth = 0;\n this.#messageMetadata.reset();\n // Drop the hydrate-window allowlist — the next thread's hydrate\n // will repopulate it from that thread's `state.tasks[].interrupts`.\n this.#hydratedActiveInterruptIds = null;\n this.queueStore.setState(\n () => EMPTY_QUEUE as SubmissionQueueSnapshot<StateType>\n );\n\n if (thread != null) {\n try {\n await thread.close();\n } catch {\n /* already closed */\n }\n this.#notifyThreadListeners();\n }\n }\n\n /**\n * Determine whether the configured transport uses the resumable event-stream path.\n */\n #usesEventStreamTransport(): boolean {\n const transport = this.#options.transport;\n if (transport === \"websocket\") return false;\n if (transport == null || transport === \"sse\") return true;\n return typeof transport.openEventStream === \"function\";\n }\n\n /**\n * Start the always-on root subscription pump for the provided thread.\n *\n * @param thread - Thread stream to subscribe to and fan out from.\n */\n #startRootPump(thread: ThreadStream): void {\n if (this.#rootPump != null) return;\n let resolveReady: (() => void) | undefined;\n this.#rootPumpReady = new Promise<void>((resolve) => {\n resolveReady = resolve;\n });\n\n /**\n * Wildcard discovery + interrupt tracking is delivered via the\n * thread's dedicated lifecycle watcher (see `ThreadStream.onEvent`).\n * This callback fires once per globally-unique event across both\n * the content pump AND the watcher, so we can drive discovery\n * runners and nested HITL capture without widening the content\n * pump's narrow filter.\n */\n this.#threadEventUnsubscribe = thread.onEvent((event) =>\n this.#onWildcardEvent(event)\n );\n\n /**\n * Persistent isLoading driver. Drives `isLoading` from\n * root-namespace lifecycle events so that in-flight runs observed\n * via `hydrate()` (not initiated by a local `submit()`) still flip\n * the UI to loading. `running` → true; terminals → false. The\n * optimistic `isLoading = true` inside `submit()` stays because\n * that fires before any subscription event arrives.\n */\n this.#rootEventListeners.add(this.#lifecycleLoading.listener);\n this.#rootEventListeners.add(this.#runLifecycleListener);\n\n this.#rootPump = (async () => {\n try {\n /**\n * Root content pump: depth 1 is required because the controller\n * classifies tool events at namespace length ≤ 1 as root-level\n * (see `#onWildcardEvent`'s `isRootLevelTool` check). The deep-\n * agent `task` dispatcher fires `tools.tool-started` at\n * `[\"tools:<id>\"]` (length 1), so a depth-0 filter would drop\n * those events server-side before they reached `root.toolCalls`.\n *\n * Deeper content (subagent message tokens, values snapshots) is\n * pulled in on demand by per-namespace selector projections\n * (e.g. `useMessages(sub)`).\n */\n const subscriptionPromise = thread.subscribe({\n channels: [...ROOT_PUMP_CHANNELS] as Channel[],\n namespaces: [[] as string[]],\n depth: 1,\n });\n if (this.#usesEventStreamTransport()) {\n /**\n * SSE streams can legitimately withhold response headers until\n * the first event is available. Waiting for `subscribe()` here\n * would deadlock new-thread submits: the run is not dispatched\n * until the root pump is \"ready\", but the pump does not become\n * ready until the run emits. `thread.subscribe()` has already\n * registered the local subscription and scheduled the stream\n * rotation by this point; waiting one microtask lets the fetch\n * get kicked off without requiring headers to arrive.\n */\n queueMicrotask(() => {\n resolveReady?.();\n resolveReady = undefined;\n });\n }\n const subscription = await subscriptionPromise;\n resolveReady?.();\n resolveReady = undefined;\n this.#rootSubscription = subscription;\n /**\n * The SSE transport pauses the underlying subscription when\n * a terminal root lifecycle event arrives (so `for await`\n * loops observing a single run exit cleanly) and re-opens\n * the next run's server stream on `#prepareForNextRun`,\n * resuming the subscription handle. The root pump needs to\n * survive that hand-off: we re-enter the inner `for await`\n * for every resumed iteration until the subscription is\n * permanently closed or the controller is disposed.\n */\n while (!this.#disposed) {\n for await (const event of subscription) {\n if (this.#disposed) {\n break;\n }\n /**\n * Resilience: isolate per-event dispatch from the pump loop.\n *\n * `#onRootEvent` runs synchronously and, transitively,\n * invokes every root-bus listener (selector projections that\n * opted into the shared stream) plus every `rootStore`\n * subscriber. Some of those subscribers live in a React\n * render tree — `useStream` drives\n * `useSyncExternalStore`, so a misbehaving component can\n * surface a render-phase error (\"Maximum update depth\n * exceeded\", \"The result of getSnapshot should be cached\",\n * etc.) that propagates out here.\n *\n * Without this guard, a single throw bubbles through the\n * `for await` loop and terminates the root pump permanently.\n * That is catastrophic: no more root events get processed —\n * the terminal `lifecycle: completed` never lands, so\n * `#awaitNextTerminal` never resolves, `isLoading` stays\n * `true`, composers stay disabled, and the final assistant\n * turn never commits to `stream.messages`. The UI looks\n * hung even though the server is still emitting events\n * (and `ThreadStream.onEvent` keeps firing).\n *\n * We therefore swallow the error and keep pumping. The\n * the pump's correctness guarantees do not depend on any\n * consumer behaving well.\n */\n try {\n this.#onRootEvent(event);\n } catch {\n /**\n * Best-effort — a consumer-facing store subscriber should not\n * terminate the root pump. Store mutations happen before\n * listeners are notified, so continuing with later events keeps\n * the controller's authoritative state moving forward.\n */\n }\n }\n if (this.#disposed) break;\n if (!subscription.isPaused) {\n break;\n }\n await subscription.waitForResume();\n }\n } catch {\n resolveReady?.();\n resolveReady = undefined;\n /* thread closed or errored */\n }\n })();\n }\n\n /**\n * Handle an event delivered via {@link ThreadStream.onEvent}.\n *\n * `onEvent` fires once per globally-unique event across the content\n * pump and the wildcard lifecycle watcher, so this is the single\n * entry point for wildcard discovery / interrupt tracking. It does\n * NOT fan events out to the root bus (that's driven by the content\n * pump iterator so root-bus short-circuits stay depth-1 scoped) and\n * it does NOT process root content — messages/tools/values at root\n * are handled by `#onRootEvent` off the content pump.\n *\n * @param event - Raw protocol event observed by the thread-wide listener.\n */\n #onWildcardEvent(event: Event): void {\n try {\n this.#subagents.push(event);\n } catch {\n /**\n * Discovery store subscribers are user/UI code. If one throws, still\n * let the wildcard watcher update subgraphs, loading, and interrupts.\n */\n }\n this.#subgraphs.push(event);\n this.#lifecycleLoading.handle(event);\n\n /**\n * Nested `input.requested` events (HITL inside a subagent /\n * subgraph) are not observable via the narrow content pump. The\n * `ThreadStream` itself already records them into\n * `thread.interrupts`, which `#latestUnresolvedInterrupt()`\n * consults — so HITL respond() works for any depth. Root-level\n * interrupts are also mirrored into `rootStore.interrupts` here so\n * UI state does not depend on the narrower content pump being the\n * first consumer to see the event.\n */\n this.#recordRootInterrupt(event);\n }\n\n /**\n * Process one root-pump event and update all root projections.\n *\n * @param event - Event yielded by the root subscription.\n */\n #onRootEvent(event: Event): void {\n try {\n this.#subagents.push(event);\n } catch {\n /**\n * Discovery store subscribers are user/UI code. If one throws, still\n * process the root event below so orchestrator messages and terminal\n * state continue to advance.\n */\n }\n\n /**\n * Fan root-pump events out to every root-bus listener (selector\n * projections that opted into the shared stream,\n * `#awaitTerminal`, etc.). The root bus mirrors the content\n * pump's narrow scope (depth 1 at root) so projections that\n * short-circuit via the bus stay bounded.\n */\n if (this.#rootEventListeners.size > 0) {\n for (const listener of this.#rootEventListeners) {\n try {\n listener(event);\n } catch {\n /**\n * Best-effort — a bad listener should not wedge other\n * projections or the root pump itself.\n */\n }\n }\n }\n\n /**\n * `messages` and `tools` events are emitted under a node's\n * namespace — for a typical StateGraph the LLM's token deltas\n * land on `[\"model:<uuid>\"]`, tool executions on\n * `[\"tools:<uuid>\"]`, etc. The orchestrator's own turns (root\n * agent, or an orchestrator-scoped subgraph like `model:*` /\n * `model_request:*`) belong in `root.messages` and\n * `root.toolCalls`.\n *\n * Subagent / tool-internal branches do NOT:\n * - `task:*` segment — legacy subagent convention.\n * - `tools:*` segment — every tool execution is wrapped in a\n * `tools` subgraph. For simple tools its only content is\n * the eventual tool result (also echoed verbatim by\n * `values.messages` so we don't lose anything). For the\n * deep-agent `task` tool its content IS the spawned\n * subagent's full message + tool stream, which is surfaced\n * separately via `useMessages(stream, subagent)` /\n * `useToolCalls(stream, subagent)`.\n *\n * We therefore drop `messages` events from any namespace that\n * contains a `task:*` or `tools:*` segment; the authoritative\n * tool-result text lands in `root.messages` via the root\n * `values.messages` snapshot merge in `#applyValues`.\n */\n const isInternalNamespace = isInternalWorkNamespace(event.params.namespace);\n const hasLegacySubagentNamespace = isLegacySubagentNamespace(\n event.params.namespace\n );\n\n if (event.method === \"messages\") {\n if (!isInternalNamespace) {\n this.#rootMessages.handleMessage(event as MessagesEvent);\n }\n return;\n }\n\n if (event.method === \"tools\") {\n /**\n * Root-level tool events (both for simple orchestrator tools\n * and the deep-agent `task` dispatcher) fire at a\n * single-segment `[\"tools:<id>\"]` namespace. Anything deeper\n * (e.g. `[tools:<outer>, tools:<inner>]`) is a subagent's own\n * tool call and belongs to that subagent's `useToolCalls`\n * view, not the orchestrator's `root.toolCalls`.\n */\n const isRootLevelTool =\n event.params.namespace.length <= 1 && !hasLegacySubagentNamespace;\n if (isRootLevelTool) {\n /**\n * Record the `namespace → tool_call_id` association so that\n * the ensuing `message-start` (role: \"tool\") at the same\n * namespace can recover the `tool_call_id` (the `messages`\n * channel's start event doesn't carry it directly).\n */\n const toolData = event.params.data as {\n event?: string;\n tool_call_id?: string;\n };\n if (\n toolData.event === \"tool-started\" &&\n typeof toolData.tool_call_id === \"string\"\n ) {\n this.#rootMessages.recordToolCallNamespace(\n event.params.namespace,\n toolData.tool_call_id\n );\n }\n const tc = this.#rootToolAssembler.consume(event as ToolsEvent);\n if (tc != null) {\n this.rootStore.setState((s) => ({\n ...s,\n toolCalls: upsertToolCall(s.toolCalls, tc),\n }));\n }\n }\n return;\n }\n\n /**\n * The `checkpoints` channel carries the lightweight envelope\n * (`id`, `parent_id`, `step`, `source`) emitted immediately\n * before its companion `values` event on the same superstep.\n * Buffer the envelope per-namespace so the ensuing `values`\n * event at the same namespace can pair with it in `#applyValues`.\n * The buffer is read-and-cleared on consumption so a subsequent\n * `values` event without a new checkpoint doesn't reuse stale\n * metadata.\n */\n if (event.method === \"checkpoints\") {\n const data = event.params.data as {\n id?: unknown;\n parent_id?: unknown;\n } | null;\n this.#messageMetadata.bufferCheckpoint(event.params.namespace, data);\n return;\n }\n\n // Channels below are only meaningful at the root namespace.\n const isRoot = isRootNamespace(event.params.namespace);\n if (!isRoot) return;\n\n if (event.method === \"values\") {\n const valuesEvent = event as ValuesEvent;\n const bufferedCheckpoint = this.#messageMetadata.consumeCheckpoint(\n event.params.namespace\n );\n this.#applyValues(valuesEvent.params.data, bufferedCheckpoint);\n return;\n }\n\n if (event.method === \"input.requested\") {\n this.#recordRootInterrupt(event);\n return;\n }\n\n if (event.method === \"lifecycle\") {\n /**\n * Root lifecycle transitions are observed elsewhere\n * (#awaitTerminal) to unblock `submit`.\n */\n const lifecycle = (event as LifecycleEvent).params.data as {\n event?: string;\n };\n void lifecycle;\n }\n }\n\n /**\n * Merge a `values` payload into root values and root messages.\n *\n * @param raw - Raw `values` channel payload.\n * @param checkpoint - Optional checkpoint envelope paired with the values event.\n */\n #applyValues(raw: unknown, checkpoint?: CheckpointEnvelope): void {\n if (raw == null || typeof raw !== \"object\" || Array.isArray(raw)) {\n return;\n }\n const state = raw as Record<string, unknown>;\n /**\n * Surface parent_checkpoint per-message when the values event\n * carries the lightweight checkpoint envelope (populated by\n * `@langchain/langgraph-core`'s `_emitValuesWithCheckpointMeta` and\n * forwarded through `convertToProtocolEvent`). Consumers surface\n * this as `useMessageMetadata(stream, msg.id).parentCheckpointId`\n * for fork / edit flows.\n */\n const parentCheckpointId = checkpoint?.parent_id;\n if (parentCheckpointId != null && Array.isArray(state[this.#messagesKey])) {\n this.#messageMetadata.recordMessages(\n state[this.#messagesKey] as Array<{ id?: string }>,\n { parentCheckpointId }\n );\n }\n const maybeMessages = state[this.#messagesKey];\n let nextValues: StateType;\n let nextMessages: BaseMessage[];\n if (Array.isArray(maybeMessages)) {\n const coerced = ensureMessageInstances(\n maybeMessages as (Message | BaseMessage)[]\n );\n nextValues = {\n ...(state as StateType),\n [this.#messagesKey]: coerced,\n } as StateType;\n nextMessages = coerced;\n } else {\n nextValues = state as StateType;\n nextMessages = [];\n }\n this.#rootMessages.applyValues(nextValues, nextMessages);\n }\n\n /**\n * Mirror root protocol interrupts into the root snapshot.\n *\n * This can be called from both the wildcard lifecycle/input watcher and the\n * root content pump. Store-level dedup keeps the user-facing list stable.\n */\n #recordRootInterrupt(event: Event): void {\n if (event.method !== \"input.requested\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n const data = event.params.data as {\n interrupt_id?: string;\n payload?: unknown;\n };\n const interruptId = data?.interrupt_id;\n if (\n typeof interruptId !== \"string\" ||\n this.#resolvedInterrupts.has(interruptId)\n ) {\n return;\n }\n // Strict allowlist when populated by the most-recent hydrate: SSE\n // replay of `input.requested` carries no signal distinguishing\n // historical (already-resolved) interrupts from live ones, so we\n // accept only ids the server reported as currently active in\n // `state.tasks[].interrupts`. `null` (outside the hydrate window\n // / after a submit clears it) disables filtering entirely so new\n // live interrupts on an active run pass through.\n if (\n this.#hydratedActiveInterruptIds != null &&\n !this.#hydratedActiveInterruptIds.has(interruptId)\n ) {\n return;\n }\n const interrupt: Interrupt<InterruptType> = {\n id: interruptId,\n value: data.payload as InterruptType,\n };\n this.rootStore.setState((s) => {\n if (s.interrupts.some((entry) => entry.id === interruptId)) return s;\n const interrupts = [...s.interrupts, interrupt];\n return { ...s, interrupts, interrupt: interrupts[0] };\n });\n }\n\n /**\n * Resolve on the next root-namespace terminal lifecycle event\n * (`completed` / `failed` / `interrupted`) or on abort.\n *\n * Attaches to the controller's root event bus instead of opening\n * a second server subscription. Callers should register the\n * returned promise **before** dispatching the command that\n * triggers the run (`thread.run.start` / `thread.input.respond`)\n * — the root pump fans events out synchronously on arrival, so a\n * late registration would miss the terminal for fast runs.\n *\n * @param signal - Abort signal for the local submit lifecycle.\n */\n #awaitNextTerminal(signal: AbortSignal): Promise<{\n event: \"completed\" | \"failed\" | \"interrupted\" | \"aborted\";\n error?: string;\n }> {\n return new Promise((resolve) => {\n let settled = false;\n function finish(result: {\n event: \"completed\" | \"failed\" | \"interrupted\" | \"aborted\";\n error?: string;\n }) {\n if (settled) return;\n settled = true;\n unsubscribeRoot?.();\n unsubscribeThread?.();\n signal.removeEventListener(\"abort\", finishAborted);\n resolve(result);\n }\n const finishAborted = () => finish({ event: \"aborted\" });\n const onEvent = (event: Event) => {\n if (settled) return;\n if (event.method !== \"lifecycle\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n const lifecycle = (event as LifecycleEvent).params.data as {\n event?: string;\n error?: string;\n };\n if (lifecycle?.event === \"completed\") {\n setTimeout(() => finish({ event: \"completed\" }), 0);\n } else if (lifecycle?.event === \"failed\") {\n setTimeout(\n () => finish({ event: \"failed\", error: lifecycle.error }),\n 0\n );\n } else if (lifecycle?.event === \"interrupted\") {\n setTimeout(() => finish({ event: \"interrupted\" }), 0);\n }\n };\n const unsubscribeRoot = this.#rootBus.subscribe(onEvent);\n const unsubscribeThread = this.#thread?.onEvent(onEvent);\n if (signal.aborted) {\n finishAborted();\n } else {\n signal.addEventListener(\"abort\", finishAborted, { once: true });\n }\n });\n }\n\n /**\n * Find the newest unresolved interrupt recorded on the active thread.\n */\n #latestUnresolvedInterrupt(): ResolvedInterrupt | null {\n const thread = this.#thread;\n if (thread == null) return null;\n for (let i = thread.interrupts.length - 1; i >= 0; i -= 1) {\n const entry = thread.interrupts[i];\n if (entry == null) continue;\n if (this.#resolvedInterrupts.has(entry.interruptId)) continue;\n return {\n interruptId: entry.interruptId,\n namespace: entry.namespace,\n };\n }\n return null;\n }\n\n /**\n * Notify listeners that the underlying thread stream changed.\n */\n #notifyThreadListeners(): void {\n for (const listener of this.#threadListeners) listener(this.#thread);\n }\n}\n\n// ---------- helpers ----------\n\n/**\n * Extract and coerce the configured messages key from a values object.\n *\n * @param values - State values object to read from.\n * @param messagesKey - Key that contains the message array.\n */\nfunction extractAndCoerceMessages(\n values: Record<string, unknown>,\n messagesKey: string\n): BaseMessage[] {\n const raw = values[messagesKey];\n if (!Array.isArray(raw)) return [];\n return ensureMessageInstances(\n raw as (Message | BaseMessage)[]\n ) as BaseMessage[];\n}\n\n// Unused import guard — `AIMessage` is only referenced by type tests.\nvoid AIMessage;\n"],"mappings":"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"}
|
package/dist/stream/index.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_tools = require("../client/stream/handles/tools.cjs");
|
|
2
3
|
const require_media = require("../client/stream/media.cjs");
|
|
3
4
|
const require_constants = require("./constants.cjs");
|
|
4
5
|
const require_store = require("./store.cjs");
|
|
@@ -31,6 +32,8 @@ exports.extensionProjection = require_extension.extensionProjection;
|
|
|
31
32
|
exports.filesProjection = require_media$1.filesProjection;
|
|
32
33
|
exports.imagesProjection = require_media$1.imagesProjection;
|
|
33
34
|
exports.messagesProjection = require_messages.messagesProjection;
|
|
35
|
+
exports.parseToolOutput = require_tools.parseToolOutput;
|
|
36
|
+
exports.parseToolPayload = require_tools.parseToolPayload;
|
|
34
37
|
exports.toolCallsProjection = require_tool_calls.toolCallsProjection;
|
|
35
38
|
exports.valuesProjection = require_values.valuesProjection;
|
|
36
39
|
exports.videoProjection = require_media$1.videoProjection;
|
package/dist/stream/index.d.cts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { DefaultToolCall, ToolCallFromTool } from "../types.messages.cjs";
|
|
2
|
-
import { AssembledToolCall, ToolCallStatus } from "../client/stream/handles/tools.cjs";
|
|
1
|
+
import { DefaultToolCall, InferToolOutput, ToolCallFromTool } from "../types.messages.cjs";
|
|
2
|
+
import { AssembledToolCall, ToolCallStatus, parseToolOutput, parseToolPayload } from "../client/stream/handles/tools.cjs";
|
|
3
3
|
import { AnyMediaHandle, AudioMedia, FileMedia, ImageMedia, MediaAssembler, MediaAssemblerCallbacks, MediaAssemblerOptions, MediaAssemblyError, MediaAssemblyErrorKind, MediaBase, MediaBlockType, VideoMedia } from "../client/stream/media.cjs";
|
|
4
4
|
import { AgentServerAdapter, TransportAdapter } from "../client/stream/transport.cjs";
|
|
5
5
|
import { AgentTypeConfigLike, CompiledSubAgentLike, DeepAgentTypeConfigLike, DefaultSubagentStates, ExtractAgentConfig, ExtractDeepAgentConfig, ExtractSubAgentMiddleware, InferAgentToolCalls, InferDeepAgentSubagents, InferSubagentByName, InferSubagentNames, InferSubagentState, IsAgentLike, IsDeepAgentLike, SubAgentLike, SubagentStateMap, SubagentToolCall } from "../ui/types.cjs";
|
|
6
6
|
import { InferBag, InferNodeNames } from "../ui/stream/index.cjs";
|
|
7
|
-
import { InferStateType, InferSubagentStates, InferToolCalls, WidenUpdateMessages } from "./types-inference.cjs";
|
|
7
|
+
import { AssembledToolCallFromTool, InferStateType, InferSubagentStates, InferToolCalls, WidenUpdateMessages } from "./types-inference.cjs";
|
|
8
8
|
import { StoreListener, StreamStore } from "./store.cjs";
|
|
9
|
-
import { AcquiredProjection, AgentServerOptions, CustomAdapterOptions, ProjectionRuntime, ProjectionSpec, RootEventBus, RootSnapshot,
|
|
9
|
+
import { AcquiredProjection, AgentServerOptions, CustomAdapterOptions, ProjectionRuntime, ProjectionSpec, RootEventBus, RootSnapshot, RunCompletedInfo, RunExecutionInfo, RunExecutionReason, StreamControllerOptions, StreamSubmitOptions, SubagentDiscoverySnapshot, SubgraphDiscoverySnapshot, Target, UseStreamCommonOptions, UseStreamOptions } from "./types.cjs";
|
|
10
10
|
import { ChannelRegistry } from "./channel-registry.cjs";
|
|
11
11
|
import { SubagentDiscovery, SubagentMap } from "./discovery/subagents.cjs";
|
|
12
12
|
import { SubgraphByNodeMap, SubgraphDiscovery, SubgraphMap } from "./discovery/subgraphs.cjs";
|
|
@@ -22,4 +22,4 @@ import { MediaProjectionOptions, audioProjection, filesProjection, imagesProject
|
|
|
22
22
|
import { AssembledToMessageInput, ExtendedMessageRole, assembledMessageToBaseMessage, assembledToBaseMessage } from "./assembled-to-message.cjs";
|
|
23
23
|
import { NAMESPACE_SEPARATOR } from "./constants.cjs";
|
|
24
24
|
import { Channel, Event } from "@langchain/protocol";
|
|
25
|
-
export { type AcquiredProjection, type AgentServerAdapter, type AgentServerOptions, type AgentTypeConfigLike, type AnyMediaHandle, type AssembledToMessageInput, type AssembledToolCall, type AudioMedia, type Channel, type ChannelProjectionOptions, ChannelRegistry, type CompiledSubAgentLike, type CustomAdapterOptions, type DeepAgentTypeConfigLike, type DefaultSubagentStates, type DefaultToolCall, type Event, type ExtendedMessageRole, type ExtractAgentConfig, type ExtractDeepAgentConfig, type ExtractSubAgentMiddleware, type FileMedia, type ImageMedia, type InferAgentToolCalls, type InferBag, type InferDeepAgentSubagents, type InferNodeNames, type InferStateType, type InferSubagentByName, type InferSubagentNames, type InferSubagentState, type InferSubagentStates, type InferToolCalls, type IsAgentLike, type IsDeepAgentLike, MediaAssembler, type MediaAssemblerCallbacks, type MediaAssemblerOptions, MediaAssemblyError, type MediaAssemblyErrorKind, type MediaBase, type MediaBlockType, type MediaProjectionOptions, type MessageMetadata, type MessageMetadataMap, NAMESPACE_SEPARATOR, type ProjectionRuntime, type ProjectionSpec, ROOT_PUMP_CHANNELS, type RootEventBus, type RootSnapshot, type
|
|
25
|
+
export { type AcquiredProjection, type AgentServerAdapter, type AgentServerOptions, type AgentTypeConfigLike, type AnyMediaHandle, type AssembledToMessageInput, type AssembledToolCall, type AssembledToolCallFromTool, type AudioMedia, type Channel, type ChannelProjectionOptions, ChannelRegistry, type CompiledSubAgentLike, type CustomAdapterOptions, type DeepAgentTypeConfigLike, type DefaultSubagentStates, type DefaultToolCall, type Event, type ExtendedMessageRole, type ExtractAgentConfig, type ExtractDeepAgentConfig, type ExtractSubAgentMiddleware, type FileMedia, type ImageMedia, type InferAgentToolCalls, type InferBag, type InferDeepAgentSubagents, type InferNodeNames, type InferStateType, type InferSubagentByName, type InferSubagentNames, type InferSubagentState, type InferSubagentStates, type InferToolCalls, type InferToolOutput, type IsAgentLike, type IsDeepAgentLike, MediaAssembler, type MediaAssemblerCallbacks, type MediaAssemblerOptions, MediaAssemblyError, type MediaAssemblyErrorKind, type MediaBase, type MediaBlockType, type MediaProjectionOptions, type MessageMetadata, type MessageMetadataMap, NAMESPACE_SEPARATOR, type ProjectionRuntime, type ProjectionSpec, ROOT_PUMP_CHANNELS, type RootEventBus, type RootSnapshot, type RunCompletedInfo, type RunExecutionInfo, type RunExecutionReason, type StoreListener, StreamController, type StreamControllerOptions, StreamStore, type StreamSubmitOptions, type SubAgentLike, SubagentDiscovery, type SubagentDiscoverySnapshot, type SubagentMap, type SubagentStateMap, type SubagentToolCall, type SubgraphByNodeMap, SubgraphDiscovery, type SubgraphDiscoverySnapshot, type SubgraphMap, type SubmissionQueueEntry, type SubmissionQueueSnapshot, type Target, type ToolCallFromTool, type ToolCallStatus, type TransportAdapter, type UseStreamCommonOptions, type UseStreamOptions, type VideoMedia, type WidenUpdateMessages, assembledMessageToBaseMessage, assembledToBaseMessage, audioProjection, channelProjection, extensionProjection, filesProjection, imagesProjection, messagesProjection, parseToolOutput, parseToolPayload, toolCallsProjection, valuesProjection, videoProjection };
|
package/dist/stream/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { DefaultToolCall, ToolCallFromTool } from "../types.messages.js";
|
|
2
|
-
import { AssembledToolCall, ToolCallStatus } from "../client/stream/handles/tools.js";
|
|
1
|
+
import { DefaultToolCall, InferToolOutput, ToolCallFromTool } from "../types.messages.js";
|
|
2
|
+
import { AssembledToolCall, ToolCallStatus, parseToolOutput, parseToolPayload } from "../client/stream/handles/tools.js";
|
|
3
3
|
import { AnyMediaHandle, AudioMedia, FileMedia, ImageMedia, MediaAssembler, MediaAssemblerCallbacks, MediaAssemblerOptions, MediaAssemblyError, MediaAssemblyErrorKind, MediaBase, MediaBlockType, VideoMedia } from "../client/stream/media.js";
|
|
4
4
|
import { AgentServerAdapter, TransportAdapter } from "../client/stream/transport.js";
|
|
5
5
|
import { AgentTypeConfigLike, CompiledSubAgentLike, DeepAgentTypeConfigLike, DefaultSubagentStates, ExtractAgentConfig, ExtractDeepAgentConfig, ExtractSubAgentMiddleware, InferAgentToolCalls, InferDeepAgentSubagents, InferSubagentByName, InferSubagentNames, InferSubagentState, IsAgentLike, IsDeepAgentLike, SubAgentLike, SubagentStateMap, SubagentToolCall } from "../ui/types.js";
|
|
6
6
|
import { InferBag, InferNodeNames } from "../ui/stream/index.js";
|
|
7
|
-
import { InferStateType, InferSubagentStates, InferToolCalls, WidenUpdateMessages } from "./types-inference.js";
|
|
7
|
+
import { AssembledToolCallFromTool, InferStateType, InferSubagentStates, InferToolCalls, WidenUpdateMessages } from "./types-inference.js";
|
|
8
8
|
import { StoreListener, StreamStore } from "./store.js";
|
|
9
|
-
import { AcquiredProjection, AgentServerOptions, CustomAdapterOptions, ProjectionRuntime, ProjectionSpec, RootEventBus, RootSnapshot,
|
|
9
|
+
import { AcquiredProjection, AgentServerOptions, CustomAdapterOptions, ProjectionRuntime, ProjectionSpec, RootEventBus, RootSnapshot, RunCompletedInfo, RunExecutionInfo, RunExecutionReason, StreamControllerOptions, StreamSubmitOptions, SubagentDiscoverySnapshot, SubgraphDiscoverySnapshot, Target, UseStreamCommonOptions, UseStreamOptions } from "./types.js";
|
|
10
10
|
import { ChannelRegistry } from "./channel-registry.js";
|
|
11
11
|
import { SubagentDiscovery, SubagentMap } from "./discovery/subagents.js";
|
|
12
12
|
import { SubgraphByNodeMap, SubgraphDiscovery, SubgraphMap } from "./discovery/subgraphs.js";
|
|
@@ -22,4 +22,4 @@ import { MediaProjectionOptions, audioProjection, filesProjection, imagesProject
|
|
|
22
22
|
import { AssembledToMessageInput, ExtendedMessageRole, assembledMessageToBaseMessage, assembledToBaseMessage } from "./assembled-to-message.js";
|
|
23
23
|
import { NAMESPACE_SEPARATOR } from "./constants.js";
|
|
24
24
|
import { Channel, Event } from "@langchain/protocol";
|
|
25
|
-
export { type AcquiredProjection, type AgentServerAdapter, type AgentServerOptions, type AgentTypeConfigLike, type AnyMediaHandle, type AssembledToMessageInput, type AssembledToolCall, type AudioMedia, type Channel, type ChannelProjectionOptions, ChannelRegistry, type CompiledSubAgentLike, type CustomAdapterOptions, type DeepAgentTypeConfigLike, type DefaultSubagentStates, type DefaultToolCall, type Event, type ExtendedMessageRole, type ExtractAgentConfig, type ExtractDeepAgentConfig, type ExtractSubAgentMiddleware, type FileMedia, type ImageMedia, type InferAgentToolCalls, type InferBag, type InferDeepAgentSubagents, type InferNodeNames, type InferStateType, type InferSubagentByName, type InferSubagentNames, type InferSubagentState, type InferSubagentStates, type InferToolCalls, type IsAgentLike, type IsDeepAgentLike, MediaAssembler, type MediaAssemblerCallbacks, type MediaAssemblerOptions, MediaAssemblyError, type MediaAssemblyErrorKind, type MediaBase, type MediaBlockType, type MediaProjectionOptions, type MessageMetadata, type MessageMetadataMap, NAMESPACE_SEPARATOR, type ProjectionRuntime, type ProjectionSpec, ROOT_PUMP_CHANNELS, type RootEventBus, type RootSnapshot, type
|
|
25
|
+
export { type AcquiredProjection, type AgentServerAdapter, type AgentServerOptions, type AgentTypeConfigLike, type AnyMediaHandle, type AssembledToMessageInput, type AssembledToolCall, type AssembledToolCallFromTool, type AudioMedia, type Channel, type ChannelProjectionOptions, ChannelRegistry, type CompiledSubAgentLike, type CustomAdapterOptions, type DeepAgentTypeConfigLike, type DefaultSubagentStates, type DefaultToolCall, type Event, type ExtendedMessageRole, type ExtractAgentConfig, type ExtractDeepAgentConfig, type ExtractSubAgentMiddleware, type FileMedia, type ImageMedia, type InferAgentToolCalls, type InferBag, type InferDeepAgentSubagents, type InferNodeNames, type InferStateType, type InferSubagentByName, type InferSubagentNames, type InferSubagentState, type InferSubagentStates, type InferToolCalls, type InferToolOutput, type IsAgentLike, type IsDeepAgentLike, MediaAssembler, type MediaAssemblerCallbacks, type MediaAssemblerOptions, MediaAssemblyError, type MediaAssemblyErrorKind, type MediaBase, type MediaBlockType, type MediaProjectionOptions, type MessageMetadata, type MessageMetadataMap, NAMESPACE_SEPARATOR, type ProjectionRuntime, type ProjectionSpec, ROOT_PUMP_CHANNELS, type RootEventBus, type RootSnapshot, type RunCompletedInfo, type RunExecutionInfo, type RunExecutionReason, type StoreListener, StreamController, type StreamControllerOptions, StreamStore, type StreamSubmitOptions, type SubAgentLike, SubagentDiscovery, type SubagentDiscoverySnapshot, type SubagentMap, type SubagentStateMap, type SubagentToolCall, type SubgraphByNodeMap, SubgraphDiscovery, type SubgraphDiscoverySnapshot, type SubgraphMap, type SubmissionQueueEntry, type SubmissionQueueSnapshot, type Target, type ToolCallFromTool, type ToolCallStatus, type TransportAdapter, type UseStreamCommonOptions, type UseStreamOptions, type VideoMedia, type WidenUpdateMessages, assembledMessageToBaseMessage, assembledToBaseMessage, audioProjection, channelProjection, extensionProjection, filesProjection, imagesProjection, messagesProjection, parseToolOutput, parseToolPayload, toolCallsProjection, valuesProjection, videoProjection };
|
package/dist/stream/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseToolOutput, parseToolPayload } from "../client/stream/handles/tools.js";
|
|
1
2
|
import { MediaAssembler, MediaAssemblyError } from "../client/stream/media.js";
|
|
2
3
|
import { NAMESPACE_SEPARATOR } from "./constants.js";
|
|
3
4
|
import { StreamStore } from "./store.js";
|
|
@@ -13,4 +14,4 @@ import { extensionProjection } from "./projections/extension.js";
|
|
|
13
14
|
import { channelProjection } from "./projections/channel.js";
|
|
14
15
|
import { audioProjection, filesProjection, imagesProjection, videoProjection } from "./projections/media.js";
|
|
15
16
|
import "./projections/index.js";
|
|
16
|
-
export { ChannelRegistry, MediaAssembler, MediaAssemblyError, NAMESPACE_SEPARATOR, ROOT_PUMP_CHANNELS, StreamController, StreamStore, SubagentDiscovery, SubgraphDiscovery, assembledMessageToBaseMessage, assembledToBaseMessage, audioProjection, channelProjection, extensionProjection, filesProjection, imagesProjection, messagesProjection, toolCallsProjection, valuesProjection, videoProjection };
|
|
17
|
+
export { ChannelRegistry, MediaAssembler, MediaAssemblyError, NAMESPACE_SEPARATOR, ROOT_PUMP_CHANNELS, StreamController, StreamStore, SubagentDiscovery, SubgraphDiscovery, assembledMessageToBaseMessage, assembledToBaseMessage, audioProjection, channelProjection, extensionProjection, filesProjection, imagesProjection, messagesProjection, parseToolOutput, parseToolPayload, toolCallsProjection, valuesProjection, videoProjection };
|
|
@@ -65,7 +65,7 @@ var LifecycleLoadingTracker = class {
|
|
|
65
65
|
});
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
|
-
if (lifecycle?.event === "completed" || lifecycle?.event === "failed" || lifecycle?.event === "interrupted"
|
|
68
|
+
if (lifecycle?.event === "completed" || lifecycle?.event === "failed" || lifecycle?.event === "interrupted") {
|
|
69
69
|
if (seq != null) this.#lastTerminalLifecycleSeq = Math.max(this.#lastTerminalLifecycleSeq, seq);
|
|
70
70
|
setTimeout(() => {
|
|
71
71
|
if (this.#isDisposed()) return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle-loading-tracker.cjs","names":["#store","#isDisposed","#lastTerminalLifecycleSeq","isRootNamespace"],"sources":["../../src/stream/lifecycle-loading-tracker.ts"],"sourcesContent":["/**\n * Drives the {@link RootSnapshot.isLoading} flag from root lifecycle\n * events.\n *\n * # What it does\n *\n * The tracker watches a stream of protocol events and flips the\n * `isLoading` slot of a {@link StreamStore} based on root-namespace\n * `lifecycle` payloads:\n *\n * - `running` → `isLoading = true`\n * - `completed` / `failed` / `interrupted
|
|
1
|
+
{"version":3,"file":"lifecycle-loading-tracker.cjs","names":["#store","#isDisposed","#lastTerminalLifecycleSeq","isRootNamespace"],"sources":["../../src/stream/lifecycle-loading-tracker.ts"],"sourcesContent":["/**\n * Drives the {@link RootSnapshot.isLoading} flag from root lifecycle\n * events.\n *\n * # What it does\n *\n * The tracker watches a stream of protocol events and flips the\n * `isLoading` slot of a {@link StreamStore} based on root-namespace\n * `lifecycle` payloads:\n *\n * - `running` → `isLoading = true`\n * - `completed` / `failed` / `interrupted`\n * → `isLoading = false`\n *\n * Non-root, non-lifecycle, and unknown events are ignored.\n *\n * # Why it lives in its own class\n *\n * Lifecycle handling has two subtleties that we want to keep out of\n * the {@link StreamController}'s critical path:\n *\n * 1. **Stale `running` filtering.** SSE replays older events on\n * reconnect — including a `running` lifecycle that fired before\n * the run terminated. Without filtering, that replay would flip\n * `isLoading` back to `true` after a `completed` already brought\n * it down. We track the highest terminal `seq` we've seen and\n * drop any `running` whose `seq` is at or below it.\n * 2. **Deferred terminal flip.** The flip from `true → false` is\n * pushed to the next macrotask (`setTimeout(..., 0)`). This\n * gives synchronous consumers — most notably `for await`\n * iterators in framework bindings — one event-loop tick to\n * observe terminal-related state (e.g. the final assistant\n * message landing in `values`) before `isLoading` settles.\n *\n * # Why it's safe to register the listener as `controller.onEvent`\n *\n * The tracker subscribes to the controller's root event bus via the\n * exported {@link listener} arrow. Because the listener is bound at\n * construction time, removing it later (`bus.delete(tracker.listener)`)\n * works without `bind()` gymnastics in the controller.\n *\n * @typeParam T - The snapshot shape; must contain an `isLoading` flag.\n */\nimport type { Event, LifecycleEvent } from \"@langchain/protocol\";\nimport { StreamStore } from \"./store.js\";\nimport { isRootNamespace } from \"./namespace.js\";\n\n/**\n * Minimal contract the snapshot must satisfy. The tracker only\n * touches `isLoading`, leaving everything else for the controller.\n */\ntype LoadingSnapshot = { readonly isLoading: boolean };\n\n/**\n * Drives root-snapshot `isLoading` from root lifecycle events.\n */\nexport class LifecycleLoadingTracker<T extends LoadingSnapshot> {\n /** Snapshot store whose `isLoading` slot we manage. */\n readonly #store: StreamStore<T>;\n\n /**\n * Disposal probe. Consulted from the deferred `setTimeout` so a\n * controller torn down between scheduling and firing doesn't end\n * up writing to a defunct store.\n */\n readonly #isDisposed: () => boolean;\n\n /**\n * Highest sequence number of a terminal lifecycle we've observed.\n * `running` events at or below this seq are stale replays and\n * are dropped to avoid flipping the loading flag back on after the\n * run has already ended.\n */\n #lastTerminalLifecycleSeq = -1;\n\n /**\n * @param params.store - Store whose `isLoading` slot we drive.\n * @param params.isDisposed - Disposal probe consulted from deferred callbacks.\n */\n constructor(params: { store: StreamStore<T>; isDisposed: () => boolean }) {\n this.#store = params.store;\n this.#isDisposed = params.isDisposed;\n }\n\n /**\n * Bound listener suitable for `EventBus.subscribe`. Re-exposed as a\n * stable property so the controller can later remove the same\n * function reference from the bus on teardown.\n */\n readonly listener = (event: Event): void => {\n this.handle(event);\n };\n\n /**\n * Reset internal state when rebinding to a new thread.\n *\n * The terminal-seq guard is per-thread: a new thread's `running`\n * events are not stale relative to the old thread's terminals.\n */\n reset(): void {\n this.#lastTerminalLifecycleSeq = -1;\n }\n\n /**\n * Process a single protocol event.\n *\n * Filters down to root-namespace lifecycle events, then mutates the\n * store's `isLoading` slot. All other events are ignored.\n *\n * @param event - Any protocol event from the controller's root bus.\n */\n handle(event: Event): void {\n if (event.method !== \"lifecycle\") return;\n if (!isRootNamespace(event.params.namespace)) return;\n const lifecycle = (event as LifecycleEvent).params.data as {\n event?: string;\n };\n const seq = typeof event.seq === \"number\" ? event.seq : undefined;\n if (lifecycle?.event === \"running\") {\n // Drop stale `running` replays that arrive *after* a terminal\n // for the same run. SSE re-streams history on reconnect; without\n // this filter the loading flag would oscillate.\n if (seq != null && seq <= this.#lastTerminalLifecycleSeq) {\n return;\n }\n this.#store.setState((s) =>\n s.isLoading ? s : { ...s, isLoading: true }\n );\n return;\n }\n if (\n lifecycle?.event === \"completed\" ||\n lifecycle?.event === \"failed\" ||\n lifecycle?.event === \"interrupted\"\n ) {\n if (seq != null) {\n this.#lastTerminalLifecycleSeq = Math.max(\n this.#lastTerminalLifecycleSeq,\n seq\n );\n }\n // Flip `isLoading=false` on the next macrotask so synchronous\n // consumers iterating events get one tick to observe terminal\n // state (the final values snapshot etc.) before the loading\n // indicator drops.\n setTimeout(() => {\n if (this.#isDisposed()) return;\n this.#store.setState((s) =>\n s.isLoading ? { ...s, isLoading: false } : s\n );\n }, 0);\n }\n }\n}\n"],"mappings":";;;;;AAwDA,IAAa,0BAAb,MAAgE;;CAE9D;;;;;;CAOA;;;;;;;CAQA,4BAA4B;;;;;CAM5B,YAAY,QAA8D;AACxE,QAAA,QAAc,OAAO;AACrB,QAAA,aAAmB,OAAO;;;;;;;CAQ5B,YAAqB,UAAuB;AAC1C,OAAK,OAAO,MAAM;;;;;;;;CASpB,QAAc;AACZ,QAAA,2BAAiC;;;;;;;;;;CAWnC,OAAO,OAAoB;AACzB,MAAI,MAAM,WAAW,YAAa;AAClC,MAAI,CAACG,kBAAAA,gBAAgB,MAAM,OAAO,UAAU,CAAE;EAC9C,MAAM,YAAa,MAAyB,OAAO;EAGnD,MAAM,MAAM,OAAO,MAAM,QAAQ,WAAW,MAAM,MAAM,KAAA;AACxD,MAAI,WAAW,UAAU,WAAW;AAIlC,OAAI,OAAO,QAAQ,OAAO,MAAA,yBACxB;AAEF,SAAA,MAAY,UAAU,MACpB,EAAE,YAAY,IAAI;IAAE,GAAG;IAAG,WAAW;IAAM,CAC5C;AACD;;AAEF,MACE,WAAW,UAAU,eACrB,WAAW,UAAU,YACrB,WAAW,UAAU,eACrB;AACA,OAAI,OAAO,KACT,OAAA,2BAAiC,KAAK,IACpC,MAAA,0BACA,IACD;AAMH,oBAAiB;AACf,QAAI,MAAA,YAAkB,CAAE;AACxB,UAAA,MAAY,UAAU,MACpB,EAAE,YAAY;KAAE,GAAG;KAAG,WAAW;KAAO,GAAG,EAC5C;MACA,EAAE"}
|
|
@@ -65,7 +65,7 @@ var LifecycleLoadingTracker = class {
|
|
|
65
65
|
});
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
|
-
if (lifecycle?.event === "completed" || lifecycle?.event === "failed" || lifecycle?.event === "interrupted"
|
|
68
|
+
if (lifecycle?.event === "completed" || lifecycle?.event === "failed" || lifecycle?.event === "interrupted") {
|
|
69
69
|
if (seq != null) this.#lastTerminalLifecycleSeq = Math.max(this.#lastTerminalLifecycleSeq, seq);
|
|
70
70
|
setTimeout(() => {
|
|
71
71
|
if (this.#isDisposed()) return;
|