@langchain/langgraph-sdk 1.9.20 → 1.9.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dist/client/base.cjs +4 -1
  2. package/dist/client/base.cjs.map +1 -1
  3. package/dist/client/base.d.cts +2 -0
  4. package/dist/client/base.d.cts.map +1 -1
  5. package/dist/client/base.d.ts +2 -0
  6. package/dist/client/base.d.ts.map +1 -1
  7. package/dist/client/base.js +5 -2
  8. package/dist/client/base.js.map +1 -1
  9. package/dist/client/index.cjs +1 -1
  10. package/dist/client/index.js +1 -1
  11. package/dist/client/runs/index.cjs +2 -0
  12. package/dist/client/runs/index.cjs.map +1 -1
  13. package/dist/client/runs/index.d.cts +15 -0
  14. package/dist/client/runs/index.d.cts.map +1 -1
  15. package/dist/client/runs/index.d.ts +15 -0
  16. package/dist/client/runs/index.d.ts.map +1 -1
  17. package/dist/client/runs/index.js +2 -0
  18. package/dist/client/runs/index.js.map +1 -1
  19. package/dist/client/stream/error.cjs +21 -0
  20. package/dist/client/stream/error.cjs.map +1 -1
  21. package/dist/client/stream/error.js +21 -1
  22. package/dist/client/stream/error.js.map +1 -1
  23. package/dist/client/stream/index.cjs +24 -1
  24. package/dist/client/stream/index.cjs.map +1 -1
  25. package/dist/client/stream/index.d.cts.map +1 -1
  26. package/dist/client/stream/index.d.ts.map +1 -1
  27. package/dist/client/stream/index.js +24 -1
  28. package/dist/client/stream/index.js.map +1 -1
  29. package/dist/client/stream/transport/agent-server.cjs +8 -2
  30. package/dist/client/stream/transport/agent-server.cjs.map +1 -1
  31. package/dist/client/stream/transport/agent-server.d.cts +17 -2
  32. package/dist/client/stream/transport/agent-server.d.cts.map +1 -1
  33. package/dist/client/stream/transport/agent-server.d.ts +17 -2
  34. package/dist/client/stream/transport/agent-server.d.ts.map +1 -1
  35. package/dist/client/stream/transport/agent-server.js +8 -2
  36. package/dist/client/stream/transport/agent-server.js.map +1 -1
  37. package/dist/client/stream/transport/http.cjs +81 -21
  38. package/dist/client/stream/transport/http.cjs.map +1 -1
  39. package/dist/client/stream/transport/http.d.cts +22 -7
  40. package/dist/client/stream/transport/http.d.cts.map +1 -1
  41. package/dist/client/stream/transport/http.d.ts +22 -7
  42. package/dist/client/stream/transport/http.d.ts.map +1 -1
  43. package/dist/client/stream/transport/http.js +83 -23
  44. package/dist/client/stream/transport/http.js.map +1 -1
  45. package/dist/client/stream/transport/index.cjs +2 -1
  46. package/dist/client/stream/transport/index.js +2 -1
  47. package/dist/client/stream/transport/types.d.cts +85 -5
  48. package/dist/client/stream/transport/types.d.cts.map +1 -1
  49. package/dist/client/stream/transport/types.d.ts +85 -5
  50. package/dist/client/stream/transport/types.d.ts.map +1 -1
  51. package/dist/client/stream/transport/utils.cjs +19 -0
  52. package/dist/client/stream/transport/utils.cjs.map +1 -1
  53. package/dist/client/stream/transport/utils.js +19 -1
  54. package/dist/client/stream/transport/utils.js.map +1 -1
  55. package/dist/client/stream/transport/websocket.cjs +125 -21
  56. package/dist/client/stream/transport/websocket.cjs.map +1 -1
  57. package/dist/client/stream/transport/websocket.d.cts +32 -4
  58. package/dist/client/stream/transport/websocket.d.cts.map +1 -1
  59. package/dist/client/stream/transport/websocket.d.ts +32 -4
  60. package/dist/client/stream/transport/websocket.d.ts.map +1 -1
  61. package/dist/client/stream/transport/websocket.js +126 -23
  62. package/dist/client/stream/transport/websocket.js.map +1 -1
  63. package/dist/client/stream/transport.d.cts +20 -3
  64. package/dist/client/stream/transport.d.cts.map +1 -1
  65. package/dist/client/stream/transport.d.ts +20 -3
  66. package/dist/client/stream/transport.d.ts.map +1 -1
  67. package/dist/client/stream/types.d.cts +31 -0
  68. package/dist/client/stream/types.d.cts.map +1 -1
  69. package/dist/client/stream/types.d.ts +31 -0
  70. package/dist/client/stream/types.d.ts.map +1 -1
  71. package/dist/client/threads/index.cjs +36 -17
  72. package/dist/client/threads/index.cjs.map +1 -1
  73. package/dist/client/threads/index.js +35 -16
  74. package/dist/client/threads/index.js.map +1 -1
  75. package/dist/client.cjs +1 -1
  76. package/dist/client.js +1 -1
  77. package/dist/index.cjs +1 -1
  78. package/dist/index.js +1 -1
  79. package/dist/react-ui/server/server.cjs +2 -2
  80. package/dist/react-ui/server/server.cjs.map +1 -1
  81. package/dist/react-ui/server/server.js +1 -1
  82. package/dist/react-ui/server/server.js.map +1 -1
  83. package/dist/stream/controller.cjs +21 -3
  84. package/dist/stream/controller.cjs.map +1 -1
  85. package/dist/stream/controller.d.cts.map +1 -1
  86. package/dist/stream/controller.d.ts.map +1 -1
  87. package/dist/stream/controller.js +20 -2
  88. package/dist/stream/controller.js.map +1 -1
  89. package/dist/stream/index.cjs +2 -0
  90. package/dist/stream/index.d.cts +2 -1
  91. package/dist/stream/index.d.ts +2 -1
  92. package/dist/stream/index.js +2 -1
  93. package/dist/stream/projections/channel-effect.cjs +52 -0
  94. package/dist/stream/projections/channel-effect.cjs.map +1 -0
  95. package/dist/stream/projections/channel-effect.d.cts +35 -0
  96. package/dist/stream/projections/channel-effect.d.cts.map +1 -0
  97. package/dist/stream/projections/channel-effect.d.ts +35 -0
  98. package/dist/stream/projections/channel-effect.d.ts.map +1 -0
  99. package/dist/stream/projections/channel-effect.js +52 -0
  100. package/dist/stream/projections/channel-effect.js.map +1 -0
  101. package/dist/stream/projections/index.cjs +1 -0
  102. package/dist/stream/projections/index.d.ts +1 -0
  103. package/dist/stream/projections/index.js +1 -0
  104. package/dist/stream/root-message-projection.cjs +55 -0
  105. package/dist/stream/root-message-projection.cjs.map +1 -1
  106. package/dist/stream/root-message-projection.js +55 -0
  107. package/dist/stream/root-message-projection.js.map +1 -1
  108. package/dist/stream/submit-coordinator.cjs +3 -3
  109. package/dist/stream/submit-coordinator.cjs.map +1 -1
  110. package/dist/stream/submit-coordinator.js +1 -1
  111. package/dist/stream/submit-coordinator.js.map +1 -1
  112. package/dist/types.d.cts +20 -0
  113. package/dist/types.d.cts.map +1 -1
  114. package/dist/types.d.ts +20 -0
  115. package/dist/types.d.ts.map +1 -1
  116. package/dist/ui/branching.d.cts +1 -1
  117. package/dist/ui/branching.d.ts +1 -1
  118. package/dist/ui/orchestrator.d.cts +1 -1
  119. package/dist/ui/orchestrator.d.cts.map +1 -1
  120. package/dist/ui/orchestrator.d.ts +1 -1
  121. package/dist/ui/orchestrator.d.ts.map +1 -1
  122. package/dist/utils/index.d.cts +1 -1
  123. package/dist/utils/index.d.ts +1 -1
  124. package/dist/utils/stream.cjs +94 -0
  125. package/dist/utils/stream.cjs.map +1 -1
  126. package/dist/utils/stream.d.cts +16 -2
  127. package/dist/utils/stream.d.cts.map +1 -1
  128. package/dist/utils/stream.d.ts +16 -2
  129. package/dist/utils/stream.d.ts.map +1 -1
  130. package/dist/utils/stream.js +94 -1
  131. package/dist/utils/stream.js.map +1 -1
  132. package/package.json +6 -5
