@langchain/langgraph-sdk 1.9.9 → 1.9.10

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 (45) hide show
  1. package/dist/client/stream/index.cjs +37 -5
  2. package/dist/client/stream/index.cjs.map +1 -1
  3. package/dist/client/stream/index.d.cts +7 -14
  4. package/dist/client/stream/index.d.cts.map +1 -1
  5. package/dist/client/stream/index.d.ts +7 -14
  6. package/dist/client/stream/index.d.ts.map +1 -1
  7. package/dist/client/stream/index.js +37 -5
  8. package/dist/client/stream/index.js.map +1 -1
  9. package/dist/client/stream/types.d.cts +8 -3
  10. package/dist/client/stream/types.d.cts.map +1 -1
  11. package/dist/client/stream/types.d.ts +8 -3
  12. package/dist/client/stream/types.d.ts.map +1 -1
  13. package/dist/headless-tools.cjs +0 -24
  14. package/dist/headless-tools.cjs.map +1 -1
  15. package/dist/headless-tools.d.cts.map +1 -1
  16. package/dist/headless-tools.d.ts.map +1 -1
  17. package/dist/headless-tools.js +1 -24
  18. package/dist/headless-tools.js.map +1 -1
  19. package/dist/stream/controller.cjs +156 -19
  20. package/dist/stream/controller.cjs.map +1 -1
  21. package/dist/stream/controller.d.cts +113 -10
  22. package/dist/stream/controller.d.cts.map +1 -1
  23. package/dist/stream/controller.d.ts +113 -10
  24. package/dist/stream/controller.d.ts.map +1 -1
  25. package/dist/stream/controller.js +157 -20
  26. package/dist/stream/controller.js.map +1 -1
  27. package/dist/stream/index.d.cts +2 -2
  28. package/dist/stream/index.d.ts +2 -2
  29. package/dist/stream/message-metadata-tracker.cjs +1 -1
  30. package/dist/stream/message-metadata-tracker.cjs.map +1 -1
  31. package/dist/stream/message-metadata-tracker.d.cts +1 -1
  32. package/dist/stream/message-metadata-tracker.d.ts +1 -1
  33. package/dist/stream/message-metadata-tracker.js +1 -1
  34. package/dist/stream/message-metadata-tracker.js.map +1 -1
  35. package/dist/stream/submit-coordinator.cjs +45 -79
  36. package/dist/stream/submit-coordinator.cjs.map +1 -1
  37. package/dist/stream/submit-coordinator.d.cts.map +1 -1
  38. package/dist/stream/submit-coordinator.d.ts.map +1 -1
  39. package/dist/stream/submit-coordinator.js +45 -79
  40. package/dist/stream/submit-coordinator.js.map +1 -1
  41. package/dist/stream/types.d.cts +52 -30
  42. package/dist/stream/types.d.cts.map +1 -1
  43. package/dist/stream/types.d.ts +52 -30
  44. package/dist/stream/types.d.ts.map +1 -1
  45. package/package.json +2 -2
@@ -22,8 +22,7 @@ let uuid = require("uuid");
22
22
  * otherwise we could miss replayed events).
23
23
  * 4. Apply the {@link StreamSubmitOptions.multitaskStrategy} to
24
24
  * decide whether to abort, enqueue, reject, or proceed.
25
- * 5. Race the dispatch promise (`thread.submitRun()` or
26
- * `thread.submitRun()` for resumes) against the next root
25
+ * 5. Race the dispatch promise (`thread.submitRun()`) against the next root
27
26
  * terminal lifecycle event.
28
27
  * 6. Settle the resulting state (loading flag, error slot) and
29
28
  * drain the next queued submission, if any.
