@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.
- package/dist/client/base.cjs +4 -1
- package/dist/client/base.cjs.map +1 -1
- package/dist/client/base.d.cts +2 -0
- package/dist/client/base.d.cts.map +1 -1
- package/dist/client/base.d.ts +2 -0
- package/dist/client/base.d.ts.map +1 -1
- package/dist/client/base.js +5 -2
- package/dist/client/base.js.map +1 -1
- package/dist/client/index.cjs +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/runs/index.cjs +2 -0
- package/dist/client/runs/index.cjs.map +1 -1
- package/dist/client/runs/index.d.cts +15 -0
- package/dist/client/runs/index.d.cts.map +1 -1
- package/dist/client/runs/index.d.ts +15 -0
- package/dist/client/runs/index.d.ts.map +1 -1
- package/dist/client/runs/index.js +2 -0
- package/dist/client/runs/index.js.map +1 -1
- package/dist/client/stream/error.cjs +21 -0
- package/dist/client/stream/error.cjs.map +1 -1
- package/dist/client/stream/error.js +21 -1
- package/dist/client/stream/error.js.map +1 -1
- package/dist/client/stream/index.cjs +24 -1
- package/dist/client/stream/index.cjs.map +1 -1
- package/dist/client/stream/index.d.cts.map +1 -1
- package/dist/client/stream/index.d.ts.map +1 -1
- package/dist/client/stream/index.js +24 -1
- package/dist/client/stream/index.js.map +1 -1
- package/dist/client/stream/transport/agent-server.cjs +8 -2
- package/dist/client/stream/transport/agent-server.cjs.map +1 -1
- package/dist/client/stream/transport/agent-server.d.cts +17 -2
- package/dist/client/stream/transport/agent-server.d.cts.map +1 -1
- package/dist/client/stream/transport/agent-server.d.ts +17 -2
- package/dist/client/stream/transport/agent-server.d.ts.map +1 -1
- package/dist/client/stream/transport/agent-server.js +8 -2
- package/dist/client/stream/transport/agent-server.js.map +1 -1
- package/dist/client/stream/transport/http.cjs +81 -21
- package/dist/client/stream/transport/http.cjs.map +1 -1
- package/dist/client/stream/transport/http.d.cts +22 -7
- package/dist/client/stream/transport/http.d.cts.map +1 -1
- package/dist/client/stream/transport/http.d.ts +22 -7
- package/dist/client/stream/transport/http.d.ts.map +1 -1
- package/dist/client/stream/transport/http.js +83 -23
- package/dist/client/stream/transport/http.js.map +1 -1
- package/dist/client/stream/transport/index.cjs +2 -1
- package/dist/client/stream/transport/index.js +2 -1
- package/dist/client/stream/transport/types.d.cts +85 -5
- package/dist/client/stream/transport/types.d.cts.map +1 -1
- package/dist/client/stream/transport/types.d.ts +85 -5
- package/dist/client/stream/transport/types.d.ts.map +1 -1
- package/dist/client/stream/transport/utils.cjs +19 -0
- package/dist/client/stream/transport/utils.cjs.map +1 -1
- package/dist/client/stream/transport/utils.js +19 -1
- package/dist/client/stream/transport/utils.js.map +1 -1
- package/dist/client/stream/transport/websocket.cjs +125 -21
- package/dist/client/stream/transport/websocket.cjs.map +1 -1
- package/dist/client/stream/transport/websocket.d.cts +32 -4
- package/dist/client/stream/transport/websocket.d.cts.map +1 -1
- package/dist/client/stream/transport/websocket.d.ts +32 -4
- package/dist/client/stream/transport/websocket.d.ts.map +1 -1
- package/dist/client/stream/transport/websocket.js +126 -23
- package/dist/client/stream/transport/websocket.js.map +1 -1
- package/dist/client/stream/transport.d.cts +20 -3
- package/dist/client/stream/transport.d.cts.map +1 -1
- package/dist/client/stream/transport.d.ts +20 -3
- package/dist/client/stream/transport.d.ts.map +1 -1
- package/dist/client/stream/types.d.cts +31 -0
- package/dist/client/stream/types.d.cts.map +1 -1
- package/dist/client/stream/types.d.ts +31 -0
- package/dist/client/stream/types.d.ts.map +1 -1
- package/dist/client/threads/index.cjs +36 -17
- package/dist/client/threads/index.cjs.map +1 -1
- package/dist/client/threads/index.js +35 -16
- package/dist/client/threads/index.js.map +1 -1
- package/dist/client.cjs +1 -1
- package/dist/client.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/react-ui/server/server.cjs +2 -2
- package/dist/react-ui/server/server.cjs.map +1 -1
- package/dist/react-ui/server/server.js +1 -1
- package/dist/react-ui/server/server.js.map +1 -1
- package/dist/stream/controller.cjs +21 -3
- package/dist/stream/controller.cjs.map +1 -1
- package/dist/stream/controller.d.cts.map +1 -1
- package/dist/stream/controller.d.ts.map +1 -1
- package/dist/stream/controller.js +20 -2
- package/dist/stream/controller.js.map +1 -1
- package/dist/stream/index.cjs +2 -0
- package/dist/stream/index.d.cts +2 -1
- package/dist/stream/index.d.ts +2 -1
- package/dist/stream/index.js +2 -1
- package/dist/stream/projections/channel-effect.cjs +52 -0
- package/dist/stream/projections/channel-effect.cjs.map +1 -0
- package/dist/stream/projections/channel-effect.d.cts +35 -0
- package/dist/stream/projections/channel-effect.d.cts.map +1 -0
- package/dist/stream/projections/channel-effect.d.ts +35 -0
- package/dist/stream/projections/channel-effect.d.ts.map +1 -0
- package/dist/stream/projections/channel-effect.js +52 -0
- package/dist/stream/projections/channel-effect.js.map +1 -0
- package/dist/stream/projections/index.cjs +1 -0
- package/dist/stream/projections/index.d.ts +1 -0
- package/dist/stream/projections/index.js +1 -0
- package/dist/stream/root-message-projection.cjs +55 -0
- package/dist/stream/root-message-projection.cjs.map +1 -1
- package/dist/stream/root-message-projection.js +55 -0
- package/dist/stream/root-message-projection.js.map +1 -1
- package/dist/stream/submit-coordinator.cjs +3 -3
- package/dist/stream/submit-coordinator.cjs.map +1 -1
- package/dist/stream/submit-coordinator.js +1 -1
- package/dist/stream/submit-coordinator.js.map +1 -1
- package/dist/types.d.cts +20 -0
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +20 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/branching.d.cts +1 -1
- package/dist/ui/branching.d.ts +1 -1
- package/dist/ui/orchestrator.d.cts +1 -1
- package/dist/ui/orchestrator.d.cts.map +1 -1
- package/dist/ui/orchestrator.d.ts +1 -1
- package/dist/ui/orchestrator.d.ts.map +1 -1
- package/dist/utils/index.d.cts +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/stream.cjs +94 -0
- package/dist/utils/stream.cjs.map +1 -1
- package/dist/utils/stream.d.cts +16 -2
- package/dist/utils/stream.d.cts.map +1 -1
- package/dist/utils/stream.d.ts +16 -2
- package/dist/utils/stream.d.ts.map +1 -1
- package/dist/utils/stream.js +94 -1
- package/dist/utils/stream.js.map +1 -1
- package/package.json +6 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"submit-coordinator.js","names":["#options","#rootStore","#queueStore","#getDisposed","#getCurrentThreadId","#setCurrentThreadId","#rememberSelfCreatedThreadId","#forgetSelfCreatedThreadId","#hydrate","#ensureThread","#startDeferredRootPump","#abandonDeferredRootPump","#waitForRootPumpReady","#awaitNextTerminal","#awaitResumedRunTerminal","#onSubmitStart","#onRunStart","#onRunCreated","#onRunCompleted","#onRunEnd","#beginOptimistic","#settleOptimistic","uuidv7","#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.js","names":["#options","#rootStore","#queueStore","#getDisposed","#getCurrentThreadId","#setCurrentThreadId","#rememberSelfCreatedThreadId","#forgetSelfCreatedThreadId","#hydrate","#ensureThread","#startDeferredRootPump","#abandonDeferredRootPump","#waitForRootPumpReady","#awaitNextTerminal","#awaitResumedRunTerminal","#onSubmitStart","#onRunStart","#onRunCreated","#onRunCompleted","#onRunEnd","#beginOptimistic","#settleOptimistic","uuidv7","#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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"}
|
package/dist/types.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Checkpoint, Config, Metadata } from "./schema.cjs";
|
|
2
|
+
import { IdleReconnectMode } from "./utils/stream.cjs";
|
|
2
3
|
import { StreamMode } from "./types.stream.cjs";
|
|
3
4
|
import { LangChainTracer } from "@langchain/core/tracers/tracer_langchain";
|
|
4
5
|
|
|
@@ -150,6 +151,25 @@ interface RunsStreamPayload<TStreamMode extends StreamMode | StreamMode[] = [],
|
|
|
150
151
|
* If true, the stream can be resumed and replayed in its entirety even after disconnection.
|
|
151
152
|
*/
|
|
152
153
|
streamResumable?: boolean;
|
|
154
|
+
/**
|
|
155
|
+
* Idle-reconnect policy guarding against half-open sockets that hang
|
|
156
|
+
* indefinitely with no error or close (e.g. a platform revision rollover
|
|
157
|
+
* that hard-kills the serving pod). On idle the read is aborted and
|
|
158
|
+
* re-issued with `Last-Event-ID`, resuming from the persisted buffer
|
|
159
|
+
* (requires `streamResumable: true`).
|
|
160
|
+
*
|
|
161
|
+
* - `"auto"` (default): the client watches for the server's SSE keep-alive
|
|
162
|
+
* heartbeats (LangGraph Platform sends `: heartbeat` every ~5s) and only
|
|
163
|
+
* arms idle detection once it has observed them, sizing the window from
|
|
164
|
+
* the observed cadence. Because heartbeats keep flowing while the agent
|
|
165
|
+
* is merely quiet (long tool calls, HITL pauses), this is independent of
|
|
166
|
+
* agent activity and won't false-fire. On a server that never sends
|
|
167
|
+
* heartbeats it stays dormant.
|
|
168
|
+
* - a `number`: a fixed idle window in milliseconds (arms from the first
|
|
169
|
+
* byte; does not depend on heartbeats).
|
|
170
|
+
* - `0`: disables it.
|
|
171
|
+
*/
|
|
172
|
+
streamIdleReconnect?: IdleReconnectMode;
|
|
153
173
|
/**
|
|
154
174
|
* Pass one or more feedbackKeys if you want to request short-lived signed URLs
|
|
155
175
|
* for submitting feedback to LangSmith with this key for this run.
|
package/dist/types.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.cts","names":[],"sources":["../src/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.cts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;KAKY,iBAAA;AAAA,KACA,kBAAA;AAAA,KACA,oBAAA;AAAA,KACA,cAAA;AAAA,KACA,UAAA;AAAA,KACA,cAAA;AAAA,KACA,WAAA;AAAA,UAYK,IAAA;EACf,IAAA;EACA,KAAA;AAAA;AAAA,UAGe,OAAA;EArBL;;;EAyBV,MAAA,GAAS,MAAA;EAzBqB;AAChC;;EA6BE,MAAA;EA7BwB;;AAC1B;;;EAmCE,IAAA,GAAO,IAAA,GAAO,IAAA;AAAA;AAAA,UAGC,iBAAA;EArCS;;;EAyCxB,KAAA,GAAQ,MAAA;EAxCE;;;EA6CV,QAAA,GAAW,QAAA;EA7CU;AAYvB;;EAsCE,MAAA,GAAS,MAAA;EArCT;;AAIF;;EAuCE,OAAA;EAnCS;;;EAwCT,YAAA;EA5BkB;;;EAiClB,UAAA,GAAa,IAAA,CAAK,UAAA;EAjClB;;;;EAuCA,gBAAA;EApCe;;;;;;;EA6Cf,UAAA,GAAa,UAAA;EAAA;;;EAKb,eAAA;EA2Ce;;;EAtCf,cAAA;EAiEkC;;;;;;;;;;EArDlC,iBAAA,GAAoB,iBAAA;EArCF;;;EA0ClB,MAAA,GAAS,eAAA;EAtBT;;;;;;EA8BA,YAAA,GAAe,oBAAA;EAAA;;;EAKf,OAAA;EAcA;;;;;;EANA,YAAA,GAAe,cAAA;EAqBC;;;;EAfhB,YAAA;EAwBe;;;EAnBf,WAAA;EAoBiC;;;EAfjC,OAAA,GAAU,OAAA;EAiBF;;;EAZR,YAAA,IAAgB,MAAA;IAAU,MAAA;IAAgB,SAAA;EAAA;EAYlC;;;;EANR,gBAAA,GAAmB,eAAA;AAAA;AAAA,UAGJ,iBAAA,qBACK,UAAA,GAAa,UAAA,qDAEzB,iBAAA;EAmCc;;;EA/BtB,UAAA,GAAa,WAAA;EAwCE;;;EAnCf,eAAA,GAAkB,UAAA;EAuCc;;;;EAjChC,eAAA;EA6ByC;;;;;;;;;;AAwB3C;;;;;;;;EAjCE,mBAAA,GAAsB,iBAAA;EA8DtB;;;AAGF;EA3DE,YAAA;AAAA;AAAA,UAGe,iBAAA,SAA0B,iBAAA;EAoET;;;EAhEhC,UAAA,GAAa,UAAA,GAAa,KAAA,CAAM,UAAA;EAoD2B;;;EA/C3D,eAAA;EAsDA;;;;EAhDA,eAAA;EA0DA;;;;EApDA,YAAA;AAAA;AAAA,UAGe,kBAAA,SAA2B,iBAAA;EAkEX;;;;EA7D/B,QAAA;;;;;EAMA,QAAA;;;;;;;EAQA,cAAA;;;;EAKA,OAAA;;;;EAKA,OAAA;AAAA;AAAA,UAGe,kBAAA,SAA2B,iBAAA;EAC1C,QAAA;EACA,OAAA;;;;EAKA,QAAA;;;;EAKA,UAAA,GAAa,UAAA,GAAa,KAAA,CAAM,UAAA;;;;EAKhC,eAAA;;;;EAKA,eAAA;;;;;;;EAQA,cAAA;EACA,OAAA;AAAA;AAAA,UAGe,eAAA,SAAwB,iBAAA;;;;EAIvC,UAAA;AAAA"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Checkpoint, Config, Metadata } from "./schema.js";
|
|
2
|
+
import { IdleReconnectMode } from "./utils/stream.js";
|
|
2
3
|
import { StreamMode } from "./types.stream.js";
|
|
3
4
|
import { LangChainTracer } from "@langchain/core/tracers/tracer_langchain";
|
|
4
5
|
|
|
@@ -150,6 +151,25 @@ interface RunsStreamPayload<TStreamMode extends StreamMode | StreamMode[] = [],
|
|
|
150
151
|
* If true, the stream can be resumed and replayed in its entirety even after disconnection.
|
|
151
152
|
*/
|
|
152
153
|
streamResumable?: boolean;
|
|
154
|
+
/**
|
|
155
|
+
* Idle-reconnect policy guarding against half-open sockets that hang
|
|
156
|
+
* indefinitely with no error or close (e.g. a platform revision rollover
|
|
157
|
+
* that hard-kills the serving pod). On idle the read is aborted and
|
|
158
|
+
* re-issued with `Last-Event-ID`, resuming from the persisted buffer
|
|
159
|
+
* (requires `streamResumable: true`).
|
|
160
|
+
*
|
|
161
|
+
* - `"auto"` (default): the client watches for the server's SSE keep-alive
|
|
162
|
+
* heartbeats (LangGraph Platform sends `: heartbeat` every ~5s) and only
|
|
163
|
+
* arms idle detection once it has observed them, sizing the window from
|
|
164
|
+
* the observed cadence. Because heartbeats keep flowing while the agent
|
|
165
|
+
* is merely quiet (long tool calls, HITL pauses), this is independent of
|
|
166
|
+
* agent activity and won't false-fire. On a server that never sends
|
|
167
|
+
* heartbeats it stays dormant.
|
|
168
|
+
* - a `number`: a fixed idle window in milliseconds (arms from the first
|
|
169
|
+
* byte; does not depend on heartbeats).
|
|
170
|
+
* - `0`: disables it.
|
|
171
|
+
*/
|
|
172
|
+
streamIdleReconnect?: IdleReconnectMode;
|
|
153
173
|
/**
|
|
154
174
|
* Pass one or more feedbackKeys if you want to request short-lived signed URLs
|
|
155
175
|
* for submitting feedback to LangSmith with this key for this run.
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;KAKY,iBAAA;AAAA,KACA,kBAAA;AAAA,KACA,oBAAA;AAAA,KACA,cAAA;AAAA,KACA,UAAA;AAAA,KACA,cAAA;AAAA,KACA,WAAA;AAAA,UAYK,IAAA;EACf,IAAA;EACA,KAAA;AAAA;AAAA,UAGe,OAAA;EArBL;;;EAyBV,MAAA,GAAS,MAAA;EAzBqB;AAChC;;EA6BE,MAAA;EA7BwB;;AAC1B;;;EAmCE,IAAA,GAAO,IAAA,GAAO,IAAA;AAAA;AAAA,UAGC,iBAAA;EArCS;;;EAyCxB,KAAA,GAAQ,MAAA;EAxCE;;;EA6CV,QAAA,GAAW,QAAA;EA7CU;AAYvB;;EAsCE,MAAA,GAAS,MAAA;EArCT;;AAIF;;EAuCE,OAAA;EAnCS;;;EAwCT,YAAA;EA5BkB;;;EAiClB,UAAA,GAAa,IAAA,CAAK,UAAA;EAjClB;;;;EAuCA,gBAAA;EApCe;;;;;;;EA6Cf,UAAA,GAAa,UAAA;EAAA;;;EAKb,eAAA;EA2Ce;;;EAtCf,cAAA;EAiEkC;;;;;;;;;;EArDlC,iBAAA,GAAoB,iBAAA;EArCF;;;EA0ClB,MAAA,GAAS,eAAA;EAtBT;;;;;;EA8BA,YAAA,GAAe,oBAAA;EAAA;;;EAKf,OAAA;EAcA;;;;;;EANA,YAAA,GAAe,cAAA;EAqBC;;;;EAfhB,YAAA;EAwBe;;;EAnBf,WAAA;EAoBiC;;;EAfjC,OAAA,GAAU,OAAA;EAiBF;;;EAZR,YAAA,IAAgB,MAAA;IAAU,MAAA;IAAgB,SAAA;EAAA;EAYlC;;;;EANR,gBAAA,GAAmB,eAAA;AAAA;AAAA,UAGJ,iBAAA,qBACK,UAAA,GAAa,UAAA,qDAEzB,iBAAA;EAmCc;;;EA/BtB,UAAA,GAAa,WAAA;EAwCE;;;EAnCf,eAAA,GAAkB,UAAA;EAuCc;;;;EAjChC,eAAA;EA6ByC;;;;;;;;;;AAwB3C;;;;;;;;EAjCE,mBAAA,GAAsB,iBAAA;EA8DtB;;;AAGF;EA3DE,YAAA;AAAA;AAAA,UAGe,iBAAA,SAA0B,iBAAA;EAoET;;;EAhEhC,UAAA,GAAa,UAAA,GAAa,KAAA,CAAM,UAAA;EAoD2B;;;EA/C3D,eAAA;EAsDA;;;;EAhDA,eAAA;EA0DA;;;;EApDA,YAAA;AAAA;AAAA,UAGe,kBAAA,SAA2B,iBAAA;EAkEX;;;;EA7D/B,QAAA;;;;;EAMA,QAAA;;;;;;;EAQA,cAAA;;;;EAKA,OAAA;;;;EAKA,OAAA;AAAA;AAAA,UAGe,kBAAA,SAA2B,iBAAA;EAC1C,QAAA;EACA,OAAA;;;;EAKA,QAAA;;;;EAKA,UAAA,GAAa,UAAA,GAAa,KAAA,CAAM,UAAA;;;;EAKhC,eAAA;;;;EAKA,eAAA;;;;;;;EAQA,cAAA;EACA,OAAA;AAAA;AAAA,UAGe,eAAA,SAAwB,iBAAA;;;;EAIvC,UAAA;AAAA"}
|
package/dist/ui/branching.d.cts
CHANGED
|
@@ -23,7 +23,7 @@ declare function getBranchContext<StateType extends Record<string, unknown>>(bra
|
|
|
23
23
|
branchTree: Sequence<any>;
|
|
24
24
|
flatHistory: ThreadState<any>[];
|
|
25
25
|
branchByCheckpoint: BranchByCheckpoint;
|
|
26
|
-
threadHead: any;
|
|
26
|
+
threadHead: ThreadState<any> | undefined;
|
|
27
27
|
};
|
|
28
28
|
declare function getMessagesMetadataMap<StateType extends Record<string, unknown>>(options: {
|
|
29
29
|
initialValues: StateType | null | undefined;
|
package/dist/ui/branching.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ declare function getBranchContext<StateType extends Record<string, unknown>>(bra
|
|
|
23
23
|
branchTree: Sequence<any>;
|
|
24
24
|
flatHistory: ThreadState<any>[];
|
|
25
25
|
branchByCheckpoint: BranchByCheckpoint;
|
|
26
|
-
threadHead: any;
|
|
26
|
+
threadHead: ThreadState<any> | undefined;
|
|
27
27
|
};
|
|
28
28
|
declare function getMessagesMetadataMap<StateType extends Record<string, unknown>>(options: {
|
|
29
29
|
initialValues: StateType | null | undefined;
|
|
@@ -108,7 +108,7 @@ declare class StreamOrchestrator<StateType extends Record<string, unknown> = Rec
|
|
|
108
108
|
branchOptions: string[] | undefined;
|
|
109
109
|
};
|
|
110
110
|
};
|
|
111
|
-
threadHead: any;
|
|
111
|
+
threadHead: ThreadState<any> | undefined;
|
|
112
112
|
};
|
|
113
113
|
/**
|
|
114
114
|
* The state values from the thread head of the current branch history,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrator.d.cts","names":[],"sources":["../../src/ui/orchestrator.ts"],"mappings":";;;;;;;;;;;;;;;;;AA0GA;UAAiB,qBAAA;EACf,SAAA,IAAa,MAAA;EACb,cAAA;EACA,cAAA;AAAA;;;;;AAaF;;;;;;cAAa,kBAAA,mBACO,MAAA,oBAA0B,MAAA,+BAChC,WAAA,GAAc,WAAA;EAAA;WAEjB,MAAA,EAAQ,aAAA,CAAc,SAAA,EAAW,GAAA;EAAA,SAEjC,cAAA,EAAgB,mBAAA;EAAA,SAEhB,WAAA,EAAa,kBAAA,CACpB,SAAA,EACA,aAAA,CAAc,SAAA,EAAW,mBAAA,CAAoB,GAAA;EAAA,SAOtC,YAAA;EAPO;;;;;;;;EA+ChB,WAAA,CACE,OAAA,EAAS,gBAAA,CAAiB,SAAA,EAAW,GAAA,GACrC,SAAA,EAAW,qBAAA;EA0JsB;;;;;;;EAlGnC,SAAA,CAAU,QAAA;
|
|
1
|
+
{"version":3,"file":"orchestrator.d.cts","names":[],"sources":["../../src/ui/orchestrator.ts"],"mappings":"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"}
|
|
@@ -108,7 +108,7 @@ declare class StreamOrchestrator<StateType extends Record<string, unknown> = Rec
|
|
|
108
108
|
branchOptions: string[] | undefined;
|
|
109
109
|
};
|
|
110
110
|
};
|
|
111
|
-
threadHead: any;
|
|
111
|
+
threadHead: ThreadState<any> | undefined;
|
|
112
112
|
};
|
|
113
113
|
/**
|
|
114
114
|
* The state values from the thread head of the current branch history,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrator.d.ts","names":[],"sources":["../../src/ui/orchestrator.ts"],"mappings":";;;;;;;;;;;;;;;;;;UA0GiB,qBAAA;EACf,SAAA,IAAa,MAAA;EACb,cAAA;EACA,cAAA;AAAA;;;;;;AAaF;;;;;cAAa,kBAAA,mBACO,MAAA,oBAA0B,MAAA,+BAChC,WAAA,GAAc,WAAA;EAAA;WAEjB,MAAA,EAAQ,aAAA,CAAc,SAAA,EAAW,GAAA;EAAA,SAEjC,cAAA,EAAgB,mBAAA;EAAA,SAEhB,WAAA,EAAa,kBAAA,CACpB,SAAA,EACA,aAAA,CAAc,SAAA,EAAW,mBAAA,CAAoB,GAAA;EAAA,SAOtC,YAAA;EARP;;;;;;;;EAgDF,WAAA,CACE,OAAA,EAAS,gBAAA,CAAiB,SAAA,EAAW,GAAA,GACrC,SAAA,EAAW,qBAAA;EAAA;;;;;;;EAwDb,SAAA,CAAU,QAAA;
|
|
1
|
+
{"version":3,"file":"orchestrator.d.ts","names":[],"sources":["../../src/ui/orchestrator.ts"],"mappings":"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"}
|
package/dist/utils/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
+
import { IterableReadableStream } from "./stream.cjs";
|
|
1
2
|
import { getToolCallsWithResults } from "./tools.cjs";
|
|
2
3
|
import { BytesLineDecoder, SSEDecoder } from "./sse.cjs";
|
|
3
|
-
import { IterableReadableStream } from "./stream.cjs";
|
|
4
4
|
export { BytesLineDecoder, IterableReadableStream, SSEDecoder, getToolCallsWithResults };
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
+
import { IterableReadableStream } from "./stream.js";
|
|
1
2
|
import { getToolCallsWithResults } from "./tools.js";
|
|
2
3
|
import { BytesLineDecoder, SSEDecoder } from "./sse.js";
|
|
3
|
-
import { IterableReadableStream } from "./stream.js";
|
|
4
4
|
export { BytesLineDecoder, IterableReadableStream, SSEDecoder, getToolCallsWithResults };
|
package/dist/utils/stream.cjs
CHANGED
|
@@ -11,6 +11,99 @@ var MaxReconnectAttemptsError = class extends Error {
|
|
|
11
11
|
}
|
|
12
12
|
};
|
|
13
13
|
/**
|
|
14
|
+
* Error injected into the stream by {@link idleReconnectStream} when no lines
|
|
15
|
+
* arrive within the active idle window. Surfacing this during the read is what
|
|
16
|
+
* lets the reconnect loops in `streamWithRetry` and the protocol SSE transport
|
|
17
|
+
* recover from a half-open socket — one that was silently dropped (e.g. a hard
|
|
18
|
+
* pod kill on a platform revision rollover) without a TCP FIN/RST, so neither
|
|
19
|
+
* a `done` nor a thrown network error ever arrives.
|
|
20
|
+
*/
|
|
21
|
+
var StreamIdleTimeoutError = class extends Error {
|
|
22
|
+
idleTimeoutMs;
|
|
23
|
+
constructor(idleTimeoutMs) {
|
|
24
|
+
super(`No SSE bytes received for ${idleTimeoutMs}ms; assuming the connection is half-open and reconnecting.`);
|
|
25
|
+
this.name = "StreamIdleTimeoutError";
|
|
26
|
+
this.idleTimeoutMs = idleTimeoutMs;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
/** `":"` — first byte of an SSE comment / keep-alive line. */
|
|
30
|
+
const SSE_COMMENT_BYTE = 58;
|
|
31
|
+
/**
|
|
32
|
+
* A pass-through {@link TransformStream} that errors the stream when it goes
|
|
33
|
+
* idle, so the surrounding reconnect logic can recover a half-open socket.
|
|
34
|
+
*
|
|
35
|
+
* MUST sit on the *line* stream — i.e. after
|
|
36
|
+
* {@link import("./sse.js").BytesLineDecoder} but before
|
|
37
|
+
* {@link import("./sse.js").SSEDecoder} (which discards `:` comment lines).
|
|
38
|
+
* Operating at the line level lets the watchdog both (a) reset on any line
|
|
39
|
+
* (data *or* heartbeat = liveness) and (b) recognise heartbeat comment lines
|
|
40
|
+
* to drive `"auto"` mode.
|
|
41
|
+
*
|
|
42
|
+
* In `"auto"` mode the watchdog is intentionally dormant until it has seen at
|
|
43
|
+
* least two heartbeats (so it can measure the cadence). This means a socket
|
|
44
|
+
* that dies inside the first heartbeat interval won't be caught until a
|
|
45
|
+
* heartbeat would have been due — an acceptable trade for never false-firing
|
|
46
|
+
* on heartbeat-less servers. Pass a fixed `number` if you need coverage from
|
|
47
|
+
* the very first byte.
|
|
48
|
+
*/
|
|
49
|
+
function idleReconnectStream(options) {
|
|
50
|
+
const factor = options.timeoutFactor ?? 3;
|
|
51
|
+
const minTimeoutMs = options.minTimeoutMs ?? 6e3;
|
|
52
|
+
const maxTimeoutMs = options.maxTimeoutMs ?? 3e4;
|
|
53
|
+
const fixedTimeoutMs = typeof options.mode === "number" ? options.mode : null;
|
|
54
|
+
let timer;
|
|
55
|
+
let controllerRef;
|
|
56
|
+
let lastHeartbeatAt;
|
|
57
|
+
let derivedTimeoutMs = fixedTimeoutMs;
|
|
58
|
+
const clear = () => {
|
|
59
|
+
if (timer != null) {
|
|
60
|
+
clearTimeout(timer);
|
|
61
|
+
timer = void 0;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const arm = () => {
|
|
65
|
+
clear();
|
|
66
|
+
const timeoutMs = derivedTimeoutMs;
|
|
67
|
+
if (timeoutMs == null || timeoutMs <= 0) return;
|
|
68
|
+
timer = setTimeout(() => {
|
|
69
|
+
options.onIdle?.({
|
|
70
|
+
timeoutMs,
|
|
71
|
+
source: fixedTimeoutMs != null ? "fixed" : "heartbeat"
|
|
72
|
+
});
|
|
73
|
+
try {
|
|
74
|
+
controllerRef?.error(new StreamIdleTimeoutError(timeoutMs));
|
|
75
|
+
} catch {}
|
|
76
|
+
}, timeoutMs);
|
|
77
|
+
timer.unref?.();
|
|
78
|
+
};
|
|
79
|
+
const noteHeartbeat = () => {
|
|
80
|
+
if (fixedTimeoutMs != null) return;
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
if (lastHeartbeatAt != null) {
|
|
83
|
+
const interval = now - lastHeartbeatAt;
|
|
84
|
+
if (interval > 0) {
|
|
85
|
+
const candidate = Math.min(Math.max(interval * factor, minTimeoutMs), maxTimeoutMs);
|
|
86
|
+
derivedTimeoutMs = derivedTimeoutMs == null ? candidate : Math.max(derivedTimeoutMs, candidate);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
lastHeartbeatAt = now;
|
|
90
|
+
};
|
|
91
|
+
return new TransformStream({
|
|
92
|
+
start(controller) {
|
|
93
|
+
controllerRef = controller;
|
|
94
|
+
arm();
|
|
95
|
+
},
|
|
96
|
+
transform(line, controller) {
|
|
97
|
+
if (line.length > 0 && line[0] === SSE_COMMENT_BYTE) noteHeartbeat();
|
|
98
|
+
arm();
|
|
99
|
+
controller.enqueue(line);
|
|
100
|
+
},
|
|
101
|
+
flush() {
|
|
102
|
+
clear();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
14
107
|
* Stream with automatic retry logic for SSE connections.
|
|
15
108
|
* Implements reconnection behavior similar to the Python SDK.
|
|
16
109
|
*
|
|
@@ -168,6 +261,7 @@ var IterableReadableStream = class IterableReadableStream extends ReadableStream
|
|
|
168
261
|
};
|
|
169
262
|
//#endregion
|
|
170
263
|
exports.IterableReadableStream = IterableReadableStream;
|
|
264
|
+
exports.idleReconnectStream = idleReconnectStream;
|
|
171
265
|
exports.streamWithRetry = streamWithRetry;
|
|
172
266
|
|
|
173
267
|
//# sourceMappingURL=stream.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.cjs","names":["isNetworkError"],"sources":["../../src/utils/stream.ts"],"sourcesContent":["import { isNetworkError } from \"./error.js\";\n\n// in this case don't quite match.\ntype IterableReadableStreamInterface<T> = ReadableStream<T> & AsyncIterable<T>;\n\n/**\n * Options for streaming with automatic retry logic.\n */\nexport interface StreamWithRetryOptions {\n /**\n * Maximum number of reconnection attempts. Default is 5.\n */\n maxRetries?: number;\n\n /**\n * AbortSignal to cancel the stream.\n */\n signal?: AbortSignal;\n\n /**\n * Callback invoked when a reconnection attempt is made.\n */\n onReconnect?: (options: {\n attempt: number;\n lastEventId?: string;\n cause: unknown;\n }) => void;\n}\n\n/**\n * Parameters for making a stream request\n */\nexport interface StreamRequestParams {\n /**\n * Last event ID to resume from, if available\n */\n lastEventId?: string;\n\n /**\n * Optional reconnection path from the Location header\n */\n reconnectPath?: string;\n}\n\n/**\n * Error thrown when maximum reconnection attempts are exceeded.\n */\nexport class MaxReconnectAttemptsError extends Error {\n constructor(maxAttempts: number, cause: unknown) {\n super(`Exceeded maximum SSE reconnection attempts (${maxAttempts})`);\n this.name = \"MaxReconnectAttemptsError\";\n this.cause = cause;\n }\n}\n\n/**\n * Stream with automatic retry logic for SSE connections.\n * Implements reconnection behavior similar to the Python SDK.\n *\n * @param makeRequest Function to make requests. When `params` is undefined/empty, it's the initial request.\n * When `params.reconnectPath` is provided, it's a reconnection request.\n * @param options Configuration options\n * @returns AsyncGenerator yielding stream events\n */\nexport async function* streamWithRetry<T extends { id?: string }>(\n makeRequest: (params?: StreamRequestParams) => Promise<{\n response: Response;\n stream: ReadableStream<T>;\n }>,\n options: StreamWithRetryOptions = {}\n): AsyncGenerator<T> {\n const maxRetries = options.maxRetries ?? 5;\n let attempt = 0;\n let lastEventId: string | undefined;\n let reconnectPath: string | undefined;\n\n while (true) {\n let shouldRetry = false;\n let lastError: unknown;\n let reader: ReadableStreamDefaultReader<T> | undefined;\n\n try {\n // Check if aborted before making request\n if (options.signal?.aborted) return;\n\n // Make request - initial if no reconnect path, reconnect otherwise\n const { response, stream } = await makeRequest(\n reconnectPath ? { lastEventId, reconnectPath } : undefined\n );\n\n // Check for Location header (server-provided reconnection path)\n const locationHeader = response.headers.get(\"location\");\n if (locationHeader) {\n reconnectPath = locationHeader;\n }\n\n // Verify content type\n const contentType = response.headers.get(\"content-type\")?.split(\";\")[0];\n if (contentType && !contentType.includes(\"text/event-stream\")) {\n throw new Error(\n `Expected response header Content-Type to contain 'text/event-stream', got '${contentType}'`\n );\n }\n\n reader = stream.getReader();\n\n try {\n while (true) {\n // Check abort signal before each read\n if (options.signal?.aborted) {\n await reader.cancel();\n return;\n }\n\n const { done, value } = await reader.read();\n\n if (done) {\n // Stream completed successfully\n break;\n }\n\n // Track last event ID for reconnection\n if (value.id) {\n lastEventId = value.id;\n }\n\n yield value;\n }\n\n // Stream completed successfully, exit retry loop\n break;\n } catch (error) {\n // Error during streaming - attempt reconnect if we have a location header\n if (reconnectPath && !options.signal?.aborted) {\n shouldRetry = true;\n } else {\n throw error;\n }\n } finally {\n if (reader) {\n try {\n reader.releaseLock();\n } catch {\n // Ignore errors when releasing lock\n }\n }\n }\n } catch (error) {\n lastError = error;\n\n // Only retry if we have reconnection capability and it's a network error\n if (isNetworkError(error) && reconnectPath && !options.signal?.aborted) {\n shouldRetry = true;\n } else {\n throw error;\n }\n }\n\n if (shouldRetry) {\n attempt += 1;\n if (attempt > maxRetries) {\n throw new MaxReconnectAttemptsError(maxRetries, lastError);\n }\n\n // Notify about reconnection attempt\n options.onReconnect?.({ attempt, lastEventId, cause: lastError });\n\n // Exponential backoff with jitter: min(1000 * 2^attempt, 5000) + random jitter\n const baseDelay = Math.min(1000 * 2 ** (attempt - 1), 5000);\n const jitter = Math.random() * 1000;\n const delay = baseDelay + jitter;\n\n await new Promise((resolve) => {\n setTimeout(resolve, delay);\n });\n\n continue;\n }\n\n // Successfully completed\n break;\n }\n}\n\n/*\n * Support async iterator syntax for ReadableStreams in all environments.\n * Source: https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490\n */\nexport class IterableReadableStream<T>\n extends ReadableStream<T>\n implements IterableReadableStreamInterface<T>\n{\n public reader: ReadableStreamDefaultReader<T>;\n\n ensureReader() {\n if (!this.reader) {\n this.reader = this.getReader();\n }\n }\n\n async next(): Promise<IteratorResult<T>> {\n this.ensureReader();\n try {\n const result = await this.reader.read();\n if (result.done) {\n this.reader.releaseLock(); // release lock when stream becomes closed\n return {\n done: true,\n value: undefined,\n };\n } else {\n return {\n done: false,\n value: result.value,\n };\n }\n } catch (e) {\n this.reader.releaseLock(); // release lock when stream becomes errored\n throw e;\n }\n }\n\n async return(): Promise<IteratorResult<T>> {\n this.ensureReader();\n // If wrapped in a Node stream, cancel is already called.\n if (this.locked) {\n const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet\n this.reader.releaseLock(); // release lock first\n await cancelPromise; // now await it\n }\n return { done: true, value: undefined };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async throw(e: any): Promise<IteratorResult<T>> {\n this.ensureReader();\n if (this.locked) {\n const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet\n this.reader.releaseLock(); // release lock first\n await cancelPromise; // now await it\n }\n throw e;\n }\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore Not present in Node 18 types, required in latest Node 22\n async [Symbol.asyncDispose]() {\n await this.return();\n }\n\n [Symbol.asyncIterator]() {\n return this;\n }\n\n static fromReadableStream<T>(stream: ReadableStream<T>) {\n // From https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams#reading_the_stream\n const reader = stream.getReader();\n return new IterableReadableStream<T>({\n start(controller) {\n return pump();\n function pump(): Promise<T | undefined> {\n return reader.read().then(({ done, value }) => {\n // When no more data needs to be consumed, close the stream\n if (done) {\n controller.close();\n return;\n }\n // Enqueue the next data chunk into our target stream\n controller.enqueue(value);\n return pump();\n });\n }\n },\n cancel() {\n reader.releaseLock();\n },\n });\n }\n\n static fromAsyncGenerator<T>(generator: AsyncGenerator<T>) {\n return new IterableReadableStream<T>({\n async pull(controller) {\n const { value, done } = await generator.next();\n // When no more data needs to be consumed, close the stream\n if (done) {\n controller.close();\n }\n // Fix: `else if (value)` will hang the streaming when nullish value (e.g. empty string) is pulled\n controller.enqueue(value);\n },\n async cancel(reason) {\n await generator.return(reason);\n },\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"stream.cjs","names":["isNetworkError"],"sources":["../../src/utils/stream.ts"],"sourcesContent":["import { isNetworkError } from \"./error.js\";\n\n// in this case don't quite match.\ntype IterableReadableStreamInterface<T> = ReadableStream<T> & AsyncIterable<T>;\n\n/**\n * Options for streaming with automatic retry logic.\n */\nexport interface StreamWithRetryOptions {\n /**\n * Maximum number of reconnection attempts. Default is 5.\n */\n maxRetries?: number;\n\n /**\n * AbortSignal to cancel the stream.\n */\n signal?: AbortSignal;\n\n /**\n * Callback invoked when a reconnection attempt is made.\n */\n onReconnect?: (options: {\n attempt: number;\n lastEventId?: string;\n cause: unknown;\n }) => void;\n}\n\n/**\n * Parameters for making a stream request\n */\nexport interface StreamRequestParams {\n /**\n * Last event ID to resume from, if available\n */\n lastEventId?: string;\n\n /**\n * Optional reconnection path from the Location header\n */\n reconnectPath?: string;\n}\n\n/**\n * Error thrown when maximum reconnection attempts are exceeded.\n */\nexport class MaxReconnectAttemptsError extends Error {\n constructor(maxAttempts: number, cause: unknown) {\n super(`Exceeded maximum SSE reconnection attempts (${maxAttempts})`);\n this.name = \"MaxReconnectAttemptsError\";\n this.cause = cause;\n }\n}\n\n/**\n * Error injected into the stream by {@link idleReconnectStream} when no lines\n * arrive within the active idle window. Surfacing this during the read is what\n * lets the reconnect loops in `streamWithRetry` and the protocol SSE transport\n * recover from a half-open socket — one that was silently dropped (e.g. a hard\n * pod kill on a platform revision rollover) without a TCP FIN/RST, so neither\n * a `done` nor a thrown network error ever arrives.\n */\nexport class StreamIdleTimeoutError extends Error {\n readonly idleTimeoutMs: number;\n\n constructor(idleTimeoutMs: number) {\n super(\n `No SSE bytes received for ${idleTimeoutMs}ms; assuming the connection is half-open and reconnecting.`\n );\n this.name = \"StreamIdleTimeoutError\";\n this.idleTimeoutMs = idleTimeoutMs;\n }\n}\n\n/** `\":\"` — first byte of an SSE comment / keep-alive line. */\nconst SSE_COMMENT_BYTE = 0x3a;\n\n/**\n * How {@link idleReconnectStream} decides when the connection is idle.\n *\n * - A `number` is a fixed idle window in milliseconds: the watchdog arms\n * immediately (even before the first byte) and trips after that long with no\n * activity. Use when you know your server's behaviour and want guaranteed\n * coverage from t=0; it does not depend on heartbeats.\n * - `\"auto\"` is heartbeat-adaptive: the watchdog stays dormant until it has\n * observed the server's SSE keep-alive comments (e.g. LangGraph Platform's\n * `: heartbeat` every ~5s), then arms with a window derived from the\n * observed cadence. On a server that never sends heartbeats it never arms,\n * so it can't false-fire during a legitimately quiet period.\n */\nexport type IdleReconnectMode = number | \"auto\";\n\nexport interface IdleReconnectStreamOptions {\n /** Fixed timeout (ms) or `\"auto\"` heartbeat-adaptive. */\n mode: IdleReconnectMode;\n /** `\"auto\"`: multiplier applied to the observed heartbeat interval. Default 3. */\n timeoutFactor?: number;\n /** `\"auto\"`: lower clamp for the derived timeout (ms). Default 6000. */\n minTimeoutMs?: number;\n /** `\"auto\"`: upper clamp for the derived timeout (ms). Default 30000. */\n maxTimeoutMs?: number;\n /** Fired immediately before the stream is errored, for logging/metrics. */\n onIdle?: (info: { timeoutMs: number; source: \"fixed\" | \"heartbeat\" }) => void;\n}\n\n/**\n * A pass-through {@link TransformStream} that errors the stream when it goes\n * idle, so the surrounding reconnect logic can recover a half-open socket.\n *\n * MUST sit on the *line* stream — i.e. after\n * {@link import(\"./sse.js\").BytesLineDecoder} but before\n * {@link import(\"./sse.js\").SSEDecoder} (which discards `:` comment lines).\n * Operating at the line level lets the watchdog both (a) reset on any line\n * (data *or* heartbeat = liveness) and (b) recognise heartbeat comment lines\n * to drive `\"auto\"` mode.\n *\n * In `\"auto\"` mode the watchdog is intentionally dormant until it has seen at\n * least two heartbeats (so it can measure the cadence). This means a socket\n * that dies inside the first heartbeat interval won't be caught until a\n * heartbeat would have been due — an acceptable trade for never false-firing\n * on heartbeat-less servers. Pass a fixed `number` if you need coverage from\n * the very first byte.\n */\nexport function idleReconnectStream(\n options: IdleReconnectStreamOptions\n): TransformStream<Uint8Array, Uint8Array> {\n const factor = options.timeoutFactor ?? 3;\n const minTimeoutMs = options.minTimeoutMs ?? 6_000;\n const maxTimeoutMs = options.maxTimeoutMs ?? 30_000;\n const fixedTimeoutMs = typeof options.mode === \"number\" ? options.mode : null;\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n let controllerRef: TransformStreamDefaultController<Uint8Array> | undefined;\n\n // `\"auto\"` cadence inference.\n let lastHeartbeatAt: number | undefined;\n // The active idle window: the fixed value, or (auto) the heartbeat-derived\n // value once known. `null` means \"not armed yet\".\n let derivedTimeoutMs: number | null = fixedTimeoutMs;\n\n const clear = () => {\n if (timer != null) {\n clearTimeout(timer);\n timer = undefined;\n }\n };\n\n const arm = () => {\n clear();\n const timeoutMs = derivedTimeoutMs;\n if (timeoutMs == null || timeoutMs <= 0) return;\n timer = setTimeout(() => {\n options.onIdle?.({\n timeoutMs,\n source: fixedTimeoutMs != null ? \"fixed\" : \"heartbeat\",\n });\n try {\n controllerRef?.error(new StreamIdleTimeoutError(timeoutMs));\n } catch {\n // Stream already closed/errored/cancelled — nothing to abort.\n }\n }, timeoutMs);\n // Don't let the watchdog by itself keep a Node process alive.\n (timer as unknown as { unref?: () => void }).unref?.();\n };\n\n const noteHeartbeat = () => {\n if (fixedTimeoutMs != null) return; // cadence irrelevant in fixed mode\n const now = Date.now();\n if (lastHeartbeatAt != null) {\n const interval = now - lastHeartbeatAt;\n if (interval > 0) {\n const candidate = Math.min(\n Math.max(interval * factor, minTimeoutMs),\n maxTimeoutMs\n );\n // Keep the most conservative (largest) window observed so far.\n derivedTimeoutMs =\n derivedTimeoutMs == null\n ? candidate\n : Math.max(derivedTimeoutMs, candidate);\n }\n }\n lastHeartbeatAt = now;\n };\n\n return new TransformStream<Uint8Array, Uint8Array>({\n start(controller) {\n controllerRef = controller;\n // Fixed mode arms eagerly (also catches pre-first-byte silence). Auto\n // mode waits until a cadence is established in `transform`.\n arm();\n },\n transform(line, controller) {\n // A line beginning with \":\" is an SSE comment / keep-alive heartbeat.\n if (line.length > 0 && line[0] === SSE_COMMENT_BYTE) {\n noteHeartbeat();\n }\n // Any line is liveness — (re)arm the idle timer.\n arm();\n controller.enqueue(line);\n },\n flush() {\n clear();\n },\n });\n}\n\n/**\n * Stream with automatic retry logic for SSE connections.\n * Implements reconnection behavior similar to the Python SDK.\n *\n * @param makeRequest Function to make requests. When `params` is undefined/empty, it's the initial request.\n * When `params.reconnectPath` is provided, it's a reconnection request.\n * @param options Configuration options\n * @returns AsyncGenerator yielding stream events\n */\nexport async function* streamWithRetry<T extends { id?: string }>(\n makeRequest: (params?: StreamRequestParams) => Promise<{\n response: Response;\n stream: ReadableStream<T>;\n }>,\n options: StreamWithRetryOptions = {}\n): AsyncGenerator<T> {\n const maxRetries = options.maxRetries ?? 5;\n let attempt = 0;\n let lastEventId: string | undefined;\n let reconnectPath: string | undefined;\n\n while (true) {\n let shouldRetry = false;\n let lastError: unknown;\n let reader: ReadableStreamDefaultReader<T> | undefined;\n\n try {\n // Check if aborted before making request\n if (options.signal?.aborted) return;\n\n // Make request - initial if no reconnect path, reconnect otherwise\n const { response, stream } = await makeRequest(\n reconnectPath ? { lastEventId, reconnectPath } : undefined\n );\n\n // Check for Location header (server-provided reconnection path)\n const locationHeader = response.headers.get(\"location\");\n if (locationHeader) {\n reconnectPath = locationHeader;\n }\n\n // Verify content type\n const contentType = response.headers.get(\"content-type\")?.split(\";\")[0];\n if (contentType && !contentType.includes(\"text/event-stream\")) {\n throw new Error(\n `Expected response header Content-Type to contain 'text/event-stream', got '${contentType}'`\n );\n }\n\n reader = stream.getReader();\n\n try {\n while (true) {\n // Check abort signal before each read\n if (options.signal?.aborted) {\n await reader.cancel();\n return;\n }\n\n const { done, value } = await reader.read();\n\n if (done) {\n // Stream completed successfully\n break;\n }\n\n // Track last event ID for reconnection\n if (value.id) {\n lastEventId = value.id;\n }\n\n yield value;\n }\n\n // Stream completed successfully, exit retry loop\n break;\n } catch (error) {\n // Error during streaming - attempt reconnect if we have a location header\n if (reconnectPath && !options.signal?.aborted) {\n shouldRetry = true;\n } else {\n throw error;\n }\n } finally {\n if (reader) {\n try {\n reader.releaseLock();\n } catch {\n // Ignore errors when releasing lock\n }\n }\n }\n } catch (error) {\n lastError = error;\n\n // Only retry if we have reconnection capability and it's a network error\n if (isNetworkError(error) && reconnectPath && !options.signal?.aborted) {\n shouldRetry = true;\n } else {\n throw error;\n }\n }\n\n if (shouldRetry) {\n attempt += 1;\n if (attempt > maxRetries) {\n throw new MaxReconnectAttemptsError(maxRetries, lastError);\n }\n\n // Notify about reconnection attempt\n options.onReconnect?.({ attempt, lastEventId, cause: lastError });\n\n // Exponential backoff with jitter: min(1000 * 2^attempt, 5000) + random jitter\n const baseDelay = Math.min(1000 * 2 ** (attempt - 1), 5000);\n const jitter = Math.random() * 1000;\n const delay = baseDelay + jitter;\n\n await new Promise((resolve) => {\n setTimeout(resolve, delay);\n });\n\n continue;\n }\n\n // Successfully completed\n break;\n }\n}\n\n/*\n * Support async iterator syntax for ReadableStreams in all environments.\n * Source: https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490\n */\nexport class IterableReadableStream<T>\n extends ReadableStream<T>\n implements IterableReadableStreamInterface<T>\n{\n public reader: ReadableStreamDefaultReader<T>;\n\n ensureReader() {\n if (!this.reader) {\n this.reader = this.getReader();\n }\n }\n\n async next(): Promise<IteratorResult<T>> {\n this.ensureReader();\n try {\n const result = await this.reader.read();\n if (result.done) {\n this.reader.releaseLock(); // release lock when stream becomes closed\n return {\n done: true,\n value: undefined,\n };\n } else {\n return {\n done: false,\n value: result.value,\n };\n }\n } catch (e) {\n this.reader.releaseLock(); // release lock when stream becomes errored\n throw e;\n }\n }\n\n async return(): Promise<IteratorResult<T>> {\n this.ensureReader();\n // If wrapped in a Node stream, cancel is already called.\n if (this.locked) {\n const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet\n this.reader.releaseLock(); // release lock first\n await cancelPromise; // now await it\n }\n return { done: true, value: undefined };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async throw(e: any): Promise<IteratorResult<T>> {\n this.ensureReader();\n if (this.locked) {\n const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet\n this.reader.releaseLock(); // release lock first\n await cancelPromise; // now await it\n }\n throw e;\n }\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore Not present in Node 18 types, required in latest Node 22\n async [Symbol.asyncDispose]() {\n await this.return();\n }\n\n [Symbol.asyncIterator]() {\n return this;\n }\n\n static fromReadableStream<T>(stream: ReadableStream<T>) {\n // From https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams#reading_the_stream\n const reader = stream.getReader();\n return new IterableReadableStream<T>({\n start(controller) {\n return pump();\n function pump(): Promise<T | undefined> {\n return reader.read().then(({ done, value }) => {\n // When no more data needs to be consumed, close the stream\n if (done) {\n controller.close();\n return;\n }\n // Enqueue the next data chunk into our target stream\n controller.enqueue(value);\n return pump();\n });\n }\n },\n cancel() {\n reader.releaseLock();\n },\n });\n }\n\n static fromAsyncGenerator<T>(generator: AsyncGenerator<T>) {\n return new IterableReadableStream<T>({\n async pull(controller) {\n const { value, done } = await generator.next();\n // When no more data needs to be consumed, close the stream\n if (done) {\n controller.close();\n }\n // Fix: `else if (value)` will hang the streaming when nullish value (e.g. empty string) is pulled\n controller.enqueue(value);\n },\n async cancel(reason) {\n await generator.return(reason);\n },\n });\n }\n}\n"],"mappings":"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"}
|
package/dist/utils/stream.d.cts
CHANGED
|
@@ -3,17 +3,31 @@ type IterableReadableStreamInterface<T> = ReadableStream<T> & AsyncIterable<T>;
|
|
|
3
3
|
/**
|
|
4
4
|
* Options for streaming with automatic retry logic.
|
|
5
5
|
*/
|
|
6
|
+
/**
|
|
7
|
+
* How {@link idleReconnectStream} decides when the connection is idle.
|
|
8
|
+
*
|
|
9
|
+
* - A `number` is a fixed idle window in milliseconds: the watchdog arms
|
|
10
|
+
* immediately (even before the first byte) and trips after that long with no
|
|
11
|
+
* activity. Use when you know your server's behaviour and want guaranteed
|
|
12
|
+
* coverage from t=0; it does not depend on heartbeats.
|
|
13
|
+
* - `"auto"` is heartbeat-adaptive: the watchdog stays dormant until it has
|
|
14
|
+
* observed the server's SSE keep-alive comments (e.g. LangGraph Platform's
|
|
15
|
+
* `: heartbeat` every ~5s), then arms with a window derived from the
|
|
16
|
+
* observed cadence. On a server that never sends heartbeats it never arms,
|
|
17
|
+
* so it can't false-fire during a legitimately quiet period.
|
|
18
|
+
*/
|
|
19
|
+
type IdleReconnectMode = number | "auto";
|
|
6
20
|
declare class IterableReadableStream<T> extends ReadableStream<T> implements IterableReadableStreamInterface<T> {
|
|
7
|
-
[Symbol.asyncDispose]: () => Promise<void>;
|
|
8
21
|
reader: ReadableStreamDefaultReader<T>;
|
|
9
22
|
ensureReader(): void;
|
|
10
23
|
next(): Promise<IteratorResult<T>>;
|
|
11
24
|
return(): Promise<IteratorResult<T>>;
|
|
12
25
|
throw(e: any): Promise<IteratorResult<T>>;
|
|
26
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
13
27
|
[Symbol.asyncIterator](): this;
|
|
14
28
|
static fromReadableStream<T>(stream: ReadableStream<T>): IterableReadableStream<T>;
|
|
15
29
|
static fromAsyncGenerator<T>(generator: AsyncGenerator<T>): IterableReadableStream<T>;
|
|
16
30
|
}
|
|
17
31
|
//#endregion
|
|
18
|
-
export { IterableReadableStream };
|
|
32
|
+
export { IdleReconnectMode, IterableReadableStream };
|
|
19
33
|
//# sourceMappingURL=stream.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.d.cts","names":[],"sources":["../../src/utils/stream.ts"],"mappings":";KAGK,+BAAA,MAAqC,cAAA,CAAe,CAAA,IAAK,aAAA,CAAc,CAAA
|
|
1
|
+
{"version":3,"file":"stream.d.cts","names":[],"sources":["../../src/utils/stream.ts"],"mappings":";KAGK,+BAAA,MAAqC,cAAA,CAAe,CAAA,IAAK,aAAA,CAAc,CAAA;;;;;;;;;;;;;;;;;KAwFhE,iBAAA;AAAA,cA2PC,sBAAA,YACH,cAAA,CAAe,CAAA,aACZ,+BAAA,CAAgC,CAAA;EAEpC,MAAA,EAAQ,2BAAA,CAA4B,CAAA;EAE3C,YAAA,CAAA;EAMM,IAAA,CAAA,GAAQ,OAAA,CAAQ,cAAA,CAAe,CAAA;EAsB/B,MAAA,CAAA,GAAU,OAAA,CAAQ,cAAA,CAAe,CAAA;EAYjC,KAAA,CAAM,CAAA,QAAS,OAAA,CAAQ,cAAA,CAAe,CAAA;EAAA,CAYrC,MAAA,CAAO,YAAA,KAAa,OAAA;EAAA,CAI1B,MAAA,CAAO,aAAA;EAAA,OAID,kBAAA,GAAA,CAAsB,MAAA,EAAQ,cAAA,CAAe,CAAA,IAAE,sBAAA,CAAA,CAAA;EAAA,OAyB/C,kBAAA,GAAA,CAAsB,SAAA,EAAW,cAAA,CAAe,CAAA,IAAE,sBAAA,CAAA,CAAA;AAAA"}
|