@@ -80,6 +80,37 @@ var RootMessageProjection = class {
80
80
  */
81
81
  #maxStep = void 0;
82
82
  /**
83
+ * Message ids seeded as complete-and-final from an idle thread's
84
+ * `getState()` snapshot. An idle thread defers its root SSE pump, and
85
+ * the first `submit()` brings it up — at which point the transport
86
+ * replays the finished run from `seq=0`. Unlike the `values` channel
87
+ * (guarded by {@link #maxStep}), `messages`-channel deltas carry no
88
+ * step, so that replay would otherwise rebuild each already-complete
89
+ * message from an empty `message-start` and re-stream the whole turn
90
+ * token-by-token, clobbering the seeded tail (a visible "messages
91
+ * replay" on the first submit). Deltas for a sealed id are dropped in
92
+ * {@link handleMessage}. The seal is lifted once a checkpoint advances
93
+ * strictly past {@link #sealStep} (see {@link applyValues}) or on
94
+ * thread rebind ({@link reset}). New ids from the next run are never
95
+ * sealed, so they stream normally.
96
+ */
97
+ #sealedMessageIds = /* @__PURE__ */ new Set();
98
+ /**
99
+ * High-water {@link #maxStep} captured when {@link sealMessageIds} ran,
100
+ * i.e. the seed checkpoint's step (or `undefined` when `getState()`
101
+ * carried no `metadata.step`). It is the boundary between the replayed
102
+ * idle history (steps `<= #sealStep`, emitted by the deferred pump's
103
+ * `seq=0` replay) and the new run (steps `> #sealStep`); only a
104
+ * checkpoint strictly past it lifts the seal. Without this boundary the
105
+ * replayed old-run checkpoints — which themselves carry increasing
106
+ * steps — would advance {@link #maxStep} and lift the seal mid-replay,
107
+ * reopening the clobber. When the seed step is unknown the boundary
108
+ * stays `undefined` and the seal holds until {@link reset}; the
109
+ * `values` channel (which ignores the seal) still reconciles any
110
+ * genuine change to a sealed id, only its streamed deltas are dropped.
111
+ */
112
+ #sealStep = void 0;
113
+ /**
83
114
  * @param params.messagesKey - Key inside `values` that holds the
84
115
  * message array.
85
116
  * @param params.store - Root snapshot store to mutate.
@@ -102,6 +133,28 @@ var RootMessageProjection = class {
102
133
  this.#pendingValues = null;
103
134
  this.#flushScheduled = false;
104
135
  this.#maxStep = void 0;
136
+ this.#sealedMessageIds.clear();
137
+ this.#sealStep = void 0;
138
+ }
139
+ /**
140
+ * Seal message ids so the streamed `messages` channel cannot downgrade
141
+ * them to partial re-streams. Called by {@link StreamController.hydrate}
142
+ * after seeding an idle thread, whose deferred pump replays the finished
143
+ * run from `seq=0` on the first submit.
144
+ *
145
+ * Captures the current {@link #maxStep} as the lift boundary
146
+ * ({@link #sealStep}). The seal is applied immediately after the seed's
147
+ * `getState()` snapshot is reconciled, so `#maxStep` here is the seed
148
+ * step (or `undefined` when `getState()` carried no `metadata.step`).
149
+ * The seal is lifted once a checkpoint advances strictly past that
150
+ * boundary (see {@link applyValues}) or on thread rebind
151
+ * ({@link reset}).
152
+ *
153
+ * @param ids - Complete message ids from the idle `getState()` seed.
154
+ */
155
+ sealMessageIds(ids) {
156
+ for (const id of ids) this.#sealedMessageIds.add(id);
157
+ if (this.#sealStep == null) this.#sealStep = this.#maxStep;
105
158
  }
106
159
  /**
107
160
  * Record a `namespace → tool_call_id` mapping captured from a root
@@ -151,6 +204,7 @@ var RootMessageProjection = class {
151
204
  if (update == null) return;
152
205
  const id = update.message.id;
153
206
  if (id == null) return;
207
+ if (this.#sealedMessageIds.has(id)) return;
154
208
  const captured = this.#roles.get(id) ?? { role: "ai" };
155
209
  const base = assembledMessageToBaseMessage(update.message, captured.role, { toolCallId: captured.toolCallId });
156
210
  const baselineMessages = this.#pendingMessages ?? this.#store.getSnapshot().messages;
@@ -195,6 +249,7 @@ var RootMessageProjection = class {
195
249
  const step = opts?.step;
196
250
  const addOnly = step != null && this.#maxStep != null && step < this.#maxStep;
197
251
  if (step != null && (this.#maxStep == null || step > this.#maxStep)) this.#maxStep = step;
252
+ if (this.#sealedMessageIds.size > 0 && step != null && this.#sealStep != null && step > this.#sealStep) this.#sealedMessageIds.clear();
198
253
  if (nextMessages.length === 0) {
199
254
  if (stateValuesShallowEqual(baselineValues, nextValues, this.#messagesKey)) return;
200
255
  this.#pendingValues = syncMessagesIntoValues(nextValues, this.#messagesKey, baselineMessages);
@@ -1 +1 @@
1
- {"version":3,"file":"root-message-projection.js","names":["#messagesKey","#store","#roles","#indexById","#toolCallIdByNamespace","#assembler","#valuesMessageIds","#pendingMessages","#pendingValues","#flushScheduled","#maxStep","#scheduleFlush","#flushPending"],"sources":["../../src/stream/root-message-projection.ts"],"sourcesContent":["/**\n * Root-namespace message projection.\n *\n * # What this module is\n *\n * The {@link RootMessageProjection} is the piece of the\n * {@link StreamController} that owns \"what messages does the root\n * namespace currently contain?\". It assembles streamed message deltas\n * via {@link MessageAssembler}, reconciles them against authoritative\n * `values.messages` snapshots from the server, and writes the merged\n * list back into the controller's root snapshot store.\n *\n * # Two streams of truth\n *\n * Root messages arrive on two channels and need to merge cleanly:\n *\n * - **`messages` channel.** Token-level deltas that build messages\n * incrementally. The {@link MessageAssembler} keeps partial\n * messages by id and emits an updated `BaseMessage` per delta.\n * - **`values` channel.** Periodic full-state snapshots that include\n * the authoritative messages array. Used for ordering, removals,\n * and forks (where the streamed messages may pre-date the new\n * timeline).\n *\n * The reconciliation rules (delegated to\n * {@link reconcileMessagesFromValues}) preserve in-flight streamed\n * content while letting values dictate ordering and removals.\n *\n * # Tool-message namespace correlation\n *\n * Tool messages arrive on `messages-start` events with `role: \"tool\"`\n * but the start event doesn't always include a `tool_call_id`. We\n * recover it via three fallbacks:\n *\n * 1. The start event itself, when the server includes it.\n * 2. The legacy `<id>-tool-<call_id>` message id format.\n * 3. The most recent `tool-started` event recorded under the same\n * namespace via {@link recordToolCallNamespace}.\n *\n * Without this correlation, tool messages render with empty\n * `tool_call_id` and downstream UIs can't pair them with the\n * originating tool call.\n *\n * # Store-write batching\n *\n * Every {@link handleMessage} / {@link applyValues} call updates the\n * in-projection bookkeeping (assembler state, id index, role cache)\n * synchronously, then stages the new `messages` / `values` into a\n * pending buffer and schedules a `setTimeout(0)` flush. A single\n * coalesced `store.setState` runs at the next macrotask boundary.\n *\n * The motivation is the long-replay freeze: a thread with hundreds\n * of messages replays through the `messages` channel on refresh or\n * mid-run join. Those events drain through the controller's\n * `for await` pump as a long microtask chain. Per-event\n * `store.setState` notifies `useSyncExternalStore` per event, and\n * after enough notifications React's `nestedUpdateCount` guard trips\n * with \"Maximum update depth exceeded\", permanently freezing the UI\n * on the first few messages. Coalescing to one notification per\n * macrotask lets React's scheduler commit between flushes.\n *\n * # Lifecycle\n *\n * - `handleMessage(event)` — apply a `messages` event delta.\n * - `applyValues(values, msgs)` — merge a `values` snapshot.\n * - `recordToolCallNamespace(ns, id)` — capture `namespace → tool_call_id`\n * so subsequent tool message starts can recover the id.\n * - `reset()` — clear all state on thread rebind.\n */\nimport type {\n MessagesEvent,\n MessageRole,\n MessageStartData,\n} from \"@langchain/protocol\";\nimport type { BaseMessage } from \"@langchain/core/messages\";\nimport { MessageAssembler } from \"../client/stream/messages.js\";\nimport {\n assembledMessageToBaseMessage,\n type ExtendedMessageRole,\n} from \"./assembled-to-message.js\";\nimport type { StreamStore } from \"./store.js\";\nimport type { RootSnapshot } from \"./types.js\";\nimport { namespaceKey } from \"./namespace.js\";\nimport {\n buildMessageIndex,\n messagesEqual,\n reconcileMessagesFromValues,\n shouldPreferValuesMessageForToolCalls,\n} from \"./message-reconciliation.js\";\n\n/**\n * Root-namespace message projection. Owns the merge between the\n * `messages` (streamed deltas) and `values` (authoritative\n * snapshots) channels for the root namespace.\n *\n * @typeParam StateType - Root state shape; the messages array is read\n * from `values[messagesKey]`.\n * @typeParam InterruptType - Shape of root protocol interrupts (forwarded\n * into `RootSnapshot` updates).\n */\nexport class RootMessageProjection<\n StateType extends object,\n InterruptType = unknown,\n> {\n /**\n * Key inside `values` that holds the message array. Defaults to\n * `\"messages\"` in the controller; configurable for state graphs\n * that surface messages under a different slot.\n */\n readonly #messagesKey: string;\n\n /** Root snapshot store written to on every merge. */\n readonly #store: StreamStore<RootSnapshot<StateType, InterruptType>>;\n\n /**\n * Stateful chunk assembler for in-flight messages. Reset (via a\n * fresh instance) on every {@link reset} so a new thread starts\n * with no half-built messages from the previous one.\n */\n #assembler = new MessageAssembler();\n\n /**\n * `messageId → role/toolCallId` captured from `message-start` events.\n * The assembler's intermediate output drops these fields, so we cache\n * them at start-time and reapply when projecting to a `BaseMessage`.\n */\n readonly #roles = new Map<\n string,\n { role: ExtendedMessageRole; toolCallId?: string }\n >();\n\n /**\n * `messageId → position in #store.messages` for fast in-place\n * updates as deltas arrive. Rebuilt on every full reconciliation\n * driven by a `values` event.\n */\n readonly #indexById = new Map<string, number>();\n\n /**\n * Ids observed in the most recent `values.messages` snapshot.\n * Reconciliation uses this to detect server-side removals: a\n * previously-seen id missing from the next snapshot means it was\n * removed by the server (and should drop from the projection).\n */\n #valuesMessageIds = new Set<string>();\n\n /**\n * `namespaceKey → tool_call_id` captured from root `tool-started`\n * events. Used as a fallback when a tool-role `message-start` is\n * missing its `tool_call_id` field.\n */\n readonly #toolCallIdByNamespace = new Map<string, string>();\n\n /**\n * Coalescing buffer for store writes. {@link handleMessage} and\n * {@link applyValues} stage their computed `messages` / `values`\n * here instead of calling `store.setState` per event. A single\n * `setTimeout(0)` flush commits them in one `setState`, so a\n * burst of SSE events draining as a microtask chain becomes one\n * store notification at the next macrotask boundary.\n *\n * `null` means \"no staged write\" — once a flush settles, the\n * slots are cleared so the next call starts from the latest\n * committed store snapshot.\n */\n #pendingMessages: BaseMessage[] | null = null;\n #pendingValues: StateType | null = null;\n #flushScheduled = false;\n\n /**\n * Highest checkpoint `step` whose `values` snapshot has been applied.\n * Seeded by {@link StreamController.hydrate} from `getState()` and\n * advanced by live `values` events. A snapshot arriving with a lower\n * step is an older checkpoint replayed by the content pump on\n * reconnect; it is reconciled in add-only mode so it cannot remove\n * the seeded message tail (the final assistant turn). `undefined`\n * until the first step-bearing snapshot, where the legacy\n * remove-on-absence behavior is preserved.\n */\n #maxStep: number | undefined = undefined;\n\n /**\n * @param params.messagesKey - Key inside `values` that holds the\n * message array.\n * @param params.store - Root snapshot store to mutate.\n */\n constructor(params: {\n messagesKey: string;\n store: StreamStore<RootSnapshot<StateType, InterruptType>>;\n }) {\n this.#messagesKey = params.messagesKey;\n this.#store = params.store;\n }\n\n /**\n * Drop all per-thread state. Called by the controller on thread\n * rebind / dispose so a swap doesn't surface stale messages.\n */\n reset(): void {\n this.#assembler = new MessageAssembler();\n this.#roles.clear();\n this.#indexById.clear();\n this.#valuesMessageIds = new Set();\n this.#toolCallIdByNamespace.clear();\n // Drop any unflushed pending writes — they were computed against\n // the previous thread's baseline and committing them after a\n // rebind would bleed stale messages into the new thread.\n this.#pendingMessages = null;\n this.#pendingValues = null;\n this.#flushScheduled = false;\n this.#maxStep = undefined;\n }\n\n /**\n * Record a `namespace → tool_call_id` mapping captured from a root\n * `tool-started` event.\n *\n * The companion tool-role `message-start` event may not carry a\n * `tool_call_id`, so we fall back to the most recent value recorded\n * here for the same namespace.\n *\n * @param namespace - Event namespace from the `tool-started` event.\n * @param toolCallId - Tool call id from the same event.\n */\n recordToolCallNamespace(\n namespace: readonly string[],\n toolCallId: string\n ): void {\n this.#toolCallIdByNamespace.set(namespaceKey(namespace), toolCallId);\n }\n\n /**\n * Apply a `messages` channel event to the projection.\n *\n * Captures role/tool metadata on `message-start`, feeds the chunk\n * to the assembler, projects the assembled output to a\n * {@link BaseMessage}, and either appends or in-place updates the\n * pending messages buffer based on whether the id was seen before.\n *\n * @param event - The `messages` channel event to consume.\n */\n handleMessage(event: MessagesEvent): void {\n const data = event.params.data;\n if (data.event === \"message-start\") {\n const startData = data as MessageStartData;\n const role = (startData.role ?? \"ai\") as MessageRole;\n const extendedRole =\n (startData as { role?: ExtendedMessageRole }).role ?? role;\n let toolCallId = (startData as { tool_call_id?: string }).tool_call_id;\n // Tool messages need a tool_call_id to render. Fall back through:\n // 1. legacy `<id>-tool-<call_id>` message id format\n // 2. namespace-recorded tool_call_id (from #recordToolCallNamespace)\n if (extendedRole === \"tool\" && toolCallId == null) {\n const messageId = startData.id;\n if (messageId != null) {\n const match = /-tool-(.+)$/.exec(messageId);\n if (match != null) toolCallId = match[1];\n }\n if (toolCallId == null) {\n toolCallId = this.#toolCallIdByNamespace.get(\n namespaceKey(event.params.namespace)\n );\n }\n }\n if (startData.id != null) {\n this.#roles.set(startData.id, {\n role: extendedRole,\n toolCallId,\n });\n }\n }\n\n const update = this.#assembler.consume(event);\n if (update == null) return;\n const id = update.message.id;\n if (id == null) return;\n const captured = this.#roles.get(id) ?? { role: \"ai\" as const };\n const base = assembledMessageToBaseMessage(update.message, captured.role, {\n toolCallId: captured.toolCallId,\n });\n\n // Compute against the pending baseline if we have one (so an\n // earlier handleMessage in the same tick is the input to this\n // one), else against the latest committed store snapshot.\n // `#indexById` is the synchronous source of truth for \"where is\n // each id in the current messages list\" — every code path below\n // keeps it in sync before returning.\n const baselineMessages =\n this.#pendingMessages ?? this.#store.getSnapshot().messages;\n const existingIdx = this.#indexById.get(id);\n let messages: BaseMessage[];\n if (existingIdx == null) {\n this.#indexById.set(id, baselineMessages.length);\n messages = [...baselineMessages, base];\n } else if (messagesEqual(baselineMessages[existingIdx], base)) {\n // Identical re-emission — skip the store write to keep\n // snapshot identity stable.\n return;\n } else {\n messages = baselineMessages.slice();\n messages[existingIdx] = base;\n }\n\n // Mirror the new messages list into `values[messagesKey]` so\n // direct `values` reads (used by some hooks and by the eventual\n // `values` reconciliation) stay in sync.\n const baselineValues =\n this.#pendingValues ?? this.#store.getSnapshot().values;\n const values = syncMessagesIntoValues(\n baselineValues,\n this.#messagesKey,\n messages\n );\n this.#pendingMessages = messages;\n if (values !== baselineValues) this.#pendingValues = values;\n this.#scheduleFlush();\n }\n\n /**\n * Reconcile a full `values` snapshot into the projection.\n *\n * Delegates the merge to {@link reconcileMessagesFromValues}:\n * values stays authoritative for ordering and removals, while\n * streamed in-flight messages keep their content until the server\n * echoes them back. Empty messages just refresh the values blob.\n *\n * Rebuilds {@link #indexById} after the merge so subsequent delta\n * applications target the new positions.\n *\n * @param nextValues - Full values snapshot from the `values` event.\n * @param nextMessages - The messages array extracted from\n * `values[messagesKey]` and coerced to `BaseMessage` instances.\n * @param opts.step - Checkpoint superstep for this snapshot, when\n * known. A snapshot whose step is below the highest applied step is\n * treated as a stale reconnect replay and reconciled add-only.\n */\n applyValues(\n nextValues: StateType,\n nextMessages: BaseMessage[],\n opts?: { step?: number }\n ): void {\n const baselineSnapshot = this.#store.getSnapshot();\n const baselineMessages = this.#pendingMessages ?? baselineSnapshot.messages;\n const baselineValues = this.#pendingValues ?? baselineSnapshot.values;\n\n const step = opts?.step;\n // Stale only when we have both a prior high-water step and a lower\n // incoming step. A missing step preserves the legacy semantics.\n const addOnly =\n step != null && this.#maxStep != null && step < this.#maxStep;\n if (step != null && (this.#maxStep == null || step > this.#maxStep)) {\n this.#maxStep = step;\n }\n\n if (nextMessages.length === 0) {\n if (\n stateValuesShallowEqual(baselineValues, nextValues, this.#messagesKey)\n ) {\n return;\n }\n // Mirror the current `messages` list back into the values slot\n // so the staged snapshot stays consistent with the (separately\n // tracked) messages array.\n this.#pendingValues = syncMessagesIntoValues(\n nextValues,\n this.#messagesKey,\n baselineMessages\n );\n this.#scheduleFlush();\n return;\n }\n\n const reconciliation = reconcileMessagesFromValues({\n valueMessages: nextMessages,\n currentMessages: baselineMessages,\n currentIndexById: this.#indexById,\n previousValueMessageIds: this.#valuesMessageIds,\n preferValuesMessage: shouldPreferValuesMessageForToolCalls,\n addOnly,\n });\n // A stale replay snapshot must not shrink the authoritative id set:\n // keep the (larger) seeded set so a genuinely-newer removal is still\n // detected once the timeline advances past the seed.\n if (!addOnly) this.#valuesMessageIds = reconciliation.valueMessageIds;\n const messages = reconciliation.messages as BaseMessage[];\n const values = {\n ...(nextValues as Record<string, unknown>),\n [this.#messagesKey]: messages,\n } as StateType;\n if (\n messages === baselineMessages &&\n stateValuesShallowEqual(baselineValues, values, this.#messagesKey)\n ) {\n return;\n }\n\n // Reconciliation may reorder, drop, or substitute messages, so\n // rebuild the id → index map to match the new array.\n this.#indexById.clear();\n for (const [id, idx] of buildMessageIndex(messages)) {\n this.#indexById.set(id, idx);\n }\n this.#pendingMessages = messages;\n this.#pendingValues = values;\n this.#scheduleFlush();\n }\n\n /**\n * Append messages applied optimistically by a local `submit()`,\n * keyed by id so the eventual server echo reconciles cleanly.\n *\n * Unlike {@link applyValues}, the supplied messages are *not* treated\n * as an authoritative ordered snapshot: they are appended to the end\n * of the current projection (or replaced in place when the id already\n * exists), preserving prior history ordering. When the server later\n * emits a `values` snapshot containing the same ids,\n * {@link applyValues} → {@link reconcileMessagesFromValues} takes over\n * (server ordering wins, the echoed message replaces the optimistic\n * one).\n *\n * Non-message input keys are shallow-merged into `values` via\n * `extraValues`; they are dropped/overwritten automatically by the\n * first server `values` event (which rebuilds `values` from the\n * server snapshot), or rolled back via {@link restoreValueKeys} when\n * the run fails before any echo.\n *\n * @param messages - Optimistic messages (already coerced to\n * `BaseMessage` instances, each carrying a stable id).\n * @param extraValues - Non-message input keys to shallow-merge into\n * `values`.\n */\n appendOptimistic(\n messages: BaseMessage[],\n extraValues?: Record<string, unknown>\n ): void {\n let working = this.#pendingMessages ?? this.#store.getSnapshot().messages;\n let mutated = false;\n for (const message of messages) {\n const id = message.id;\n if (id == null) continue;\n const existingIdx = this.#indexById.get(id);\n if (existingIdx == null) {\n if (!mutated) {\n working = working.slice();\n mutated = true;\n }\n this.#indexById.set(id, working.length);\n working.push(message);\n } else if (!messagesEqual(working[existingIdx], message)) {\n if (!mutated) {\n working = working.slice();\n mutated = true;\n }\n working[existingIdx] = message;\n }\n }\n\n const baselineValues =\n this.#pendingValues ?? this.#store.getSnapshot().values;\n let values = baselineValues;\n if (extraValues != null && Object.keys(extraValues).length > 0) {\n values = { ...(baselineValues as object), ...extraValues } as StateType;\n }\n values = syncMessagesIntoValues(values, this.#messagesKey, working);\n if (!mutated && values === baselineValues) return;\n this.#pendingMessages = working;\n if (values !== baselineValues) this.#pendingValues = values;\n this.#scheduleFlush();\n }\n\n /**\n * Drop optimistic messages by id without disturbing the rest of the\n * projection. Used by {@link StreamController.hydrate} to remove\n * never-persisted optimistic messages (`pending` / `failed`) so a\n * reload converges to server truth.\n *\n * @param ids - Message ids to remove.\n */\n dropOptimisticMessages(ids: ReadonlySet<string>): void {\n if (ids.size === 0) return;\n const baselineMessages =\n this.#pendingMessages ?? this.#store.getSnapshot().messages;\n const next = baselineMessages.filter((m) => m.id == null || !ids.has(m.id));\n if (next.length === baselineMessages.length) return;\n this.#indexById.clear();\n for (const [id, idx] of buildMessageIndex(next)) {\n this.#indexById.set(id, idx);\n }\n const baselineValues =\n this.#pendingValues ?? this.#store.getSnapshot().values;\n this.#pendingMessages = next;\n this.#pendingValues = syncMessagesIntoValues(\n baselineValues,\n this.#messagesKey,\n next\n );\n this.#scheduleFlush();\n }\n\n /**\n * Restore (or delete) `values` keys that were optimistically merged\n * by {@link appendOptimistic} but never echoed by the server — i.e.\n * roll back non-message optimistic state when the run fails before\n * any `values` event lands. Messages are left untouched (kept on\n * failure per the optimistic contract).\n *\n * @param restore - Per-key pre-submit snapshot: when `hadKey` is\n * false the key is deleted, otherwise it is reset to `prevValue`.\n */\n restoreValueKeys(\n restore: ReadonlyArray<{\n key: string;\n hadKey: boolean;\n prevValue: unknown;\n }>\n ): void {\n if (restore.length === 0) return;\n const baselineValues =\n this.#pendingValues ?? this.#store.getSnapshot().values;\n const next = { ...(baselineValues as Record<string, unknown>) };\n let changed = false;\n for (const { key, hadKey, prevValue } of restore) {\n if (key === this.#messagesKey) continue;\n if (hadKey) {\n if (!Object.is(next[key], prevValue)) {\n next[key] = prevValue;\n changed = true;\n }\n } else if (Object.prototype.hasOwnProperty.call(next, key)) {\n delete next[key];\n changed = true;\n }\n }\n if (!changed) return;\n this.#pendingValues = next as StateType;\n this.#scheduleFlush();\n }\n\n /**\n * Schedule a coalesced flush on the next macrotask. Idempotent\n * within a tick — multiple `handleMessage` / `applyValues` calls\n * before the flush fires collapse into one store write.\n *\n * `setTimeout(0)` is a macrotask: it runs after the current\n * microtask chain drains, so a burst of SSE events processed by\n * the controller's `for await` pump becomes one `store.setState`\n * (and therefore one `useSyncExternalStore` notification).\n */\n #scheduleFlush = (): void => {\n if (this.#flushScheduled) return;\n this.#flushScheduled = true;\n setTimeout(this.#flushPending, 0);\n };\n\n /**\n * Drain `#pendingMessages` / `#pendingValues` to the store in a\n * single `setState` call.\n */\n #flushPending = (): void => {\n this.#flushScheduled = false;\n const messages = this.#pendingMessages;\n const values = this.#pendingValues;\n this.#pendingMessages = null;\n this.#pendingValues = null;\n if (messages == null && values == null) return;\n this.#store.setState((s) => {\n // Other rootStore mutators (controller-driven `isLoading`,\n // `interrupts`, `toolCalls`, etc.) do not touch `s.messages`\n // / `s.values`, so a last-write-wins commit on those two\n // fields is safe.\n if (messages == null) {\n return values == null ? s : { ...s, values };\n }\n if (values == null) return { ...s, messages };\n return { ...s, messages, values };\n });\n };\n}\n\n/**\n * Mirror a freshly-updated message list into `values[messagesKey]`.\n *\n * Returns the same `values` reference when the list is already\n * equal-by-content so the caller can keep the existing snapshot\n * identity (and avoid spurious `setSnapshot` notifications).\n */\nfunction syncMessagesIntoValues<StateType extends object>(\n values: StateType,\n messagesKey: string,\n messages: BaseMessage[]\n): StateType {\n const record = values as Record<string, unknown>;\n const current = record[messagesKey];\n if (Array.isArray(current) && messagesEqualList(current, messages)) {\n return values;\n }\n return {\n ...record,\n [messagesKey]: messages,\n } as StateType;\n}\n\n/**\n * True when two `BaseMessage` arrays carry the same per-message\n * content (using {@link messagesEqual}).\n */\nfunction messagesEqualList(\n previous: readonly BaseMessage[],\n next: readonly BaseMessage[]\n): boolean {\n if (previous === next) return true;\n if (previous.length !== next.length) return false;\n for (let i = 0; i < previous.length; i += 1) {\n if (!messagesEqual(previous[i], next[i])) return false;\n }\n return true;\n}\n\n/**\n * Shallow-equal for `values` objects, *ignoring* the messages slot.\n *\n * The messages array is compared separately by the caller (via\n * {@link messagesEqualList}) because both arrays contain class\n * instances whose JSON representation is not stable across reads.\n */\nfunction stateValuesShallowEqual(\n previous: object,\n next: object,\n messagesKey: string\n): boolean {\n if (previous === next) return true;\n const previousRecord = previous as Record<string, unknown>;\n const nextRecord = next as Record<string, unknown>;\n const previousKeys = Object.keys(previousRecord);\n const nextKeys = Object.keys(nextRecord);\n if (previousKeys.length !== nextKeys.length) return false;\n for (const key of previousKeys) {\n if (!Object.prototype.hasOwnProperty.call(nextRecord, key)) return false;\n const previousValue = previousRecord[key];\n const nextValue = nextRecord[key];\n if (\n key === messagesKey &&\n Array.isArray(previousValue) &&\n Array.isArray(nextValue)\n ) {\n continue;\n }\n if (!Object.is(previousValue, nextValue)) return false;\n }\n return true;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"root-message-projection.js","names":["#messagesKey","#store","#roles","#indexById","#toolCallIdByNamespace","#sealedMessageIds","#assembler","#valuesMessageIds","#pendingMessages","#pendingValues","#flushScheduled","#maxStep","#sealStep","#scheduleFlush","#flushPending"],"sources":["../../src/stream/root-message-projection.ts"],"sourcesContent":["/**\n * Root-namespace message projection.\n *\n * # What this module is\n *\n * The {@link RootMessageProjection} is the piece of the\n * {@link StreamController} that owns \"what messages does the root\n * namespace currently contain?\". It assembles streamed message deltas\n * via {@link MessageAssembler}, reconciles them against authoritative\n * `values.messages` snapshots from the server, and writes the merged\n * list back into the controller's root snapshot store.\n *\n * # Two streams of truth\n *\n * Root messages arrive on two channels and need to merge cleanly:\n *\n * - **`messages` channel.** Token-level deltas that build messages\n * incrementally. The {@link MessageAssembler} keeps partial\n * messages by id and emits an updated `BaseMessage` per delta.\n * - **`values` channel.** Periodic full-state snapshots that include\n * the authoritative messages array. Used for ordering, removals,\n * and forks (where the streamed messages may pre-date the new\n * timeline).\n *\n * The reconciliation rules (delegated to\n * {@link reconcileMessagesFromValues}) preserve in-flight streamed\n * content while letting values dictate ordering and removals.\n *\n * # Tool-message namespace correlation\n *\n * Tool messages arrive on `messages-start` events with `role: \"tool\"`\n * but the start event doesn't always include a `tool_call_id`. We\n * recover it via three fallbacks:\n *\n * 1. The start event itself, when the server includes it.\n * 2. The legacy `<id>-tool-<call_id>` message id format.\n * 3. The most recent `tool-started` event recorded under the same\n * namespace via {@link recordToolCallNamespace}.\n *\n * Without this correlation, tool messages render with empty\n * `tool_call_id` and downstream UIs can't pair them with the\n * originating tool call.\n *\n * # Store-write batching\n *\n * Every {@link handleMessage} / {@link applyValues} call updates the\n * in-projection bookkeeping (assembler state, id index, role cache)\n * synchronously, then stages the new `messages` / `values` into a\n * pending buffer and schedules a `setTimeout(0)` flush. A single\n * coalesced `store.setState` runs at the next macrotask boundary.\n *\n * The motivation is the long-replay freeze: a thread with hundreds\n * of messages replays through the `messages` channel on refresh or\n * mid-run join. Those events drain through the controller's\n * `for await` pump as a long microtask chain. Per-event\n * `store.setState` notifies `useSyncExternalStore` per event, and\n * after enough notifications React's `nestedUpdateCount` guard trips\n * with \"Maximum update depth exceeded\", permanently freezing the UI\n * on the first few messages. Coalescing to one notification per\n * macrotask lets React's scheduler commit between flushes.\n *\n * # Lifecycle\n *\n * - `handleMessage(event)` — apply a `messages` event delta.\n * - `applyValues(values, msgs)` — merge a `values` snapshot.\n * - `recordToolCallNamespace(ns, id)` — capture `namespace → tool_call_id`\n * so subsequent tool message starts can recover the id.\n * - `reset()` — clear all state on thread rebind.\n */\nimport type {\n MessagesEvent,\n MessageRole,\n MessageStartData,\n} from \"@langchain/protocol\";\nimport type { BaseMessage } from \"@langchain/core/messages\";\nimport { MessageAssembler } from \"../client/stream/messages.js\";\nimport {\n assembledMessageToBaseMessage,\n type ExtendedMessageRole,\n} from \"./assembled-to-message.js\";\nimport type { StreamStore } from \"./store.js\";\nimport type { RootSnapshot } from \"./types.js\";\nimport { namespaceKey } from \"./namespace.js\";\nimport {\n buildMessageIndex,\n messagesEqual,\n reconcileMessagesFromValues,\n shouldPreferValuesMessageForToolCalls,\n} from \"./message-reconciliation.js\";\n\n/**\n * Root-namespace message projection. Owns the merge between the\n * `messages` (streamed deltas) and `values` (authoritative\n * snapshots) channels for the root namespace.\n *\n * @typeParam StateType - Root state shape; the messages array is read\n * from `values[messagesKey]`.\n * @typeParam InterruptType - Shape of root protocol interrupts (forwarded\n * into `RootSnapshot` updates).\n */\nexport class RootMessageProjection<\n StateType extends object,\n InterruptType = unknown,\n> {\n /**\n * Key inside `values` that holds the message array. Defaults to\n * `\"messages\"` in the controller; configurable for state graphs\n * that surface messages under a different slot.\n */\n readonly #messagesKey: string;\n\n /** Root snapshot store written to on every merge. */\n readonly #store: StreamStore<RootSnapshot<StateType, InterruptType>>;\n\n /**\n * Stateful chunk assembler for in-flight messages. Reset (via a\n * fresh instance) on every {@link reset} so a new thread starts\n * with no half-built messages from the previous one.\n */\n #assembler = new MessageAssembler();\n\n /**\n * `messageId → role/toolCallId` captured from `message-start` events.\n * The assembler's intermediate output drops these fields, so we cache\n * them at start-time and reapply when projecting to a `BaseMessage`.\n */\n readonly #roles = new Map<\n string,\n { role: ExtendedMessageRole; toolCallId?: string }\n >();\n\n /**\n * `messageId → position in #store.messages` for fast in-place\n * updates as deltas arrive. Rebuilt on every full reconciliation\n * driven by a `values` event.\n */\n readonly #indexById = new Map<string, number>();\n\n /**\n * Ids observed in the most recent `values.messages` snapshot.\n * Reconciliation uses this to detect server-side removals: a\n * previously-seen id missing from the next snapshot means it was\n * removed by the server (and should drop from the projection).\n */\n #valuesMessageIds = new Set<string>();\n\n /**\n * `namespaceKey → tool_call_id` captured from root `tool-started`\n * events. Used as a fallback when a tool-role `message-start` is\n * missing its `tool_call_id` field.\n */\n readonly #toolCallIdByNamespace = new Map<string, string>();\n\n /**\n * Coalescing buffer for store writes. {@link handleMessage} and\n * {@link applyValues} stage their computed `messages` / `values`\n * here instead of calling `store.setState` per event. A single\n * `setTimeout(0)` flush commits them in one `setState`, so a\n * burst of SSE events draining as a microtask chain becomes one\n * store notification at the next macrotask boundary.\n *\n * `null` means \"no staged write\" — once a flush settles, the\n * slots are cleared so the next call starts from the latest\n * committed store snapshot.\n */\n #pendingMessages: BaseMessage[] | null = null;\n #pendingValues: StateType | null = null;\n #flushScheduled = false;\n\n /**\n * Highest checkpoint `step` whose `values` snapshot has been applied.\n * Seeded by {@link StreamController.hydrate} from `getState()` and\n * advanced by live `values` events. A snapshot arriving with a lower\n * step is an older checkpoint replayed by the content pump on\n * reconnect; it is reconciled in add-only mode so it cannot remove\n * the seeded message tail (the final assistant turn). `undefined`\n * until the first step-bearing snapshot, where the legacy\n * remove-on-absence behavior is preserved.\n */\n #maxStep: number | undefined = undefined;\n\n /**\n * Message ids seeded as complete-and-final from an idle thread's\n * `getState()` snapshot. An idle thread defers its root SSE pump, and\n * the first `submit()` brings it up — at which point the transport\n * replays the finished run from `seq=0`. Unlike the `values` channel\n * (guarded by {@link #maxStep}), `messages`-channel deltas carry no\n * step, so that replay would otherwise rebuild each already-complete\n * message from an empty `message-start` and re-stream the whole turn\n * token-by-token, clobbering the seeded tail (a visible \"messages\n * replay\" on the first submit). Deltas for a sealed id are dropped in\n * {@link handleMessage}. The seal is lifted once a checkpoint advances\n * strictly past {@link #sealStep} (see {@link applyValues}) or on\n * thread rebind ({@link reset}). New ids from the next run are never\n * sealed, so they stream normally.\n */\n readonly #sealedMessageIds = new Set<string>();\n\n /**\n * High-water {@link #maxStep} captured when {@link sealMessageIds} ran,\n * i.e. the seed checkpoint's step (or `undefined` when `getState()`\n * carried no `metadata.step`). It is the boundary between the replayed\n * idle history (steps `<= #sealStep`, emitted by the deferred pump's\n * `seq=0` replay) and the new run (steps `> #sealStep`); only a\n * checkpoint strictly past it lifts the seal. Without this boundary the\n * replayed old-run checkpoints — which themselves carry increasing\n * steps — would advance {@link #maxStep} and lift the seal mid-replay,\n * reopening the clobber. When the seed step is unknown the boundary\n * stays `undefined` and the seal holds until {@link reset}; the\n * `values` channel (which ignores the seal) still reconciles any\n * genuine change to a sealed id, only its streamed deltas are dropped.\n */\n #sealStep: number | undefined = undefined;\n\n /**\n * @param params.messagesKey - Key inside `values` that holds the\n * message array.\n * @param params.store - Root snapshot store to mutate.\n */\n constructor(params: {\n messagesKey: string;\n store: StreamStore<RootSnapshot<StateType, InterruptType>>;\n }) {\n this.#messagesKey = params.messagesKey;\n this.#store = params.store;\n }\n\n /**\n * Drop all per-thread state. Called by the controller on thread\n * rebind / dispose so a swap doesn't surface stale messages.\n */\n reset(): void {\n this.#assembler = new MessageAssembler();\n this.#roles.clear();\n this.#indexById.clear();\n this.#valuesMessageIds = new Set();\n this.#toolCallIdByNamespace.clear();\n // Drop any unflushed pending writes — they were computed against\n // the previous thread's baseline and committing them after a\n // rebind would bleed stale messages into the new thread.\n this.#pendingMessages = null;\n this.#pendingValues = null;\n this.#flushScheduled = false;\n this.#maxStep = undefined;\n this.#sealedMessageIds.clear();\n this.#sealStep = undefined;\n }\n\n /**\n * Seal message ids so the streamed `messages` channel cannot downgrade\n * them to partial re-streams. Called by {@link StreamController.hydrate}\n * after seeding an idle thread, whose deferred pump replays the finished\n * run from `seq=0` on the first submit.\n *\n * Captures the current {@link #maxStep} as the lift boundary\n * ({@link #sealStep}). The seal is applied immediately after the seed's\n * `getState()` snapshot is reconciled, so `#maxStep` here is the seed\n * step (or `undefined` when `getState()` carried no `metadata.step`).\n * The seal is lifted once a checkpoint advances strictly past that\n * boundary (see {@link applyValues}) or on thread rebind\n * ({@link reset}).\n *\n * @param ids - Complete message ids from the idle `getState()` seed.\n */\n sealMessageIds(ids: Iterable<string>): void {\n for (const id of ids) this.#sealedMessageIds.add(id);\n if (this.#sealStep == null) this.#sealStep = this.#maxStep;\n }\n\n /**\n * Record a `namespace → tool_call_id` mapping captured from a root\n * `tool-started` event.\n *\n * The companion tool-role `message-start` event may not carry a\n * `tool_call_id`, so we fall back to the most recent value recorded\n * here for the same namespace.\n *\n * @param namespace - Event namespace from the `tool-started` event.\n * @param toolCallId - Tool call id from the same event.\n */\n recordToolCallNamespace(\n namespace: readonly string[],\n toolCallId: string\n ): void {\n this.#toolCallIdByNamespace.set(namespaceKey(namespace), toolCallId);\n }\n\n /**\n * Apply a `messages` channel event to the projection.\n *\n * Captures role/tool metadata on `message-start`, feeds the chunk\n * to the assembler, projects the assembled output to a\n * {@link BaseMessage}, and either appends or in-place updates the\n * pending messages buffer based on whether the id was seen before.\n *\n * @param event - The `messages` channel event to consume.\n */\n handleMessage(event: MessagesEvent): void {\n const data = event.params.data;\n if (data.event === \"message-start\") {\n const startData = data as MessageStartData;\n const role = (startData.role ?? \"ai\") as MessageRole;\n const extendedRole =\n (startData as { role?: ExtendedMessageRole }).role ?? role;\n let toolCallId = (startData as { tool_call_id?: string }).tool_call_id;\n // Tool messages need a tool_call_id to render. Fall back through:\n // 1. legacy `<id>-tool-<call_id>` message id format\n // 2. namespace-recorded tool_call_id (from #recordToolCallNamespace)\n if (extendedRole === \"tool\" && toolCallId == null) {\n const messageId = startData.id;\n if (messageId != null) {\n const match = /-tool-(.+)$/.exec(messageId);\n if (match != null) toolCallId = match[1];\n }\n if (toolCallId == null) {\n toolCallId = this.#toolCallIdByNamespace.get(\n namespaceKey(event.params.namespace)\n );\n }\n }\n if (startData.id != null) {\n this.#roles.set(startData.id, {\n role: extendedRole,\n toolCallId,\n });\n }\n }\n\n const update = this.#assembler.consume(event);\n if (update == null) return;\n const id = update.message.id;\n if (id == null) return;\n // A sealed id belongs to a message seeded complete from an idle\n // thread's `getState()`; the deferred pump's `seq=0` replay would\n // otherwise rebuild it from an empty start and re-stream the whole\n // turn. Drop the replayed delta — the authoritative seed already\n // holds the final content (see {@link #sealedMessageIds}).\n if (this.#sealedMessageIds.has(id)) return;\n const captured = this.#roles.get(id) ?? { role: \"ai\" as const };\n const base = assembledMessageToBaseMessage(update.message, captured.role, {\n toolCallId: captured.toolCallId,\n });\n\n // Compute against the pending baseline if we have one (so an\n // earlier handleMessage in the same tick is the input to this\n // one), else against the latest committed store snapshot.\n // `#indexById` is the synchronous source of truth for \"where is\n // each id in the current messages list\" — every code path below\n // keeps it in sync before returning.\n const baselineMessages =\n this.#pendingMessages ?? this.#store.getSnapshot().messages;\n const existingIdx = this.#indexById.get(id);\n let messages: BaseMessage[];\n if (existingIdx == null) {\n this.#indexById.set(id, baselineMessages.length);\n messages = [...baselineMessages, base];\n } else if (messagesEqual(baselineMessages[existingIdx], base)) {\n // Identical re-emission — skip the store write to keep\n // snapshot identity stable.\n return;\n } else {\n messages = baselineMessages.slice();\n messages[existingIdx] = base;\n }\n\n // Mirror the new messages list into `values[messagesKey]` so\n // direct `values` reads (used by some hooks and by the eventual\n // `values` reconciliation) stay in sync.\n const baselineValues =\n this.#pendingValues ?? this.#store.getSnapshot().values;\n const values = syncMessagesIntoValues(\n baselineValues,\n this.#messagesKey,\n messages\n );\n this.#pendingMessages = messages;\n if (values !== baselineValues) this.#pendingValues = values;\n this.#scheduleFlush();\n }\n\n /**\n * Reconcile a full `values` snapshot into the projection.\n *\n * Delegates the merge to {@link reconcileMessagesFromValues}:\n * values stays authoritative for ordering and removals, while\n * streamed in-flight messages keep their content until the server\n * echoes them back. Empty messages just refresh the values blob.\n *\n * Rebuilds {@link #indexById} after the merge so subsequent delta\n * applications target the new positions.\n *\n * @param nextValues - Full values snapshot from the `values` event.\n * @param nextMessages - The messages array extracted from\n * `values[messagesKey]` and coerced to `BaseMessage` instances.\n * @param opts.step - Checkpoint superstep for this snapshot, when\n * known. A snapshot whose step is below the highest applied step is\n * treated as a stale reconnect replay and reconciled add-only.\n */\n applyValues(\n nextValues: StateType,\n nextMessages: BaseMessage[],\n opts?: { step?: number }\n ): void {\n const baselineSnapshot = this.#store.getSnapshot();\n const baselineMessages = this.#pendingMessages ?? baselineSnapshot.messages;\n const baselineValues = this.#pendingValues ?? baselineSnapshot.values;\n\n const step = opts?.step;\n // Stale only when we have both a prior high-water step and a lower\n // incoming step. A missing step preserves the legacy semantics.\n const addOnly =\n step != null && this.#maxStep != null && step < this.#maxStep;\n if (step != null && (this.#maxStep == null || step > this.#maxStep)) {\n this.#maxStep = step;\n }\n // Lift the replay seal only when a checkpoint advances strictly past\n // the step captured when the ids were sealed (the seed step). That\n // boundary separates the replayed idle history (steps <= #sealStep,\n // emitted by the deferred pump's seq=0 replay) from the new run\n // (steps > #sealStep), so crossing it means seeded ids may now take\n // genuine streamed updates. Replayed old-run checkpoints advance\n // #maxStep but never reach past #sealStep, so they can't lift it. A\n // `null` boundary (the seed step was unknown) keeps the seal until\n // reset() — we can't tell replay from live, and the values channel\n // still reconciles a sealed id even while its streamed deltas drop.\n if (\n this.#sealedMessageIds.size > 0 &&\n step != null &&\n this.#sealStep != null &&\n step > this.#sealStep\n ) {\n this.#sealedMessageIds.clear();\n }\n\n if (nextMessages.length === 0) {\n if (\n stateValuesShallowEqual(baselineValues, nextValues, this.#messagesKey)\n ) {\n return;\n }\n // Mirror the current `messages` list back into the values slot\n // so the staged snapshot stays consistent with the (separately\n // tracked) messages array.\n this.#pendingValues = syncMessagesIntoValues(\n nextValues,\n this.#messagesKey,\n baselineMessages\n );\n this.#scheduleFlush();\n return;\n }\n\n const reconciliation = reconcileMessagesFromValues({\n valueMessages: nextMessages,\n currentMessages: baselineMessages,\n currentIndexById: this.#indexById,\n previousValueMessageIds: this.#valuesMessageIds,\n preferValuesMessage: shouldPreferValuesMessageForToolCalls,\n addOnly,\n });\n // A stale replay snapshot must not shrink the authoritative id set:\n // keep the (larger) seeded set so a genuinely-newer removal is still\n // detected once the timeline advances past the seed.\n if (!addOnly) this.#valuesMessageIds = reconciliation.valueMessageIds;\n const messages = reconciliation.messages as BaseMessage[];\n const values = {\n ...(nextValues as Record<string, unknown>),\n [this.#messagesKey]: messages,\n } as StateType;\n if (\n messages === baselineMessages &&\n stateValuesShallowEqual(baselineValues, values, this.#messagesKey)\n ) {\n return;\n }\n\n // Reconciliation may reorder, drop, or substitute messages, so\n // rebuild the id → index map to match the new array.\n this.#indexById.clear();\n for (const [id, idx] of buildMessageIndex(messages)) {\n this.#indexById.set(id, idx);\n }\n this.#pendingMessages = messages;\n this.#pendingValues = values;\n this.#scheduleFlush();\n }\n\n /**\n * Append messages applied optimistically by a local `submit()`,\n * keyed by id so the eventual server echo reconciles cleanly.\n *\n * Unlike {@link applyValues}, the supplied messages are *not* treated\n * as an authoritative ordered snapshot: they are appended to the end\n * of the current projection (or replaced in place when the id already\n * exists), preserving prior history ordering. When the server later\n * emits a `values` snapshot containing the same ids,\n * {@link applyValues} → {@link reconcileMessagesFromValues} takes over\n * (server ordering wins, the echoed message replaces the optimistic\n * one).\n *\n * Non-message input keys are shallow-merged into `values` via\n * `extraValues`; they are dropped/overwritten automatically by the\n * first server `values` event (which rebuilds `values` from the\n * server snapshot), or rolled back via {@link restoreValueKeys} when\n * the run fails before any echo.\n *\n * @param messages - Optimistic messages (already coerced to\n * `BaseMessage` instances, each carrying a stable id).\n * @param extraValues - Non-message input keys to shallow-merge into\n * `values`.\n */\n appendOptimistic(\n messages: BaseMessage[],\n extraValues?: Record<string, unknown>\n ): void {\n let working = this.#pendingMessages ?? this.#store.getSnapshot().messages;\n let mutated = false;\n for (const message of messages) {\n const id = message.id;\n if (id == null) continue;\n const existingIdx = this.#indexById.get(id);\n if (existingIdx == null) {\n if (!mutated) {\n working = working.slice();\n mutated = true;\n }\n this.#indexById.set(id, working.length);\n working.push(message);\n } else if (!messagesEqual(working[existingIdx], message)) {\n if (!mutated) {\n working = working.slice();\n mutated = true;\n }\n working[existingIdx] = message;\n }\n }\n\n const baselineValues =\n this.#pendingValues ?? this.#store.getSnapshot().values;\n let values = baselineValues;\n if (extraValues != null && Object.keys(extraValues).length > 0) {\n values = { ...(baselineValues as object), ...extraValues } as StateType;\n }\n values = syncMessagesIntoValues(values, this.#messagesKey, working);\n if (!mutated && values === baselineValues) return;\n this.#pendingMessages = working;\n if (values !== baselineValues) this.#pendingValues = values;\n this.#scheduleFlush();\n }\n\n /**\n * Drop optimistic messages by id without disturbing the rest of the\n * projection. Used by {@link StreamController.hydrate} to remove\n * never-persisted optimistic messages (`pending` / `failed`) so a\n * reload converges to server truth.\n *\n * @param ids - Message ids to remove.\n */\n dropOptimisticMessages(ids: ReadonlySet<string>): void {\n if (ids.size === 0) return;\n const baselineMessages =\n this.#pendingMessages ?? this.#store.getSnapshot().messages;\n const next = baselineMessages.filter((m) => m.id == null || !ids.has(m.id));\n if (next.length === baselineMessages.length) return;\n this.#indexById.clear();\n for (const [id, idx] of buildMessageIndex(next)) {\n this.#indexById.set(id, idx);\n }\n const baselineValues =\n this.#pendingValues ?? this.#store.getSnapshot().values;\n this.#pendingMessages = next;\n this.#pendingValues = syncMessagesIntoValues(\n baselineValues,\n this.#messagesKey,\n next\n );\n this.#scheduleFlush();\n }\n\n /**\n * Restore (or delete) `values` keys that were optimistically merged\n * by {@link appendOptimistic} but never echoed by the server — i.e.\n * roll back non-message optimistic state when the run fails before\n * any `values` event lands. Messages are left untouched (kept on\n * failure per the optimistic contract).\n *\n * @param restore - Per-key pre-submit snapshot: when `hadKey` is\n * false the key is deleted, otherwise it is reset to `prevValue`.\n */\n restoreValueKeys(\n restore: ReadonlyArray<{\n key: string;\n hadKey: boolean;\n prevValue: unknown;\n }>\n ): void {\n if (restore.length === 0) return;\n const baselineValues =\n this.#pendingValues ?? this.#store.getSnapshot().values;\n const next = { ...(baselineValues as Record<string, unknown>) };\n let changed = false;\n for (const { key, hadKey, prevValue } of restore) {\n if (key === this.#messagesKey) continue;\n if (hadKey) {\n if (!Object.is(next[key], prevValue)) {\n next[key] = prevValue;\n changed = true;\n }\n } else if (Object.prototype.hasOwnProperty.call(next, key)) {\n delete next[key];\n changed = true;\n }\n }\n if (!changed) return;\n this.#pendingValues = next as StateType;\n this.#scheduleFlush();\n }\n\n /**\n * Schedule a coalesced flush on the next macrotask. Idempotent\n * within a tick — multiple `handleMessage` / `applyValues` calls\n * before the flush fires collapse into one store write.\n *\n * `setTimeout(0)` is a macrotask: it runs after the current\n * microtask chain drains, so a burst of SSE events processed by\n * the controller's `for await` pump becomes one `store.setState`\n * (and therefore one `useSyncExternalStore` notification).\n */\n #scheduleFlush = (): void => {\n if (this.#flushScheduled) return;\n this.#flushScheduled = true;\n setTimeout(this.#flushPending, 0);\n };\n\n /**\n * Drain `#pendingMessages` / `#pendingValues` to the store in a\n * single `setState` call.\n */\n #flushPending = (): void => {\n this.#flushScheduled = false;\n const messages = this.#pendingMessages;\n const values = this.#pendingValues;\n this.#pendingMessages = null;\n this.#pendingValues = null;\n if (messages == null && values == null) return;\n this.#store.setState((s) => {\n // Other rootStore mutators (controller-driven `isLoading`,\n // `interrupts`, `toolCalls`, etc.) do not touch `s.messages`\n // / `s.values`, so a last-write-wins commit on those two\n // fields is safe.\n if (messages == null) {\n return values == null ? s : { ...s, values };\n }\n if (values == null) return { ...s, messages };\n return { ...s, messages, values };\n });\n };\n}\n\n/**\n * Mirror a freshly-updated message list into `values[messagesKey]`.\n *\n * Returns the same `values` reference when the list is already\n * equal-by-content so the caller can keep the existing snapshot\n * identity (and avoid spurious `setSnapshot` notifications).\n */\nfunction syncMessagesIntoValues<StateType extends object>(\n values: StateType,\n messagesKey: string,\n messages: BaseMessage[]\n): StateType {\n const record = values as Record<string, unknown>;\n const current = record[messagesKey];\n if (Array.isArray(current) && messagesEqualList(current, messages)) {\n return values;\n }\n return {\n ...record,\n [messagesKey]: messages,\n } as StateType;\n}\n\n/**\n * True when two `BaseMessage` arrays carry the same per-message\n * content (using {@link messagesEqual}).\n */\nfunction messagesEqualList(\n previous: readonly BaseMessage[],\n next: readonly BaseMessage[]\n): boolean {\n if (previous === next) return true;\n if (previous.length !== next.length) return false;\n for (let i = 0; i < previous.length; i += 1) {\n if (!messagesEqual(previous[i], next[i])) return false;\n }\n return true;\n}\n\n/**\n * Shallow-equal for `values` objects, *ignoring* the messages slot.\n *\n * The messages array is compared separately by the caller (via\n * {@link messagesEqualList}) because both arrays contain class\n * instances whose JSON representation is not stable across reads.\n */\nfunction stateValuesShallowEqual(\n previous: object,\n next: object,\n messagesKey: string\n): boolean {\n if (previous === next) return true;\n const previousRecord = previous as Record<string, unknown>;\n const nextRecord = next as Record<string, unknown>;\n const previousKeys = Object.keys(previousRecord);\n const nextKeys = Object.keys(nextRecord);\n if (previousKeys.length !== nextKeys.length) return false;\n for (const key of previousKeys) {\n if (!Object.prototype.hasOwnProperty.call(nextRecord, key)) return false;\n const previousValue = previousRecord[key];\n const nextValue = nextRecord[key];\n if (\n key === messagesKey &&\n Array.isArray(previousValue) &&\n Array.isArray(nextValue)\n ) {\n continue;\n }\n if (!Object.is(previousValue, nextValue)) return false;\n }\n return true;\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  require("../_virtual/_rolldown/runtime.cjs");
2
- let uuid = require("uuid");
2
+ let _langchain_core_utils_uuid = require("@langchain/core/utils/uuid");
3
3
  //#region src/stream/submit-coordinator.ts
4
4
  /**
5
5
  * Owns the run-submission lifecycle for a single
@@ -204,7 +204,7 @@ var SubmitCoordinator = class {
204
204
  if (overrideThreadId !== void 0 && overrideThreadId !== this.#getCurrentThreadId()) await this.#hydrate(overrideThreadId);
205
205
  const wasSelfCreated = this.#getCurrentThreadId() == null;
206
206
  if (wasSelfCreated) {
207
- const threadId = (0, uuid.v7)();
207
+ const threadId = (0, _langchain_core_utils_uuid.v7)();
208
208
  this.#setCurrentThreadId(threadId);
209
209
  this.#rememberSelfCreatedThreadId(threadId);
210
210
  this.#options.onThreadId?.(threadId);
@@ -457,7 +457,7 @@ var SubmitCoordinator = class {
457
457
  */
458
458
  #enqueueSubmission(input, options) {
459
459
  const entry = {
460
- id: (0, uuid.v7)(),
460
+ id: (0, _langchain_core_utils_uuid.v7)(),
461
461
  values: input ?? void 0,
462
462
  options,
463
463
  createdAt: /* @__PURE__ */ new Date()
@@ -1 +1 @@
1
- {"version":3,"file":"submit-coordinator.cjs","names":["#options","#rootStore","#queueStore","#getDisposed","#getCurrentThreadId","#setCurrentThreadId","#rememberSelfCreatedThreadId","#forgetSelfCreatedThreadId","#hydrate","#ensureThread","#startDeferredRootPump","#abandonDeferredRootPump","#waitForRootPumpReady","#awaitNextTerminal","#awaitResumedRunTerminal","#onSubmitStart","#onRunStart","#onRunCreated","#onRunCompleted","#onRunEnd","#beginOptimistic","#settleOptimistic","#runAbort","#enqueueSubmission","#drainQueue"],"sources":["../../src/stream/submit-coordinator.ts"],"sourcesContent":["/**\n * Owns the run-submission lifecycle for a single\n * {@link StreamController}.\n *\n * # What this module is\n *\n * The {@link SubmitCoordinator} is the piece of the controller that\n * dispatches runs (`submit()`), enforces multitask strategies, queues\n * deferred submissions, races dispatch against terminal lifecycle\n * events, and surfaces errors back through the per-submit `onError`\n * callback and the root snapshot.\n *\n * Conceptually a submit looks like:\n *\n * 1. Optionally rebind to a different thread (`options.threadId`).\n * 2. Mint a thread id if one isn't bound yet.\n * 3. Wait for the controller's root pump to be ready (so the\n * transport is subscribed before the run is dispatched —\n * otherwise we could miss replayed events).\n * 4. Apply the {@link StreamSubmitOptions.multitaskStrategy} to\n * decide whether to abort, enqueue, reject, or proceed.\n * 5. Race the dispatch promise (`thread.submitRun()`) against the next root\n * terminal lifecycle event.\n * 6. Settle the resulting state (loading flag, error slot) and\n * drain the next queued submission, if any.\n *\n * # Why it lives in its own class\n *\n * The submit lifecycle is the most state-heavy part of the\n * controller — six promises, an abort controller, a queue, a\n * terminal-vs-command race, and bidirectional callback wiring with\n * the controller. Splitting it out keeps `controller.ts` focused on\n * subscription / projection wiring while letting the submit logic\n * evolve independently.\n *\n * # Why we race \"command\" against \"terminal\"\n *\n * For fast runs, the server's terminal lifecycle event can arrive\n * *before* the dispatch HTTP response has resolved. Racing the two\n * lets us detect terminal early and not block waiting for a now-stale\n * dispatch response. The dispatch response is still consumed (via\n * `.then(notifyCreated).catch(reportError)`) so `onCreated` still\n * fires and dispatch errors still surface through `onError`.\n *\n * # Queue semantics (`multitaskStrategy: \"enqueue\"`)\n *\n * When a run is already in flight, an `\"enqueue\"` submit is recorded\n * into {@link queueStore} and the call returns immediately. After the\n * active run terminates, `#drainQueue` schedules the head of the\n * queue as a fresh submit on the next macrotask. Each drained\n * submission has its own `multitaskStrategy` cleared so it doesn't\n * recursively re-enqueue.\n *\n * @see StreamController - The owner; injects every collaborator dep.\n */\nimport { v7 as uuidv7 } from \"uuid\";\nimport type { ThreadStream } from \"../client/stream/index.js\";\nimport { StreamStore } from \"./store.js\";\nimport type { OptimisticHandle } from \"./optimistic-input.js\";\nimport type {\n RootSnapshot,\n RunExecutionReason,\n StreamControllerOptions,\n StreamSubmitOptions,\n} from \"./types.js\";\n\n/**\n * Result of awaiting the next root terminal lifecycle event. Mirrors\n * the three terminal lifecycle states the protocol surfaces, plus a\n * synthetic `\"aborted\"` for client-side cancellation.\n */\ntype TerminalResult = {\n event: \"completed\" | \"failed\" | \"interrupted\" | \"aborted\";\n error?: string;\n};\n\nfunction terminalReason(event: TerminalResult[\"event\"]): RunExecutionReason {\n if (event === \"completed\") return \"success\";\n if (event === \"failed\") return \"error\";\n if (event === \"interrupted\") return \"interrupt\";\n return \"stopped\";\n}\n\n/**\n * Queued submission entry mirrored from the server-side run queue.\n *\n * Surfaces the deferred submission to UI consumers via\n * {@link StreamController.queueStore}.\n */\nexport interface SubmissionQueueEntry<\n StateType extends object = Record<string, unknown>,\n> {\n /** Stable id minted on enqueue (uuidv7 — sortable by creation time). */\n readonly id: string;\n /** Original submit input, narrowed to the partial state shape. */\n readonly values: Partial<StateType> | null | undefined;\n /** Original submit options, minus the strategy slot which is reset on drain. */\n readonly options?: StreamSubmitOptions<StateType>;\n /** Wall-clock timestamp at enqueue. */\n readonly createdAt: Date;\n}\n\n/**\n * Read-only snapshot of the queue. The queue store hands this out\n * directly; consumers must not mutate the array.\n */\nexport type SubmissionQueueSnapshot<\n StateType extends object = Record<string, unknown>,\n> = ReadonlyArray<SubmissionQueueEntry<StateType>>;\n\n/**\n * Frozen empty queue value used as the initial / cleared snapshot.\n *\n * Reusing one frozen reference keeps store identity stable across\n * empty resets, so React's `useSyncExternalStore` doesn't think the\n * queue changed when it actually didn't.\n */\nexport const EMPTY_QUEUE: SubmissionQueueSnapshot<never> = Object.freeze([]);\n\n/**\n * Coordinates one controller's run-submission lifecycle.\n *\n * The constructor takes a bag of callbacks rather than a reference to\n * the parent {@link StreamController} on purpose:\n *\n * - It keeps the dependency surface explicit and testable — every\n * piece of controller state the submit lifecycle touches is one\n * of these closures.\n * - It avoids a cyclic dependency between controller and coordinator.\n * - Tests can construct one with stub callbacks and assert behavior\n * without mocking the entire controller.\n *\n * @typeParam StateType - Root state shape.\n * @typeParam InterruptType - Root interrupt payload shape.\n * @typeParam ConfigurableType - `config.configurable` shape accepted\n * by submit (usually `Record<string, unknown>`).\n */\nexport class SubmitCoordinator<\n StateType extends object = Record<string, unknown>,\n InterruptType = unknown,\n ConfigurableType extends object = Record<string, unknown>,\n> {\n /** Controller-level options forwarded into `submitRun` / callbacks. */\n readonly #options: StreamControllerOptions<StateType>;\n /** Root snapshot store; written for `isLoading`, `error`, `interrupts`. */\n readonly #rootStore: StreamStore<RootSnapshot<StateType, InterruptType>>;\n /** Pending submissions awaiting the active run to terminate. */\n readonly #queueStore: StreamStore<SubmissionQueueSnapshot<StateType>>;\n /** Probes the controller's `disposed` flag from deferred work. */\n readonly #getDisposed: () => boolean;\n /** Reads the controller's currently-bound thread id. */\n readonly #getCurrentThreadId: () => string | null;\n /** Updates the controller's thread id (used when minting a new id). */\n readonly #setCurrentThreadId: (threadId: string | null) => void;\n /** Records a thread id we created client-side so hydrate can skip a 404 round-trip. */\n readonly #rememberSelfCreatedThreadId: (threadId: string) => void;\n /** Drops a thread id from the self-created set once it's committed server-side. */\n readonly #forgetSelfCreatedThreadId: (threadId: string) => void;\n /** Triggers a hydrate on the controller (used by `options.threadId` rebinds). */\n readonly #hydrate: (threadId?: string | null) => Promise<void>;\n /** Lazily creates / returns the active {@link ThreadStream}. */\n readonly #ensureThread: (\n threadId: string,\n deferRootPump?: boolean\n ) => ThreadStream;\n /** Starts the previously-deferred root pump after a self-created thread commits. */\n readonly #startDeferredRootPump: () => void;\n /** Abandons a deferred root pump after a self-created dispatch fails. */\n readonly #abandonDeferredRootPump: () => void;\n /** Resolves once the controller's root subscription pump is up. */\n readonly #waitForRootPumpReady: () => Promise<void> | undefined;\n /** Resolves on the next root terminal lifecycle (or on abort). */\n readonly #awaitNextTerminal: (signal: AbortSignal) => Promise<TerminalResult>;\n /**\n * Resolves on the resumed run's terminal, skipping stale `interrupted`\n * events from the run being resumed (see {@link dispatchResume}).\n */\n readonly #awaitResumedRunTerminal: (\n signal: AbortSignal\n ) => Promise<TerminalResult>;\n /** Called once at the start of every {@link submit} invocation. */\n readonly #onSubmitStart: () => void;\n /** Marks that a local run dispatch is now active. */\n readonly #onRunStart: () => void;\n /** Records a server-accepted local run id and fires `onCreated`. */\n readonly #onRunCreated: (runId: string) => void;\n /** Fires `onCompleted` for the local run lifecycle. */\n readonly #onRunCompleted: (\n reason: RunExecutionReason,\n runId?: string\n ) => void;\n /** Marks the local run dispatch lifecycle as settled. */\n readonly #onRunEnd: () => void;\n /**\n * Apply a submit input optimistically before dispatch. Returns the\n * id-injected payload to dispatch plus a handle for terminal\n * reconciliation, or `undefined` when optimistic UI is disabled / no\n * echo applies (dispatch the raw input).\n */\n readonly #beginOptimistic: (\n input: unknown\n ) => { dispatchInput: unknown; handle: OptimisticHandle } | undefined;\n /** Reconcile optimistic state when a run terminates. */\n readonly #settleOptimistic: (\n handle: OptimisticHandle,\n event: TerminalResult[\"event\"]\n ) => void;\n\n /**\n * Active submission's abort controller. `undefined` between submits.\n *\n * Used both for `multitaskStrategy: \"rollback\"` (abort the previous\n * controller's signal) and `stop()` (abort the current one without\n * starting a new one).\n */\n #runAbort: AbortController | undefined;\n\n constructor(params: {\n options: StreamControllerOptions<StateType>;\n rootStore: StreamStore<RootSnapshot<StateType, InterruptType>>;\n queueStore: StreamStore<SubmissionQueueSnapshot<StateType>>;\n getDisposed: () => boolean;\n getCurrentThreadId: () => string | null;\n setCurrentThreadId: (threadId: string | null) => void;\n rememberSelfCreatedThreadId: (threadId: string) => void;\n forgetSelfCreatedThreadId: (threadId: string) => void;\n hydrate: (threadId?: string | null) => Promise<void>;\n ensureThread: (threadId: string, deferRootPump?: boolean) => ThreadStream;\n startDeferredRootPump: () => void;\n abandonDeferredRootPump: () => void;\n waitForRootPumpReady: () => Promise<void> | undefined;\n awaitNextTerminal: (signal: AbortSignal) => Promise<TerminalResult>;\n awaitResumedRunTerminal: (signal: AbortSignal) => Promise<TerminalResult>;\n onSubmitStart?: () => void;\n onRunStart?: () => void;\n onRunCreated?: (runId: string) => void;\n onRunCompleted?: (reason: RunExecutionReason, runId?: string) => void;\n onRunEnd?: () => void;\n beginOptimistic?: (\n input: unknown\n ) => { dispatchInput: unknown; handle: OptimisticHandle } | undefined;\n settleOptimistic?: (\n handle: OptimisticHandle,\n event: TerminalResult[\"event\"]\n ) => void;\n }) {\n this.#options = params.options;\n this.#rootStore = params.rootStore;\n this.#queueStore = params.queueStore;\n this.#getDisposed = params.getDisposed;\n this.#getCurrentThreadId = params.getCurrentThreadId;\n this.#setCurrentThreadId = params.setCurrentThreadId;\n this.#rememberSelfCreatedThreadId = params.rememberSelfCreatedThreadId;\n this.#forgetSelfCreatedThreadId = params.forgetSelfCreatedThreadId;\n this.#hydrate = params.hydrate;\n this.#ensureThread = params.ensureThread;\n this.#startDeferredRootPump = params.startDeferredRootPump;\n this.#abandonDeferredRootPump = params.abandonDeferredRootPump;\n this.#waitForRootPumpReady = params.waitForRootPumpReady;\n this.#awaitNextTerminal = params.awaitNextTerminal;\n this.#awaitResumedRunTerminal = params.awaitResumedRunTerminal;\n this.#onSubmitStart = params.onSubmitStart ?? (() => undefined);\n this.#onRunStart = params.onRunStart ?? (() => undefined);\n this.#onRunCreated = params.onRunCreated ?? (() => undefined);\n this.#onRunCompleted = params.onRunCompleted ?? (() => undefined);\n this.#onRunEnd = params.onRunEnd ?? (() => undefined);\n this.#beginOptimistic = params.beginOptimistic ?? (() => undefined);\n this.#settleOptimistic = params.settleOptimistic ?? (() => undefined);\n }\n\n /**\n * Submit input to the active thread.\n *\n * Honours {@link StreamSubmitOptions.multitaskStrategy}:\n *\n * - `\"rollback\"` (default) — aborts any in-flight run and\n * dispatches immediately.\n * - `\"reject\"` — throws synchronously when a run is\n * already in flight.\n * - `\"enqueue\"` — defers via {@link #enqueueSubmission};\n * the call returns without dispatching.\n * - `\"interrupt\"` — falls through to the default path\n *\n * Errors are routed through both the per-submit `onError` callback\n * and `rootStore.error`. Aborts (controller dispose / rollback) are\n * silently dropped.\n *\n * To resume a pending interrupt, use {@link StreamController.respond}\n * instead of `submit()`.\n *\n * @param input - Input payload for the run.\n * @param options - Per-submit options (config, metadata, callbacks,\n * strategy, etc).\n */\n async submit(\n input: unknown,\n options?: StreamSubmitOptions<StateType, ConfigurableType>\n ): Promise<void> {\n if (this.#getDisposed()) return;\n this.#onSubmitStart();\n\n // Per-submit thread override: rebind first so the rest of the\n // submit operates against the new thread.\n const overrideThreadId = options?.threadId;\n if (\n overrideThreadId !== undefined &&\n overrideThreadId !== this.#getCurrentThreadId()\n ) {\n await this.#hydrate(overrideThreadId);\n }\n\n // Self-created thread id path: mint client-side so the controller\n // (and Suspense boundaries) get a stable id even before the run\n // is dispatched.\n const wasSelfCreated = this.#getCurrentThreadId() == null;\n if (wasSelfCreated) {\n const threadId = uuidv7();\n this.#setCurrentThreadId(threadId);\n this.#rememberSelfCreatedThreadId(threadId);\n this.#options.onThreadId?.(threadId);\n this.#rootStore.setState((s) => ({\n ...s,\n threadId,\n }));\n }\n\n const currentThreadId = this.#getCurrentThreadId();\n if (currentThreadId == null) return;\n // For client-self-created threads we defer the persistent root SSE\n // pump until after `submitRun` / `respondInput` commits the thread\n // server-side. Opening the pump's `subscription.subscribe` against\n // a not-yet-existent thread row produces a `404: Thread not found`\n // protocol error that strands lifecycle / messages events for the\n // first run. The deferred path starts the pump after dispatch\n // returns (see `#startDeferredRootPump` calls below).\n const thread = this.#ensureThread(currentThreadId, wasSelfCreated);\n const activeThreadId = currentThreadId;\n\n const strategy = options?.multitaskStrategy ?? \"rollback\";\n // `wasSelfCreated` short-circuit: when this submit just minted a\n // brand-new thread id (the user clicked \"New Thread\"), the\n // strategy check shouldn't see a run on the *previous* thread as\n // a reason to enqueue. The previous run is on a thread the user\n // navigated away from; abandoning its client-side abort tracking\n // is correct (the server-side run continues independently).\n // Without this, `enqueue` would trap the new submission and\n // `submitRun` never fires for the new thread — leaving a freshly-\n // minted thread id committed to the URL but never to the server.\n const hasActiveRun =\n !wasSelfCreated &&\n this.#runAbort != null &&\n !this.#runAbort.signal.aborted;\n if (hasActiveRun && strategy === \"reject\") {\n throw new Error(\n \"submit() rejected: a run is already in flight and multitaskStrategy is 'reject'.\"\n );\n }\n if (hasActiveRun && strategy === \"enqueue\") {\n this.#enqueueSubmission(input, options);\n return;\n }\n\n // Rollback: abort the previous run before starting a new one.\n this.#runAbort?.abort();\n const abort = new AbortController();\n this.#runAbort = abort;\n\n // Claim the in-flight slot before awaiting the root pump so\n // concurrent `enqueue` submits in the same tick observe\n // `hasActiveRun` and land in {@link queueStore}.\n this.#rootStore.setState((s) => ({\n ...s,\n interrupts: [],\n interrupt: undefined,\n error: undefined,\n isLoading: true,\n }));\n\n // Declared before the try so the catch/finally can settle the\n // submit lifecycle (loading flag, abort slot, optimistic state)\n // even if optimistic preparation or the pump wait throws.\n let optimisticHandle: OptimisticHandle | undefined;\n let dispatchInput: unknown = input;\n let createdRunId: string | undefined;\n let pendingCompletionReason: RunExecutionReason | undefined;\n let completionNotified = false;\n let settleEvent: TerminalResult[\"event\"] | undefined;\n const notifyCompletion = (reason: RunExecutionReason): void => {\n if (completionNotified) return;\n if (createdRunId == null) {\n pendingCompletionReason = reason;\n return;\n }\n completionNotified = true;\n this.#onRunCompleted(reason, createdRunId);\n };\n const reportError = (error: unknown): void => {\n if (abort.signal.aborted) return;\n this.#rootStore.setState((s) => ({ ...s, error }));\n try {\n options?.onError?.(error);\n } catch {\n /* caller-supplied callback errors must not crash the submit */\n }\n };\n\n try {\n // Apply the input optimistically *before* the first await so the\n // user's message (and any merged state) paints without waiting for\n // the server round-trip. Kept as the first statement in the try so\n // the synchronous paint still precedes the first `await`, while a\n // synchronous coercion failure (e.g. a malformed message entry)\n // settles the submit lifecycle through the catch/finally below —\n // exactly like a dispatch failure — instead of wedging `isLoading`\n // / `#runAbort` and stranding later enqueue/reject submits behind a\n // phantom in-flight run. Runs only on the dispatched path — an\n // `\"enqueue\"`d submission returns above and echoes when it drains,\n // keeping one optimistic batch bound to exactly one run lifecycle.\n // `dispatchInput` carries the minted ids the server must echo for\n // reconciliation, so the run is dispatched with it (not raw input).\n const prepared = this.#beginOptimistic(input);\n if (prepared != null) {\n optimisticHandle = prepared.handle;\n dispatchInput = prepared.dispatchInput;\n }\n\n // Wait for the root subscription to be live; otherwise the\n // dispatch could resolve before we're listening for events and\n // we'd miss the terminal that ends the run.\n await this.#waitForRootPumpReady();\n\n const boundConfig = bindThreadConfig(options?.config, currentThreadId);\n // Subscribe to the next terminal *before* dispatching so a fast\n // run's terminal can't race us.\n const terminalPromise = this.#awaitNextTerminal(abort.signal);\n this.#onRunStart();\n\n let terminalSettled = false;\n let terminal: TerminalResult | undefined;\n\n const commandPromise = thread.submitRun({\n input: dispatchInput ?? null,\n config: boundConfig,\n metadata: (options?.metadata ?? undefined) as Record<string, unknown>,\n forkFrom: options?.forkFrom,\n multitaskStrategy:\n options?.multitaskStrategy === \"enqueue\"\n ? \"enqueue\"\n : options?.multitaskStrategy,\n });\n // Start the deferred root pump *after* the dispatch HTTP\n // response lands — that's when the thread row exists server-\n // side. Doing it synchronously here would race the response\n // and the pump's `subscription.subscribe` would 404. Same\n // reason we drop the self-created flag only after dispatch:\n // future hydrates need the thread to exist before they fetch\n // state.\n //\n // Fire-and-forget: we don't want to gate Promise.race on this,\n // and `commandPromise.catch` is already handled below. A\n // dispatch failure means there's no thread to pump anyway.\n void commandPromise.then(\n () => {\n this.#startDeferredRootPump();\n this.#forgetSelfCreatedThreadId(activeThreadId);\n },\n () => {\n // Dispatch failed. Without abandoning, `#rootPumpDeferred`\n // stays armed and `selfCreatedThreadIds` still holds this\n // id — a retry submit would see `wasSelfCreated=false`\n // (currentThreadId is no longer null), `#ensureThread`\n // would early-return because `#thread != null`, and the\n // root pump would never start. Tear down so the next\n // submit re-runs `#ensureThread` from scratch.\n if (wasSelfCreated) {\n this.#abandonDeferredRootPump();\n this.#forgetSelfCreatedThreadId(activeThreadId);\n }\n }\n );\n const notifyCreated = (result: { run_id?: unknown }) => {\n if (typeof result.run_id !== \"string\") return;\n createdRunId = result.run_id;\n this.#onRunCreated(createdRunId);\n if (pendingCompletionReason != null) {\n notifyCompletion(pendingCompletionReason);\n }\n };\n const first = await Promise.race([\n terminalPromise.then((value) => ({\n type: \"terminal\" as const,\n value,\n })),\n commandPromise.then(\n (result) => ({ type: \"command\" as const, result }),\n (error) => ({ type: \"error\" as const, error })\n ),\n ]);\n if (first.type === \"error\") throw first.error;\n if (first.type === \"command\") {\n notifyCreated(first.result);\n } else {\n // Terminal landed first (very fast runs). Wait for the\n // dispatch response in the background so onCreated fires\n // and dispatch errors still surface.\n terminal = first.value;\n terminalSettled = true;\n void commandPromise.then(notifyCreated).catch((error) => {\n if (!terminalSettled) reportError(error);\n });\n }\n\n terminal ??= await terminalPromise;\n terminalSettled = true;\n settleEvent = terminal.event;\n if (terminal.event === \"failed\" && !abort.signal.aborted) {\n const runError = new Error(\n terminal.error ?? \"Run failed with no error message\"\n );\n this.#rootStore.setState((s) => ({ ...s, error: runError }));\n try {\n options?.onError?.(runError);\n } catch {\n /* caller-supplied callback errors must not crash the submit */\n }\n }\n notifyCompletion(terminalReason(terminal.event));\n } catch (error) {\n if (!abort.signal.aborted) settleEvent = \"failed\";\n reportError(error);\n } finally {\n // Always settle loading and clear our slot of the abort\n // controller. Schedule queue drain on the next macrotask so any\n // late state updates from this run finish flushing first.\n this.#rootStore.setState((s) => ({ ...s, isLoading: false }));\n if (this.#runAbort === abort) this.#runAbort = undefined;\n // Reconcile optimistic state: flip pending messages to sent/failed\n // and roll back un-echoed non-message keys. `aborted` covers a\n // rollback-resubmit or `stop()` cancelling this run.\n if (optimisticHandle != null) {\n this.#settleOptimistic(\n optimisticHandle,\n abort.signal.aborted ? \"aborted\" : (settleEvent ?? \"failed\")\n );\n }\n this.#onRunEnd();\n setTimeout(() => this.#drainQueue(), 0);\n }\n }\n\n /**\n * Surface a *resumed* run's failure the same way {@link submit} surfaces\n * a fresh run's failure — by writing it to the reactive\n * {@link RootSnapshot.error} slot.\n *\n * `respond()` / `respondAll()` dispatch their `input.respond` command on\n * the controller directly (they target a specific interrupt, so they\n * cannot go through {@link submit}, which only does `run.start`). The\n * resumed run therefore never passed through the submit lifecycle that\n * populates `rootStore.error` — only the persistent lifecycle listener\n * observed it, and that listener drives `isLoading` alone. Without this,\n * a resumed run that fails (e.g. a missing model key surfaced after the\n * user approves an interrupt) would flip `isLoading` back to `false`\n * with `error` left untouched, so `stream.error`-driven UIs (error\n * banners, API-key retry prompts) would silently miss it.\n *\n * The `dispatch` thunk is awaited, so a dispatch failure rejects the\n * caller's `respond()` *and* lands in `rootStore.error`. The resumed\n * run's terminal is watched in the **background** so the returned promise\n * still settles on dispatch — preserving the resume command's\n * resolve-on-dispatch contract (and avoiding a hang when no terminal is\n * ever emitted, e.g. in unit tests).\n *\n * Reuses the shared {@link #runAbort} slot, so `stop()`, `dispose()`, and\n * a rollback `submit()` all cancel the terminal watch (no spurious error\n * on user-initiated cancel) and treat the resumed run as the active run.\n *\n * The terminal watch uses {@link #awaitResumedRunTerminal}, which skips\n * stale `interrupted` terminals from the run being resumed (they can reach\n * the pump after `input.requested` but before `respondInput` calls\n * `#prepareForNextRun`) and only accepts a later `interrupted` once a\n * root `running` lifecycle for the resumed run has been observed.\n *\n * @param dispatch - Sends the `input.respond` command (and marks the\n * targeted interrupt resolved). Invoked after the terminal watch is\n * armed.\n */\n async dispatchResume(dispatch: () => Promise<void>): Promise<void> {\n if (this.#getDisposed()) return;\n\n // Rollback any run still tracked as active (mirrors submit()), then\n // claim the in-flight slot so stop()/dispose()/a concurrent submit\n // cancels the terminal watch armed below.\n this.#runAbort?.abort();\n const abort = new AbortController();\n this.#runAbort = abort;\n\n // Optimistically clear a stale error from a previous run, matching\n // submit()'s reset, so the resume starts from a clean error slot.\n this.#rootStore.setState((s) =>\n s.error === undefined ? s : { ...s, error: undefined }\n );\n\n const reportError = (error: unknown): void => {\n if (abort.signal.aborted) return;\n this.#rootStore.setState((s) => ({ ...s, error }));\n };\n\n // Subscribe to the resumed run's terminal *before* dispatching so a fast\n // `failed` can't race us. Unlike `#awaitNextTerminal`, the resume watcher\n // ignores stale `interrupted` events until root `running` is seen.\n // Watched in the background — we never gate the returned promise on the\n // resumed run's terminal.\n const terminalPromise = this.#awaitResumedRunTerminal(abort.signal);\n void terminalPromise.then((terminal) => {\n if (this.#runAbort === abort) this.#runAbort = undefined;\n if (terminal.event === \"failed\" && !abort.signal.aborted) {\n reportError(\n new Error(terminal.error ?? \"Run failed with no error message\")\n );\n }\n // Drain any submission enqueued while the resumed run was active.\n setTimeout(() => this.#drainQueue(), 0);\n });\n\n try {\n await dispatch();\n } catch (error) {\n // The `input.respond` send itself failed, before any run started.\n reportError(error);\n if (this.#runAbort === abort) this.#runAbort = undefined;\n throw error;\n }\n }\n\n /**\n * Abort the current run (if any) and force `isLoading=false`.\n *\n * Client-side only — server-side cancel is handled by\n * {@link StreamController.stop} before this is invoked.\n */\n async stop(): Promise<void> {\n this.abortActiveRun();\n this.#rootStore.setState((s) => ({ ...s, isLoading: false }));\n }\n\n /**\n * Abort the current run without forcing the loading flag down.\n *\n * Used by {@link StreamController.dispose}: disposal already tears\n * down the root store, so flipping `isLoading` here is unnecessary\n * and would race the dispose path.\n */\n abortActiveRun(): void {\n this.#runAbort?.abort();\n this.#runAbort = undefined;\n }\n\n /**\n * Cancel a queued submission by id.\n *\n * @param id - Client-side queue entry id to remove.\n * @returns `true` when the entry was found and dropped, `false` otherwise.\n */\n async cancelQueued(id: string): Promise<boolean> {\n const current = this.#queueStore.getSnapshot();\n const next = current.filter((entry) => entry.id !== id);\n if (next.length === current.length) return false;\n this.#queueStore.setState(() => next);\n return true;\n }\n\n /**\n * Drop every queued submission. Server-side cancel arrives with A0.3.\n */\n async clearQueue(): Promise<void> {\n this.#queueStore.setState(\n () => EMPTY_QUEUE as SubmissionQueueSnapshot<StateType>\n );\n }\n\n /**\n * Append a submission to the queue without dispatching.\n *\n * The drained submission is later run via {@link #drainQueue} after\n * the active run terminates.\n */\n #enqueueSubmission(\n input: unknown,\n options?: StreamSubmitOptions<StateType, ConfigurableType>\n ): void {\n const entry: SubmissionQueueEntry<StateType> = {\n id: uuidv7(),\n values: (input ?? undefined) as Partial<StateType> | null | undefined,\n options: options as StreamSubmitOptions<StateType> | undefined,\n createdAt: new Date(),\n };\n this.#queueStore.setState((current) => [...current, entry]);\n }\n\n /**\n * Drain the head of the queue if no run is active.\n *\n * Called from the `finally` block of `submit()` on the next\n * macrotask (so the just-finished run's state flushes first).\n * Strips the strategy off the dequeued options to prevent infinite\n * re-enqueueing.\n */\n #drainQueue(): void {\n if (this.#getDisposed()) return;\n if (this.#runAbort != null && !this.#runAbort.signal.aborted) return;\n const current = this.#queueStore.getSnapshot();\n if (current.length === 0) return;\n const [next, ...rest] = current;\n this.#queueStore.setState(() => rest);\n const nextOptions: StreamSubmitOptions<StateType, ConfigurableType> = {\n ...((next.options ?? {}) as StreamSubmitOptions<\n StateType,\n ConfigurableType\n >),\n multitaskStrategy: undefined,\n };\n void this.submit(next.values, nextOptions).catch(() => {\n /* submit() already routes errors through the per-submit onError\n * hook and the root store; swallow here so a failing drain does\n * not surface as an unhandled rejection. */\n });\n }\n}\n\n/**\n * Merge `thread_id` into a user-supplied `config.configurable` blob.\n *\n * The platform expects `config.configurable.thread_id` on every run\n * dispatch; we set it last so user-supplied values can't accidentally\n * override the active thread id (which would route the run to a\n * different thread).\n */\nfunction bindThreadConfig(\n config: unknown,\n threadId: string\n): Record<string, unknown> {\n const base =\n config != null && typeof config === \"object\"\n ? (config as Record<string, unknown>)\n : {};\n const configurable =\n base.configurable != null && typeof base.configurable === \"object\"\n ? (base.configurable as Record<string, unknown>)\n : {};\n return {\n ...base,\n configurable: {\n ...configurable,\n thread_id: threadId,\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"submit-coordinator.cjs","names":["#options","#rootStore","#queueStore","#getDisposed","#getCurrentThreadId","#setCurrentThreadId","#rememberSelfCreatedThreadId","#forgetSelfCreatedThreadId","#hydrate","#ensureThread","#startDeferredRootPump","#abandonDeferredRootPump","#waitForRootPumpReady","#awaitNextTerminal","#awaitResumedRunTerminal","#onSubmitStart","#onRunStart","#onRunCreated","#onRunCompleted","#onRunEnd","#beginOptimistic","#settleOptimistic","#runAbort","#enqueueSubmission","#drainQueue"],"sources":["../../src/stream/submit-coordinator.ts"],"sourcesContent":["/**\n * Owns the run-submission lifecycle for a single\n * {@link StreamController}.\n *\n * # What this module is\n *\n * The {@link SubmitCoordinator} is the piece of the controller that\n * dispatches runs (`submit()`), enforces multitask strategies, queues\n * deferred submissions, races dispatch against terminal lifecycle\n * events, and surfaces errors back through the per-submit `onError`\n * callback and the root snapshot.\n *\n * Conceptually a submit looks like:\n *\n * 1. Optionally rebind to a different thread (`options.threadId`).\n * 2. Mint a thread id if one isn't bound yet.\n * 3. Wait for the controller's root pump to be ready (so the\n * transport is subscribed before the run is dispatched —\n * otherwise we could miss replayed events).\n * 4. Apply the {@link StreamSubmitOptions.multitaskStrategy} to\n * decide whether to abort, enqueue, reject, or proceed.\n * 5. Race the dispatch promise (`thread.submitRun()`) against the next root\n * terminal lifecycle event.\n * 6. Settle the resulting state (loading flag, error slot) and\n * drain the next queued submission, if any.\n *\n * # Why it lives in its own class\n *\n * The submit lifecycle is the most state-heavy part of the\n * controller — six promises, an abort controller, a queue, a\n * terminal-vs-command race, and bidirectional callback wiring with\n * the controller. Splitting it out keeps `controller.ts` focused on\n * subscription / projection wiring while letting the submit logic\n * evolve independently.\n *\n * # Why we race \"command\" against \"terminal\"\n *\n * For fast runs, the server's terminal lifecycle event can arrive\n * *before* the dispatch HTTP response has resolved. Racing the two\n * lets us detect terminal early and not block waiting for a now-stale\n * dispatch response. The dispatch response is still consumed (via\n * `.then(notifyCreated).catch(reportError)`) so `onCreated` still\n * fires and dispatch errors still surface through `onError`.\n *\n * # Queue semantics (`multitaskStrategy: \"enqueue\"`)\n *\n * When a run is already in flight, an `\"enqueue\"` submit is recorded\n * into {@link queueStore} and the call returns immediately. After the\n * active run terminates, `#drainQueue` schedules the head of the\n * queue as a fresh submit on the next macrotask. Each drained\n * submission has its own `multitaskStrategy` cleared so it doesn't\n * recursively re-enqueue.\n *\n * @see StreamController - The owner; injects every collaborator dep.\n */\nimport { v7 as uuidv7 } from \"@langchain/core/utils/uuid\";\nimport type { ThreadStream } from \"../client/stream/index.js\";\nimport { StreamStore } from \"./store.js\";\nimport type { OptimisticHandle } from \"./optimistic-input.js\";\nimport type {\n RootSnapshot,\n RunExecutionReason,\n StreamControllerOptions,\n StreamSubmitOptions,\n} from \"./types.js\";\n\n/**\n * Result of awaiting the next root terminal lifecycle event. Mirrors\n * the three terminal lifecycle states the protocol surfaces, plus a\n * synthetic `\"aborted\"` for client-side cancellation.\n */\ntype TerminalResult = {\n event: \"completed\" | \"failed\" | \"interrupted\" | \"aborted\";\n error?: string;\n};\n\nfunction terminalReason(event: TerminalResult[\"event\"]): RunExecutionReason {\n if (event === \"completed\") return \"success\";\n if (event === \"failed\") return \"error\";\n if (event === \"interrupted\") return \"interrupt\";\n return \"stopped\";\n}\n\n/**\n * Queued submission entry mirrored from the server-side run queue.\n *\n * Surfaces the deferred submission to UI consumers via\n * {@link StreamController.queueStore}.\n */\nexport interface SubmissionQueueEntry<\n StateType extends object = Record<string, unknown>,\n> {\n /** Stable id minted on enqueue (uuidv7 — sortable by creation time). */\n readonly id: string;\n /** Original submit input, narrowed to the partial state shape. */\n readonly values: Partial<StateType> | null | undefined;\n /** Original submit options, minus the strategy slot which is reset on drain. */\n readonly options?: StreamSubmitOptions<StateType>;\n /** Wall-clock timestamp at enqueue. */\n readonly createdAt: Date;\n}\n\n/**\n * Read-only snapshot of the queue. The queue store hands this out\n * directly; consumers must not mutate the array.\n */\nexport type SubmissionQueueSnapshot<\n StateType extends object = Record<string, unknown>,\n> = ReadonlyArray<SubmissionQueueEntry<StateType>>;\n\n/**\n * Frozen empty queue value used as the initial / cleared snapshot.\n *\n * Reusing one frozen reference keeps store identity stable across\n * empty resets, so React's `useSyncExternalStore` doesn't think the\n * queue changed when it actually didn't.\n */\nexport const EMPTY_QUEUE: SubmissionQueueSnapshot<never> = Object.freeze([]);\n\n/**\n * Coordinates one controller's run-submission lifecycle.\n *\n * The constructor takes a bag of callbacks rather than a reference to\n * the parent {@link StreamController} on purpose:\n *\n * - It keeps the dependency surface explicit and testable — every\n * piece of controller state the submit lifecycle touches is one\n * of these closures.\n * - It avoids a cyclic dependency between controller and coordinator.\n * - Tests can construct one with stub callbacks and assert behavior\n * without mocking the entire controller.\n *\n * @typeParam StateType - Root state shape.\n * @typeParam InterruptType - Root interrupt payload shape.\n * @typeParam ConfigurableType - `config.configurable` shape accepted\n * by submit (usually `Record<string, unknown>`).\n */\nexport class SubmitCoordinator<\n StateType extends object = Record<string, unknown>,\n InterruptType = unknown,\n ConfigurableType extends object = Record<string, unknown>,\n> {\n /** Controller-level options forwarded into `submitRun` / callbacks. */\n readonly #options: StreamControllerOptions<StateType>;\n /** Root snapshot store; written for `isLoading`, `error`, `interrupts`. */\n readonly #rootStore: StreamStore<RootSnapshot<StateType, InterruptType>>;\n /** Pending submissions awaiting the active run to terminate. */\n readonly #queueStore: StreamStore<SubmissionQueueSnapshot<StateType>>;\n /** Probes the controller's `disposed` flag from deferred work. */\n readonly #getDisposed: () => boolean;\n /** Reads the controller's currently-bound thread id. */\n readonly #getCurrentThreadId: () => string | null;\n /** Updates the controller's thread id (used when minting a new id). */\n readonly #setCurrentThreadId: (threadId: string | null) => void;\n /** Records a thread id we created client-side so hydrate can skip a 404 round-trip. */\n readonly #rememberSelfCreatedThreadId: (threadId: string) => void;\n /** Drops a thread id from the self-created set once it's committed server-side. */\n readonly #forgetSelfCreatedThreadId: (threadId: string) => void;\n /** Triggers a hydrate on the controller (used by `options.threadId` rebinds). */\n readonly #hydrate: (threadId?: string | null) => Promise<void>;\n /** Lazily creates / returns the active {@link ThreadStream}. */\n readonly #ensureThread: (\n threadId: string,\n deferRootPump?: boolean\n ) => ThreadStream;\n /** Starts the previously-deferred root pump after a self-created thread commits. */\n readonly #startDeferredRootPump: () => void;\n /** Abandons a deferred root pump after a self-created dispatch fails. */\n readonly #abandonDeferredRootPump: () => void;\n /** Resolves once the controller's root subscription pump is up. */\n readonly #waitForRootPumpReady: () => Promise<void> | undefined;\n /** Resolves on the next root terminal lifecycle (or on abort). */\n readonly #awaitNextTerminal: (signal: AbortSignal) => Promise<TerminalResult>;\n /**\n * Resolves on the resumed run's terminal, skipping stale `interrupted`\n * events from the run being resumed (see {@link dispatchResume}).\n */\n readonly #awaitResumedRunTerminal: (\n signal: AbortSignal\n ) => Promise<TerminalResult>;\n /** Called once at the start of every {@link submit} invocation. */\n readonly #onSubmitStart: () => void;\n /** Marks that a local run dispatch is now active. */\n readonly #onRunStart: () => void;\n /** Records a server-accepted local run id and fires `onCreated`. */\n readonly #onRunCreated: (runId: string) => void;\n /** Fires `onCompleted` for the local run lifecycle. */\n readonly #onRunCompleted: (\n reason: RunExecutionReason,\n runId?: string\n ) => void;\n /** Marks the local run dispatch lifecycle as settled. */\n readonly #onRunEnd: () => void;\n /**\n * Apply a submit input optimistically before dispatch. Returns the\n * id-injected payload to dispatch plus a handle for terminal\n * reconciliation, or `undefined` when optimistic UI is disabled / no\n * echo applies (dispatch the raw input).\n */\n readonly #beginOptimistic: (\n input: unknown\n ) => { dispatchInput: unknown; handle: OptimisticHandle } | undefined;\n /** Reconcile optimistic state when a run terminates. */\n readonly #settleOptimistic: (\n handle: OptimisticHandle,\n event: TerminalResult[\"event\"]\n ) => void;\n\n /**\n * Active submission's abort controller. `undefined` between submits.\n *\n * Used both for `multitaskStrategy: \"rollback\"` (abort the previous\n * controller's signal) and `stop()` (abort the current one without\n * starting a new one).\n */\n #runAbort: AbortController | undefined;\n\n constructor(params: {\n options: StreamControllerOptions<StateType>;\n rootStore: StreamStore<RootSnapshot<StateType, InterruptType>>;\n queueStore: StreamStore<SubmissionQueueSnapshot<StateType>>;\n getDisposed: () => boolean;\n getCurrentThreadId: () => string | null;\n setCurrentThreadId: (threadId: string | null) => void;\n rememberSelfCreatedThreadId: (threadId: string) => void;\n forgetSelfCreatedThreadId: (threadId: string) => void;\n hydrate: (threadId?: string | null) => Promise<void>;\n ensureThread: (threadId: string, deferRootPump?: boolean) => ThreadStream;\n startDeferredRootPump: () => void;\n abandonDeferredRootPump: () => void;\n waitForRootPumpReady: () => Promise<void> | undefined;\n awaitNextTerminal: (signal: AbortSignal) => Promise<TerminalResult>;\n awaitResumedRunTerminal: (signal: AbortSignal) => Promise<TerminalResult>;\n onSubmitStart?: () => void;\n onRunStart?: () => void;\n onRunCreated?: (runId: string) => void;\n onRunCompleted?: (reason: RunExecutionReason, runId?: string) => void;\n onRunEnd?: () => void;\n beginOptimistic?: (\n input: unknown\n ) => { dispatchInput: unknown; handle: OptimisticHandle } | undefined;\n settleOptimistic?: (\n handle: OptimisticHandle,\n event: TerminalResult[\"event\"]\n ) => void;\n }) {\n this.#options = params.options;\n this.#rootStore = params.rootStore;\n this.#queueStore = params.queueStore;\n this.#getDisposed = params.getDisposed;\n this.#getCurrentThreadId = params.getCurrentThreadId;\n this.#setCurrentThreadId = params.setCurrentThreadId;\n this.#rememberSelfCreatedThreadId = params.rememberSelfCreatedThreadId;\n this.#forgetSelfCreatedThreadId = params.forgetSelfCreatedThreadId;\n this.#hydrate = params.hydrate;\n this.#ensureThread = params.ensureThread;\n this.#startDeferredRootPump = params.startDeferredRootPump;\n this.#abandonDeferredRootPump = params.abandonDeferredRootPump;\n this.#waitForRootPumpReady = params.waitForRootPumpReady;\n this.#awaitNextTerminal = params.awaitNextTerminal;\n this.#awaitResumedRunTerminal = params.awaitResumedRunTerminal;\n this.#onSubmitStart = params.onSubmitStart ?? (() => undefined);\n this.#onRunStart = params.onRunStart ?? (() => undefined);\n this.#onRunCreated = params.onRunCreated ?? (() => undefined);\n this.#onRunCompleted = params.onRunCompleted ?? (() => undefined);\n this.#onRunEnd = params.onRunEnd ?? (() => undefined);\n this.#beginOptimistic = params.beginOptimistic ?? (() => undefined);\n this.#settleOptimistic = params.settleOptimistic ?? (() => undefined);\n }\n\n /**\n * Submit input to the active thread.\n *\n * Honours {@link StreamSubmitOptions.multitaskStrategy}:\n *\n * - `\"rollback\"` (default) — aborts any in-flight run and\n * dispatches immediately.\n * - `\"reject\"` — throws synchronously when a run is\n * already in flight.\n * - `\"enqueue\"` — defers via {@link #enqueueSubmission};\n * the call returns without dispatching.\n * - `\"interrupt\"` — falls through to the default path\n *\n * Errors are routed through both the per-submit `onError` callback\n * and `rootStore.error`. Aborts (controller dispose / rollback) are\n * silently dropped.\n *\n * To resume a pending interrupt, use {@link StreamController.respond}\n * instead of `submit()`.\n *\n * @param input - Input payload for the run.\n * @param options - Per-submit options (config, metadata, callbacks,\n * strategy, etc).\n */\n async submit(\n input: unknown,\n options?: StreamSubmitOptions<StateType, ConfigurableType>\n ): Promise<void> {\n if (this.#getDisposed()) return;\n this.#onSubmitStart();\n\n // Per-submit thread override: rebind first so the rest of the\n // submit operates against the new thread.\n const overrideThreadId = options?.threadId;\n if (\n overrideThreadId !== undefined &&\n overrideThreadId !== this.#getCurrentThreadId()\n ) {\n await this.#hydrate(overrideThreadId);\n }\n\n // Self-created thread id path: mint client-side so the controller\n // (and Suspense boundaries) get a stable id even before the run\n // is dispatched.\n const wasSelfCreated = this.#getCurrentThreadId() == null;\n if (wasSelfCreated) {\n const threadId = uuidv7();\n this.#setCurrentThreadId(threadId);\n this.#rememberSelfCreatedThreadId(threadId);\n this.#options.onThreadId?.(threadId);\n this.#rootStore.setState((s) => ({\n ...s,\n threadId,\n }));\n }\n\n const currentThreadId = this.#getCurrentThreadId();\n if (currentThreadId == null) return;\n // For client-self-created threads we defer the persistent root SSE\n // pump until after `submitRun` / `respondInput` commits the thread\n // server-side. Opening the pump's `subscription.subscribe` against\n // a not-yet-existent thread row produces a `404: Thread not found`\n // protocol error that strands lifecycle / messages events for the\n // first run. The deferred path starts the pump after dispatch\n // returns (see `#startDeferredRootPump` calls below).\n const thread = this.#ensureThread(currentThreadId, wasSelfCreated);\n const activeThreadId = currentThreadId;\n\n const strategy = options?.multitaskStrategy ?? \"rollback\";\n // `wasSelfCreated` short-circuit: when this submit just minted a\n // brand-new thread id (the user clicked \"New Thread\"), the\n // strategy check shouldn't see a run on the *previous* thread as\n // a reason to enqueue. The previous run is on a thread the user\n // navigated away from; abandoning its client-side abort tracking\n // is correct (the server-side run continues independently).\n // Without this, `enqueue` would trap the new submission and\n // `submitRun` never fires for the new thread — leaving a freshly-\n // minted thread id committed to the URL but never to the server.\n const hasActiveRun =\n !wasSelfCreated &&\n this.#runAbort != null &&\n !this.#runAbort.signal.aborted;\n if (hasActiveRun && strategy === \"reject\") {\n throw new Error(\n \"submit() rejected: a run is already in flight and multitaskStrategy is 'reject'.\"\n );\n }\n if (hasActiveRun && strategy === \"enqueue\") {\n this.#enqueueSubmission(input, options);\n return;\n }\n\n // Rollback: abort the previous run before starting a new one.\n this.#runAbort?.abort();\n const abort = new AbortController();\n this.#runAbort = abort;\n\n // Claim the in-flight slot before awaiting the root pump so\n // concurrent `enqueue` submits in the same tick observe\n // `hasActiveRun` and land in {@link queueStore}.\n this.#rootStore.setState((s) => ({\n ...s,\n interrupts: [],\n interrupt: undefined,\n error: undefined,\n isLoading: true,\n }));\n\n // Declared before the try so the catch/finally can settle the\n // submit lifecycle (loading flag, abort slot, optimistic state)\n // even if optimistic preparation or the pump wait throws.\n let optimisticHandle: OptimisticHandle | undefined;\n let dispatchInput: unknown = input;\n let createdRunId: string | undefined;\n let pendingCompletionReason: RunExecutionReason | undefined;\n let completionNotified = false;\n let settleEvent: TerminalResult[\"event\"] | undefined;\n const notifyCompletion = (reason: RunExecutionReason): void => {\n if (completionNotified) return;\n if (createdRunId == null) {\n pendingCompletionReason = reason;\n return;\n }\n completionNotified = true;\n this.#onRunCompleted(reason, createdRunId);\n };\n const reportError = (error: unknown): void => {\n if (abort.signal.aborted) return;\n this.#rootStore.setState((s) => ({ ...s, error }));\n try {\n options?.onError?.(error);\n } catch {\n /* caller-supplied callback errors must not crash the submit */\n }\n };\n\n try {\n // Apply the input optimistically *before* the first await so the\n // user's message (and any merged state) paints without waiting for\n // the server round-trip. Kept as the first statement in the try so\n // the synchronous paint still precedes the first `await`, while a\n // synchronous coercion failure (e.g. a malformed message entry)\n // settles the submit lifecycle through the catch/finally below —\n // exactly like a dispatch failure — instead of wedging `isLoading`\n // / `#runAbort` and stranding later enqueue/reject submits behind a\n // phantom in-flight run. Runs only on the dispatched path — an\n // `\"enqueue\"`d submission returns above and echoes when it drains,\n // keeping one optimistic batch bound to exactly one run lifecycle.\n // `dispatchInput` carries the minted ids the server must echo for\n // reconciliation, so the run is dispatched with it (not raw input).\n const prepared = this.#beginOptimistic(input);\n if (prepared != null) {\n optimisticHandle = prepared.handle;\n dispatchInput = prepared.dispatchInput;\n }\n\n // Wait for the root subscription to be live; otherwise the\n // dispatch could resolve before we're listening for events and\n // we'd miss the terminal that ends the run.\n await this.#waitForRootPumpReady();\n\n const boundConfig = bindThreadConfig(options?.config, currentThreadId);\n // Subscribe to the next terminal *before* dispatching so a fast\n // run's terminal can't race us.\n const terminalPromise = this.#awaitNextTerminal(abort.signal);\n this.#onRunStart();\n\n let terminalSettled = false;\n let terminal: TerminalResult | undefined;\n\n const commandPromise = thread.submitRun({\n input: dispatchInput ?? null,\n config: boundConfig,\n metadata: (options?.metadata ?? undefined) as Record<string, unknown>,\n forkFrom: options?.forkFrom,\n multitaskStrategy:\n options?.multitaskStrategy === \"enqueue\"\n ? \"enqueue\"\n : options?.multitaskStrategy,\n });\n // Start the deferred root pump *after* the dispatch HTTP\n // response lands — that's when the thread row exists server-\n // side. Doing it synchronously here would race the response\n // and the pump's `subscription.subscribe` would 404. Same\n // reason we drop the self-created flag only after dispatch:\n // future hydrates need the thread to exist before they fetch\n // state.\n //\n // Fire-and-forget: we don't want to gate Promise.race on this,\n // and `commandPromise.catch` is already handled below. A\n // dispatch failure means there's no thread to pump anyway.\n void commandPromise.then(\n () => {\n this.#startDeferredRootPump();\n this.#forgetSelfCreatedThreadId(activeThreadId);\n },\n () => {\n // Dispatch failed. Without abandoning, `#rootPumpDeferred`\n // stays armed and `selfCreatedThreadIds` still holds this\n // id — a retry submit would see `wasSelfCreated=false`\n // (currentThreadId is no longer null), `#ensureThread`\n // would early-return because `#thread != null`, and the\n // root pump would never start. Tear down so the next\n // submit re-runs `#ensureThread` from scratch.\n if (wasSelfCreated) {\n this.#abandonDeferredRootPump();\n this.#forgetSelfCreatedThreadId(activeThreadId);\n }\n }\n );\n const notifyCreated = (result: { run_id?: unknown }) => {\n if (typeof result.run_id !== \"string\") return;\n createdRunId = result.run_id;\n this.#onRunCreated(createdRunId);\n if (pendingCompletionReason != null) {\n notifyCompletion(pendingCompletionReason);\n }\n };\n const first = await Promise.race([\n terminalPromise.then((value) => ({\n type: \"terminal\" as const,\n value,\n })),\n commandPromise.then(\n (result) => ({ type: \"command\" as const, result }),\n (error) => ({ type: \"error\" as const, error })\n ),\n ]);\n if (first.type === \"error\") throw first.error;\n if (first.type === \"command\") {\n notifyCreated(first.result);\n } else {\n // Terminal landed first (very fast runs). Wait for the\n // dispatch response in the background so onCreated fires\n // and dispatch errors still surface.\n terminal = first.value;\n terminalSettled = true;\n void commandPromise.then(notifyCreated).catch((error) => {\n if (!terminalSettled) reportError(error);\n });\n }\n\n terminal ??= await terminalPromise;\n terminalSettled = true;\n settleEvent = terminal.event;\n if (terminal.event === \"failed\" && !abort.signal.aborted) {\n const runError = new Error(\n terminal.error ?? \"Run failed with no error message\"\n );\n this.#rootStore.setState((s) => ({ ...s, error: runError }));\n try {\n options?.onError?.(runError);\n } catch {\n /* caller-supplied callback errors must not crash the submit */\n }\n }\n notifyCompletion(terminalReason(terminal.event));\n } catch (error) {\n if (!abort.signal.aborted) settleEvent = \"failed\";\n reportError(error);\n } finally {\n // Always settle loading and clear our slot of the abort\n // controller. Schedule queue drain on the next macrotask so any\n // late state updates from this run finish flushing first.\n this.#rootStore.setState((s) => ({ ...s, isLoading: false }));\n if (this.#runAbort === abort) this.#runAbort = undefined;\n // Reconcile optimistic state: flip pending messages to sent/failed\n // and roll back un-echoed non-message keys. `aborted` covers a\n // rollback-resubmit or `stop()` cancelling this run.\n if (optimisticHandle != null) {\n this.#settleOptimistic(\n optimisticHandle,\n abort.signal.aborted ? \"aborted\" : (settleEvent ?? \"failed\")\n );\n }\n this.#onRunEnd();\n setTimeout(() => this.#drainQueue(), 0);\n }\n }\n\n /**\n * Surface a *resumed* run's failure the same way {@link submit} surfaces\n * a fresh run's failure — by writing it to the reactive\n * {@link RootSnapshot.error} slot.\n *\n * `respond()` / `respondAll()` dispatch their `input.respond` command on\n * the controller directly (they target a specific interrupt, so they\n * cannot go through {@link submit}, which only does `run.start`). The\n * resumed run therefore never passed through the submit lifecycle that\n * populates `rootStore.error` — only the persistent lifecycle listener\n * observed it, and that listener drives `isLoading` alone. Without this,\n * a resumed run that fails (e.g. a missing model key surfaced after the\n * user approves an interrupt) would flip `isLoading` back to `false`\n * with `error` left untouched, so `stream.error`-driven UIs (error\n * banners, API-key retry prompts) would silently miss it.\n *\n * The `dispatch` thunk is awaited, so a dispatch failure rejects the\n * caller's `respond()` *and* lands in `rootStore.error`. The resumed\n * run's terminal is watched in the **background** so the returned promise\n * still settles on dispatch — preserving the resume command's\n * resolve-on-dispatch contract (and avoiding a hang when no terminal is\n * ever emitted, e.g. in unit tests).\n *\n * Reuses the shared {@link #runAbort} slot, so `stop()`, `dispose()`, and\n * a rollback `submit()` all cancel the terminal watch (no spurious error\n * on user-initiated cancel) and treat the resumed run as the active run.\n *\n * The terminal watch uses {@link #awaitResumedRunTerminal}, which skips\n * stale `interrupted` terminals from the run being resumed (they can reach\n * the pump after `input.requested` but before `respondInput` calls\n * `#prepareForNextRun`) and only accepts a later `interrupted` once a\n * root `running` lifecycle for the resumed run has been observed.\n *\n * @param dispatch - Sends the `input.respond` command (and marks the\n * targeted interrupt resolved). Invoked after the terminal watch is\n * armed.\n */\n async dispatchResume(dispatch: () => Promise<void>): Promise<void> {\n if (this.#getDisposed()) return;\n\n // Rollback any run still tracked as active (mirrors submit()), then\n // claim the in-flight slot so stop()/dispose()/a concurrent submit\n // cancels the terminal watch armed below.\n this.#runAbort?.abort();\n const abort = new AbortController();\n this.#runAbort = abort;\n\n // Optimistically clear a stale error from a previous run, matching\n // submit()'s reset, so the resume starts from a clean error slot.\n this.#rootStore.setState((s) =>\n s.error === undefined ? s : { ...s, error: undefined }\n );\n\n const reportError = (error: unknown): void => {\n if (abort.signal.aborted) return;\n this.#rootStore.setState((s) => ({ ...s, error }));\n };\n\n // Subscribe to the resumed run's terminal *before* dispatching so a fast\n // `failed` can't race us. Unlike `#awaitNextTerminal`, the resume watcher\n // ignores stale `interrupted` events until root `running` is seen.\n // Watched in the background — we never gate the returned promise on the\n // resumed run's terminal.\n const terminalPromise = this.#awaitResumedRunTerminal(abort.signal);\n void terminalPromise.then((terminal) => {\n if (this.#runAbort === abort) this.#runAbort = undefined;\n if (terminal.event === \"failed\" && !abort.signal.aborted) {\n reportError(\n new Error(terminal.error ?? \"Run failed with no error message\")\n );\n }\n // Drain any submission enqueued while the resumed run was active.\n setTimeout(() => this.#drainQueue(), 0);\n });\n\n try {\n await dispatch();\n } catch (error) {\n // The `input.respond` send itself failed, before any run started.\n reportError(error);\n if (this.#runAbort === abort) this.#runAbort = undefined;\n throw error;\n }\n }\n\n /**\n * Abort the current run (if any) and force `isLoading=false`.\n *\n * Client-side only — server-side cancel is handled by\n * {@link StreamController.stop} before this is invoked.\n */\n async stop(): Promise<void> {\n this.abortActiveRun();\n this.#rootStore.setState((s) => ({ ...s, isLoading: false }));\n }\n\n /**\n * Abort the current run without forcing the loading flag down.\n *\n * Used by {@link StreamController.dispose}: disposal already tears\n * down the root store, so flipping `isLoading` here is unnecessary\n * and would race the dispose path.\n */\n abortActiveRun(): void {\n this.#runAbort?.abort();\n this.#runAbort = undefined;\n }\n\n /**\n * Cancel a queued submission by id.\n *\n * @param id - Client-side queue entry id to remove.\n * @returns `true` when the entry was found and dropped, `false` otherwise.\n */\n async cancelQueued(id: string): Promise<boolean> {\n const current = this.#queueStore.getSnapshot();\n const next = current.filter((entry) => entry.id !== id);\n if (next.length === current.length) return false;\n this.#queueStore.setState(() => next);\n return true;\n }\n\n /**\n * Drop every queued submission. Server-side cancel arrives with A0.3.\n */\n async clearQueue(): Promise<void> {\n this.#queueStore.setState(\n () => EMPTY_QUEUE as SubmissionQueueSnapshot<StateType>\n );\n }\n\n /**\n * Append a submission to the queue without dispatching.\n *\n * The drained submission is later run via {@link #drainQueue} after\n * the active run terminates.\n */\n #enqueueSubmission(\n input: unknown,\n options?: StreamSubmitOptions<StateType, ConfigurableType>\n ): void {\n const entry: SubmissionQueueEntry<StateType> = {\n id: uuidv7(),\n values: (input ?? undefined) as Partial<StateType> | null | undefined,\n options: options as StreamSubmitOptions<StateType> | undefined,\n createdAt: new Date(),\n };\n this.#queueStore.setState((current) => [...current, entry]);\n }\n\n /**\n * Drain the head of the queue if no run is active.\n *\n * Called from the `finally` block of `submit()` on the next\n * macrotask (so the just-finished run's state flushes first).\n * Strips the strategy off the dequeued options to prevent infinite\n * re-enqueueing.\n */\n #drainQueue(): void {\n if (this.#getDisposed()) return;\n if (this.#runAbort != null && !this.#runAbort.signal.aborted) return;\n const current = this.#queueStore.getSnapshot();\n if (current.length === 0) return;\n const [next, ...rest] = current;\n this.#queueStore.setState(() => rest);\n const nextOptions: StreamSubmitOptions<StateType, ConfigurableType> = {\n ...((next.options ?? {}) as StreamSubmitOptions<\n StateType,\n ConfigurableType\n >),\n multitaskStrategy: undefined,\n };\n void this.submit(next.values, nextOptions).catch(() => {\n /* submit() already routes errors through the per-submit onError\n * hook and the root store; swallow here so a failing drain does\n * not surface as an unhandled rejection. */\n });\n }\n}\n\n/**\n * Merge `thread_id` into a user-supplied `config.configurable` blob.\n *\n * The platform expects `config.configurable.thread_id` on every run\n * dispatch; we set it last so user-supplied values can't accidentally\n * override the active thread id (which would route the run to a\n * different thread).\n */\nfunction bindThreadConfig(\n config: unknown,\n threadId: string\n): Record<string, unknown> {\n const base =\n config != null && typeof config === \"object\"\n ? (config as Record<string, unknown>)\n : {};\n const configurable =\n base.configurable != null && typeof base.configurable === \"object\"\n ? (base.configurable as Record<string, unknown>)\n : {};\n return {\n ...base,\n configurable: {\n ...configurable,\n thread_id: threadId,\n },\n };\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { v7 } from "uuid";
1
+ import { v7 } from "@langchain/core/utils/uuid";
2
2
  //#region src/stream/submit-coordinator.ts
3
3
  /**
4
4
  * Owns the run-submission lifecycle for a single