@@ -118,10 +117,6 @@ var SubmitCoordinator = class {
118
117
  #waitForRootPumpReady;
119
118
  /** Resolves on the next root terminal lifecycle (or on abort). */
120
119
  #awaitNextTerminal;
121
- /** Builds interrupt-id keyed `run.start` input from `command.resume`. */
122
- #buildResumeRunInput;
123
- /** Marks an interrupt id as resolved so it isn't re-targeted. */
124
- #markInterruptResolved;
125
120
  /** Called once at the start of every {@link submit} invocation. */
126
121
  #onSubmitStart;
127
122
  /** Marks that a local run dispatch is now active. */
@@ -155,8 +150,6 @@ var SubmitCoordinator = class {
155
150
  this.#abandonDeferredRootPump = params.abandonDeferredRootPump;
156
151
  this.#waitForRootPumpReady = params.waitForRootPumpReady;
157
152
  this.#awaitNextTerminal = params.awaitNextTerminal;
158
- this.#buildResumeRunInput = params.buildResumeRunInput;
159
- this.#markInterruptResolved = params.markInterruptResolved;
160
153
  this.#onSubmitStart = params.onSubmitStart ?? (() => void 0);
161
154
  this.#onRunStart = params.onRunStart ?? (() => void 0);
162
155
  this.#onRunCreated = params.onRunCreated ?? (() => void 0);
@@ -164,7 +157,7 @@ var SubmitCoordinator = class {
164
157
  this.#onRunEnd = params.onRunEnd ?? (() => void 0);
165
158
  }
166
159
  /**
167
- * Submit input or a resume command to the active thread.
160
+ * Submit input to the active thread.
168
161
  *
169
162
  * Honours {@link StreamSubmitOptions.multitaskStrategy}:
170
163
  *
@@ -180,8 +173,10 @@ var SubmitCoordinator = class {
180
173
  * and `rootStore.error`. Aborts (controller dispose / rollback) are
181
174
  * silently dropped.
182
175
  *
183
- * @param input - Input payload, or `null`/`undefined` for no input
184
- * (typical for resume commands).
176
+ * To resume a pending interrupt, use {@link StreamController.respond}
177
+ * instead of `submit()`.
178
+ *
179
+ * @param input - Input payload for the run.
185
180
  * @param options - Per-submit options (config, metadata, callbacks,
186
181
  * strategy, etc).
187
182
  */
@@ -216,8 +211,6 @@ var SubmitCoordinator = class {
216
211
  this.#runAbort?.abort();
217
212
  const abort = new AbortController();
218
213
  this.#runAbort = abort;
219
- const resumeCommand = options?.command?.resume;
220
- const isResume = resumeCommand !== void 0;
221
214
  this.#rootStore.setState((s) => ({
222
215
  ...s,
223
216
  interrupts: [],
@@ -234,7 +227,7 @@ var SubmitCoordinator = class {
234
227
  let completionNotified = false;
235
228
  const notifyCompletion = (reason) => {
236
229
  if (completionNotified) return;
237
- if (!isResume && createdRunId == null) {
230
+ if (createdRunId == null) {
238
231
  pendingCompletionReason = reason;
239
232
  return;
240
233
  }
@@ -253,73 +246,46 @@ var SubmitCoordinator = class {
253
246
  };
254
247
  try {
255
248
  let terminal;
256
- if (isResume) {
257
- const resumeInput = this.#buildResumeRunInput(resumeCommand);
258
- if (resumeInput == null) throw new Error("submit({ command: { resume } }) called but no pending protocol interrupt is available.");
259
- const commandPromise = thread.submitRun({
260
- input: resumeInput,
261
- config: boundConfig,
262
- metadata: options?.metadata ?? void 0
263
- });
264
- commandPromise.then(() => this.#startDeferredRootPump(), () => {});
265
- for (const interruptId of Object.keys(resumeInput)) this.#markInterruptResolved(interruptId);
266
- const first = await Promise.race([terminalPromise.then((value) => ({
267
- type: "terminal",
268
- value
269
- })), commandPromise.then(() => ({ type: "command" }), (error) => ({
270
- type: "error",
271
- error
272
- }))]);
273
- if (first.type === "error") throw first.error;
274
- if (first.type === "terminal") {
275
- terminal = first.value;
276
- terminalSettled = true;
277
- commandPromise.catch((error) => {
278
- if (!terminalSettled) reportError(error);
279
- });
280
- }
281
- } else {
282
- const commandPromise = thread.submitRun({
283
- input: input ?? null,
284
- config: boundConfig,
285
- metadata: options?.metadata ?? void 0,
286
- forkFrom: options?.forkFrom,
287
- multitaskStrategy: options?.multitaskStrategy === "enqueue" ? "enqueue" : options?.multitaskStrategy
288
- });
289
- commandPromise.then(() => {
290
- this.#startDeferredRootPump();
249
+ const commandPromise = thread.submitRun({
250
+ input: input ?? null,
251
+ config: boundConfig,
252
+ metadata: options?.metadata ?? void 0,
253
+ forkFrom: options?.forkFrom,
254
+ multitaskStrategy: options?.multitaskStrategy === "enqueue" ? "enqueue" : options?.multitaskStrategy
255
+ });
256
+ commandPromise.then(() => {
257
+ this.#startDeferredRootPump();
258
+ this.#forgetSelfCreatedThreadId(activeThreadId);
259
+ }, () => {
260
+ if (wasSelfCreated) {
261
+ this.#abandonDeferredRootPump();
291
262
  this.#forgetSelfCreatedThreadId(activeThreadId);
292
- }, () => {
293
- if (wasSelfCreated) {
294
- this.#abandonDeferredRootPump();
295
- this.#forgetSelfCreatedThreadId(activeThreadId);
296
- }
297
- });
298
- const notifyCreated = (result) => {
299
- if (typeof result.run_id !== "string") return;
300
- createdRunId = result.run_id;
301
- this.#onRunCreated(createdRunId);
302
- if (pendingCompletionReason != null) notifyCompletion(pendingCompletionReason);
303
- };
304
- const first = await Promise.race([terminalPromise.then((value) => ({
305
- type: "terminal",
306
- value
307
- })), commandPromise.then((result) => ({
308
- type: "command",
309
- result
310
- }), (error) => ({
311
- type: "error",
312
- error
313
- }))]);
314
- if (first.type === "error") throw first.error;
315
- if (first.type === "command") notifyCreated(first.result);
316
- else {
317
- terminal = first.value;
318
- terminalSettled = true;
319
- commandPromise.then(notifyCreated).catch((error) => {
320
- if (!terminalSettled) reportError(error);
321
- });
322
263
  }
264
+ });
265
+ const notifyCreated = (result) => {
266
+ if (typeof result.run_id !== "string") return;
267
+ createdRunId = result.run_id;
268
+ this.#onRunCreated(createdRunId);
269
+ if (pendingCompletionReason != null) notifyCompletion(pendingCompletionReason);
270
+ };
271
+ const first = await Promise.race([terminalPromise.then((value) => ({
272
+ type: "terminal",
273
+ value
274
+ })), commandPromise.then((result) => ({
275
+ type: "command",
276
+ result
277
+ }), (error) => ({
278
+ type: "error",
279
+ error
280
+ }))]);
281
+ if (first.type === "error") throw first.error;
282
+ if (first.type === "command") notifyCreated(first.result);
283
+ else {
284
+ terminal = first.value;
285
+ terminalSettled = true;
286
+ commandPromise.then(notifyCreated).catch((error) => {
287
+ if (!terminalSettled) reportError(error);
288
+ });
323
289
  }
324
290
  terminal ??= await terminalPromise;
325
291
  terminalSettled = true;
@@ -1 +1 @@
1
- {"version":3,"file":"submit-coordinator.cjs","names":["#options","#rootStore","#queueStore","#getDisposed","#getCurrentThreadId","#setCurrentThreadId","#rememberSelfCreatedThreadId","#forgetSelfCreatedThreadId","#hydrate","#ensureThread","#startDeferredRootPump","#abandonDeferredRootPump","#waitForRootPumpReady","#awaitNextTerminal","#buildResumeRunInput","#markInterruptResolved","#onSubmitStart","#onRunStart","#onRunCreated","#onRunCompleted","#onRunEnd","#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()` or\n * `thread.submitRun()` for resumes) 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 {\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 /** Builds interrupt-id keyed `run.start` input from `command.resume`. */\n readonly #buildResumeRunInput: (\n resume: unknown\n ) => Record<string, unknown> | null;\n /** Marks an interrupt id as resolved so it isn't re-targeted. */\n readonly #markInterruptResolved: (interruptId: string) => void;\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 /**\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 buildResumeRunInput: (resume: unknown) => Record<string, unknown> | null;\n markInterruptResolved: (interruptId: string) => void;\n onSubmitStart?: () => void;\n onRunStart?: () => void;\n onRunCreated?: (runId: string) => void;\n onRunCompleted?: (reason: RunExecutionReason, runId?: string) => void;\n onRunEnd?: () => 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.#buildResumeRunInput = params.buildResumeRunInput;\n this.#markInterruptResolved = params.markInterruptResolved;\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 }\n\n /**\n * Submit input or a resume command 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 * @param input - Input payload, or `null`/`undefined` for no input\n * (typical for resume commands).\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 // 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 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 const resumeCommand = options?.command?.resume;\n const isResume = resumeCommand !== undefined;\n\n // Optimistically clear interrupts/error and flip loading. The\n // root pump's lifecycle listener will re-flip these as the run\n // terminates.\n this.#rootStore.setState((s) => ({\n ...s,\n interrupts: [],\n interrupt: undefined,\n error: undefined,\n isLoading: true,\n }));\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 createdRunId: string | undefined;\n let pendingCompletionReason: RunExecutionReason | undefined;\n let completionNotified = false;\n const notifyCompletion = (reason: RunExecutionReason): void => {\n if (completionNotified) return;\n if (!isResume && 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 let terminal: TerminalResult | undefined;\n\n if (isResume) {\n const resumeInput = this.#buildResumeRunInput(resumeCommand);\n if (resumeInput == null) {\n throw new Error(\n \"submit({ command: { resume } }) called but no pending protocol interrupt is available.\"\n );\n }\n const commandPromise = thread.submitRun({\n input: resumeInput,\n config: boundConfig,\n metadata: (options?.metadata ?? undefined) as Record<string, unknown>,\n });\n // Defer the pump start until the dispatch HTTP response\n // lands — see the analogous block in the non-resume path\n // below for the rationale (thread row not committed\n // synchronously). For a resume the thread exists already\n // (it must, since `buildResumeRunInput()` was non-null),\n // so `#startDeferredRootPump` is typically a no-op here, but\n // we keep the same shape to avoid a future regression.\n //\n // Asymmetry with the non-resume path: we don't call\n // `#forgetSelfCreatedThreadId` because a resume implies the\n // thread already committed, so the self-created marker was\n // already cleared on the original submit. Same reason we\n // don't call `#abandonDeferredRootPump` on failure here.\n void commandPromise.then(\n () => this.#startDeferredRootPump(),\n () => {}\n );\n // Mark resolved synchronously: even if the response races and\n // the command settles after the terminal, we don't want to\n // re-target these interrupts on the next submit.\n for (const interruptId of Object.keys(resumeInput)) {\n this.#markInterruptResolved(interruptId);\n }\n const first = await Promise.race([\n terminalPromise.then((value) => ({\n type: \"terminal\" as const,\n value,\n })),\n commandPromise.then(\n () => ({ type: \"command\" as const }),\n (error) => ({ type: \"error\" as const, error })\n ),\n ]);\n if (first.type === \"error\") throw first.error;\n if (first.type === \"terminal\") {\n terminal = first.value;\n terminalSettled = true;\n // Stale command response — surface as error only if it\n // arrives with a real failure (not just our own abort).\n void commandPromise.catch((error) => {\n if (!terminalSettled) reportError(error);\n });\n }\n } else {\n const commandPromise = thread.submitRun({\n input: input ?? 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\n terminal ??= await terminalPromise;\n terminalSettled = true;\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 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 this.#onRunEnd();\n setTimeout(() => this.#drainQueue(), 0);\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","#onSubmitStart","#onRunStart","#onRunCreated","#onRunCompleted","#onRunEnd","#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 {\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 /** 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 /**\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 onSubmitStart?: () => void;\n onRunStart?: () => void;\n onRunCreated?: (runId: string) => void;\n onRunCompleted?: (reason: RunExecutionReason, runId?: string) => void;\n onRunEnd?: () => 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.#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 }\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 // 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 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 // Optimistically clear interrupts/error and flip loading. The\n // root pump's lifecycle listener will re-flip these as the run\n // terminates.\n this.#rootStore.setState((s) => ({\n ...s,\n interrupts: [],\n interrupt: undefined,\n error: undefined,\n isLoading: true,\n }));\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 createdRunId: string | undefined;\n let pendingCompletionReason: RunExecutionReason | undefined;\n let completionNotified = false;\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 let terminal: TerminalResult | undefined;\n\n const commandPromise = thread.submitRun({\n input: input ?? 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 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 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 this.#onRunEnd();\n setTimeout(() => this.#drainQueue(), 0);\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 +1 @@
1
- {"version":3,"file":"submit-coordinator.d.cts","names":[],"sources":["../../src/stream/submit-coordinator.ts"],"mappings":";;;;;;;;;UAyFiB,oBAAA,4BACY,MAAA;EAKF;EAAA,SAFhB,EAAA;EAIU;EAAA,SAFV,MAAA,EAAQ,OAAA,CAAQ,SAAA;EAIhB;EAAA,SAFA,OAAA,GAAU,mBAAA,CAAoB,SAAA;EAEf;EAAA,SAAf,SAAA,EAAW,IAAA;AAAA;;;;;KAOV,uBAAA,4BACiB,MAAA,qBACzB,aAAA,CAAc,oBAAA,CAAqB,SAAA"}
1
+ {"version":3,"file":"submit-coordinator.d.cts","names":[],"sources":["../../src/stream/submit-coordinator.ts"],"mappings":";;;;;;;;;UAwFiB,oBAAA,4BACY,MAAA;EAKF;EAAA,SAFhB,EAAA;EAIU;EAAA,SAFV,MAAA,EAAQ,OAAA,CAAQ,SAAA;EAIhB;EAAA,SAFA,OAAA,GAAU,mBAAA,CAAoB,SAAA;EAEf;EAAA,SAAf,SAAA,EAAW,IAAA;AAAA;;;;;KAOV,uBAAA,4BACiB,MAAA,qBACzB,aAAA,CAAc,oBAAA,CAAqB,SAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"submit-coordinator.d.ts","names":[],"sources":["../../src/stream/submit-coordinator.ts"],"mappings":";;;;;;;;;UAyFiB,oBAAA,4BACY,MAAA;EAKV;EAAA,SAFR,EAAA;EAIA;EAAA,SAFA,MAAA,EAAQ,OAAA,CAAQ,SAAA;EAEc;EAAA,SAA9B,OAAA,GAAU,mBAAA,CAAoB,SAAA;EAEnB;EAAA,SAAX,SAAA,EAAW,IAAA;AAAA;AAOtB;;;;AAAA,KAAY,uBAAA,4BACiB,MAAA,qBACzB,aAAA,CAAc,oBAAA,CAAqB,SAAA"}
1
+ {"version":3,"file":"submit-coordinator.d.ts","names":[],"sources":["../../src/stream/submit-coordinator.ts"],"mappings":";;;;;;;;;UAwFiB,oBAAA,4BACY,MAAA;EAKV;EAAA,SAFR,EAAA;EAIA;EAAA,SAFA,MAAA,EAAQ,OAAA,CAAQ,SAAA;EAEc;EAAA,SAA9B,OAAA,GAAU,mBAAA,CAAoB,SAAA;EAEnB;EAAA,SAAX,SAAA,EAAW,IAAA;AAAA;AAOtB;;;;AAAA,KAAY,uBAAA,4BACiB,MAAA,qBACzB,aAAA,CAAc,oBAAA,CAAqB,SAAA"}
@@ -21,8 +21,7 @@ import { v7 } from "uuid";
21
21
  * otherwise we could miss replayed events).
22
22
  * 4. Apply the {@link StreamSubmitOptions.multitaskStrategy} to
23
23
  * decide whether to abort, enqueue, reject, or proceed.
24
- * 5. Race the dispatch promise (`thread.submitRun()` or
25
- * `thread.submitRun()` for resumes) against the next root
24
+ * 5. Race the dispatch promise (`thread.submitRun()`) against the next root
26
25
  * terminal lifecycle event.
27
26
  * 6. Settle the resulting state (loading flag, error slot) and
28
27
  * drain the next queued submission, if any.
@@ -117,10 +116,6 @@ var SubmitCoordinator = class {
117
116
  #waitForRootPumpReady;
118
117
  /** Resolves on the next root terminal lifecycle (or on abort). */
119
118
  #awaitNextTerminal;
120
- /** Builds interrupt-id keyed `run.start` input from `command.resume`. */
121
- #buildResumeRunInput;
122
- /** Marks an interrupt id as resolved so it isn't re-targeted. */
123
- #markInterruptResolved;
124
119
  /** Called once at the start of every {@link submit} invocation. */
125
120
  #onSubmitStart;
126
121
  /** Marks that a local run dispatch is now active. */
@@ -154,8 +149,6 @@ var SubmitCoordinator = class {
154
149
  this.#abandonDeferredRootPump = params.abandonDeferredRootPump;
155
150
  this.#waitForRootPumpReady = params.waitForRootPumpReady;
156
151
  this.#awaitNextTerminal = params.awaitNextTerminal;
157
- this.#buildResumeRunInput = params.buildResumeRunInput;
158
- this.#markInterruptResolved = params.markInterruptResolved;
159
152
  this.#onSubmitStart = params.onSubmitStart ?? (() => void 0);
160
153
  this.#onRunStart = params.onRunStart ?? (() => void 0);
161
154
  this.#onRunCreated = params.onRunCreated ?? (() => void 0);
@@ -163,7 +156,7 @@ var SubmitCoordinator = class {
163
156
  this.#onRunEnd = params.onRunEnd ?? (() => void 0);
164
157
  }
165
158
  /**
166
- * Submit input or a resume command to the active thread.
159
+ * Submit input to the active thread.
167
160
  *
168
161
  * Honours {@link StreamSubmitOptions.multitaskStrategy}:
169
162
  *
@@ -179,8 +172,10 @@ var SubmitCoordinator = class {
179
172
  * and `rootStore.error`. Aborts (controller dispose / rollback) are
180
173
  * silently dropped.
181
174
  *
182
- * @param input - Input payload, or `null`/`undefined` for no input
183
- * (typical for resume commands).
175
+ * To resume a pending interrupt, use {@link StreamController.respond}
176
+ * instead of `submit()`.
177
+ *
178
+ * @param input - Input payload for the run.
184
179
  * @param options - Per-submit options (config, metadata, callbacks,
185
180
  * strategy, etc).
186
181
  */
@@ -215,8 +210,6 @@ var SubmitCoordinator = class {
215
210
  this.#runAbort?.abort();
216
211
  const abort = new AbortController();
217
212
  this.#runAbort = abort;
218
- const resumeCommand = options?.command?.resume;
219
- const isResume = resumeCommand !== void 0;
220
213
  this.#rootStore.setState((s) => ({
221
214
  ...s,
222
215
  interrupts: [],
@@ -233,7 +226,7 @@ var SubmitCoordinator = class {
233
226
  let completionNotified = false;
234
227
  const notifyCompletion = (reason) => {
235
228
  if (completionNotified) return;
236
- if (!isResume && createdRunId == null) {
229
+ if (createdRunId == null) {
237
230
  pendingCompletionReason = reason;
238
231
  return;
239
232
  }
@@ -252,73 +245,46 @@ var SubmitCoordinator = class {
252
245
  };
253
246
  try {
254
247
  let terminal;
255
- if (isResume) {
256
- const resumeInput = this.#buildResumeRunInput(resumeCommand);
257
- if (resumeInput == null) throw new Error("submit({ command: { resume } }) called but no pending protocol interrupt is available.");
258
- const commandPromise = thread.submitRun({
259
- input: resumeInput,
260
- config: boundConfig,
261
- metadata: options?.metadata ?? void 0
262
- });
263
- commandPromise.then(() => this.#startDeferredRootPump(), () => {});
264
- for (const interruptId of Object.keys(resumeInput)) this.#markInterruptResolved(interruptId);
265
- const first = await Promise.race([terminalPromise.then((value) => ({
266
- type: "terminal",
267
- value
268
- })), commandPromise.then(() => ({ type: "command" }), (error) => ({
269
- type: "error",
270
- error
271
- }))]);
272
- if (first.type === "error") throw first.error;
273
- if (first.type === "terminal") {
274
- terminal = first.value;
275
- terminalSettled = true;
276
- commandPromise.catch((error) => {
277
- if (!terminalSettled) reportError(error);
278
- });
279
- }
280
- } else {
281
- const commandPromise = thread.submitRun({
282
- input: input ?? null,
283
- config: boundConfig,
284
- metadata: options?.metadata ?? void 0,
285
- forkFrom: options?.forkFrom,
286
- multitaskStrategy: options?.multitaskStrategy === "enqueue" ? "enqueue" : options?.multitaskStrategy
287
- });
288
- commandPromise.then(() => {
289
- this.#startDeferredRootPump();
248
+ const commandPromise = thread.submitRun({
249
+ input: input ?? null,
250
+ config: boundConfig,
251
+ metadata: options?.metadata ?? void 0,
252
+ forkFrom: options?.forkFrom,
253
+ multitaskStrategy: options?.multitaskStrategy === "enqueue" ? "enqueue" : options?.multitaskStrategy
254
+ });
255
+ commandPromise.then(() => {
256
+ this.#startDeferredRootPump();
257
+ this.#forgetSelfCreatedThreadId(activeThreadId);
258
+ }, () => {
259
+ if (wasSelfCreated) {
260
+ this.#abandonDeferredRootPump();
290
261
  this.#forgetSelfCreatedThreadId(activeThreadId);
291
- }, () => {
292
- if (wasSelfCreated) {
293
- this.#abandonDeferredRootPump();
294
- this.#forgetSelfCreatedThreadId(activeThreadId);
295
- }
296
- });
297
- const notifyCreated = (result) => {
298
- if (typeof result.run_id !== "string") return;
299
- createdRunId = result.run_id;
300
- this.#onRunCreated(createdRunId);
301
- if (pendingCompletionReason != null) notifyCompletion(pendingCompletionReason);
302
- };
303
- const first = await Promise.race([terminalPromise.then((value) => ({
304
- type: "terminal",
305
- value
306
- })), commandPromise.then((result) => ({
307
- type: "command",
308
- result
309
- }), (error) => ({
310
- type: "error",
311
- error
312
- }))]);
313
- if (first.type === "error") throw first.error;
314
- if (first.type === "command") notifyCreated(first.result);
315
- else {
316
- terminal = first.value;
317
- terminalSettled = true;
318
- commandPromise.then(notifyCreated).catch((error) => {
319
- if (!terminalSettled) reportError(error);
320
- });
321
262
  }
263
+ });
264
+ const notifyCreated = (result) => {
265
+ if (typeof result.run_id !== "string") return;
266
+ createdRunId = result.run_id;
267
+ this.#onRunCreated(createdRunId);
268
+ if (pendingCompletionReason != null) notifyCompletion(pendingCompletionReason);
269
+ };
270
+ const first = await Promise.race([terminalPromise.then((value) => ({
271
+ type: "terminal",
272
+ value
273
+ })), commandPromise.then((result) => ({
274
+ type: "command",
275
+ result
276
+ }), (error) => ({
277
+ type: "error",
278
+ error
279
+ }))]);
280
+ if (first.type === "error") throw first.error;
281
+ if (first.type === "command") notifyCreated(first.result);
282
+ else {
283
+ terminal = first.value;
284
+ terminalSettled = true;
285
+ commandPromise.then(notifyCreated).catch((error) => {
286
+ if (!terminalSettled) reportError(error);
287
+ });
322
288
  }
323
289
  terminal ??= await terminalPromise;
324
290
  terminalSettled = true;