@langchain/langgraph 1.3.5 → 1.3.7
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/stream/index.cjs +1 -0
- package/dist/stream/index.d.cts +2 -1
- package/dist/stream/index.d.ts +2 -1
- package/dist/stream/index.js +1 -0
- package/dist/stream/mux.cjs +13 -3
- package/dist/stream/mux.cjs.map +1 -1
- package/dist/stream/mux.d.cts +2 -2
- package/dist/stream/mux.d.cts.map +1 -1
- package/dist/stream/mux.d.ts +2 -2
- package/dist/stream/mux.d.ts.map +1 -1
- package/dist/stream/mux.js +13 -3
- package/dist/stream/mux.js.map +1 -1
- package/dist/stream/stream-channel.cjs +3 -3
- package/dist/stream/stream-channel.cjs.map +1 -1
- package/dist/stream/stream-channel.d.cts +12 -4
- package/dist/stream/stream-channel.d.cts.map +1 -1
- package/dist/stream/stream-channel.d.ts +12 -4
- package/dist/stream/stream-channel.d.ts.map +1 -1
- package/dist/stream/stream-channel.js +3 -3
- package/dist/stream/stream-channel.js.map +1 -1
- package/dist/stream/subscription.cjs +136 -0
- package/dist/stream/subscription.cjs.map +1 -0
- package/dist/stream/subscription.d.cts +94 -0
- package/dist/stream/subscription.d.cts.map +1 -0
- package/dist/stream/subscription.d.ts +94 -0
- package/dist/stream/subscription.d.ts.map +1 -0
- package/dist/stream/subscription.js +131 -0
- package/dist/stream/subscription.js.map +1 -0
- package/dist/stream/types.cjs.map +1 -1
- package/dist/stream/types.d.cts +3 -3
- package/dist/stream/types.d.ts +3 -3
- package/dist/stream/types.js.map +1 -1
- package/dist/stream.cjs +16 -0
- package/dist/stream.d.cts +5 -0
- package/dist/stream.d.ts +5 -0
- package/dist/stream.js +4 -0
- package/package.json +16 -4
package/dist/stream/index.cjs
CHANGED
package/dist/stream/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AgentStatus, ChatModelStream as ChatModelStream$1, InferExtensions, InterruptPayload, LifecycleCause, LifecycleData, MessagesEventData, Namespace, NativeStreamTransformer, ProtocolEvent, StreamEmitter, StreamTransformer, ToolCallStatus, ToolCallStream, ToolsEventData, UpdatesEventData, UsageInfo, isNativeTransformer } from "./types.cjs";
|
|
2
|
-
import { StreamChannel } from "./stream-channel.cjs";
|
|
2
|
+
import { StreamChannel, isStreamChannel } from "./stream-channel.cjs";
|
|
3
3
|
import { ConvertToProtocolEventOptions, STREAM_EVENTS_V3_MODES, convertToProtocolEvent, isCheckpointEnvelope } from "./convert.cjs";
|
|
4
4
|
import { REJECT_VALUES, RESOLVE_VALUES, StreamHandle, StreamMux, SubgraphDiscovery } from "./mux.cjs";
|
|
5
5
|
import { LifecycleEntry, LifecycleTransformerOptions } from "./transformers/types.cjs";
|
|
@@ -8,5 +8,6 @@ import { createMessagesTransformer } from "./transformers/messages.cjs";
|
|
|
8
8
|
import { SubgraphDiscoveryProjection, SubgraphDiscoveryTransformerOptions, createSubgraphDiscoveryTransformer, filterSubgraphHandles } from "./transformers/subgraphs.cjs";
|
|
9
9
|
import { createValuesTransformer } from "./transformers/values.cjs";
|
|
10
10
|
import { CreateGraphRunStreamOptions, GraphRunStream, SET_LIFECYCLE_ITERABLE, SET_MESSAGES_ITERABLE, SET_VALUES_LOG, SubgraphRunStream, createGraphRunStream } from "./run-stream.cjs";
|
|
11
|
+
import { MatchableEvent, SUPPORTED_CHANNELS, inferChannel, isPrefixMatch, isSupportedChannel, matchesSubscription, normalizeNamespaceSegment } from "./subscription.cjs";
|
|
11
12
|
import { ChatModelStream as ChatModelStreamImpl } from "@langchain/core/language_models/stream";
|
|
12
13
|
export { ChatModelStreamImpl };
|
package/dist/stream/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AgentStatus, ChatModelStream as ChatModelStream$1, InferExtensions, InterruptPayload, LifecycleCause, LifecycleData, MessagesEventData, Namespace, NativeStreamTransformer, ProtocolEvent, StreamEmitter, StreamTransformer, ToolCallStatus, ToolCallStream, ToolsEventData, UpdatesEventData, UsageInfo, isNativeTransformer } from "./types.js";
|
|
2
|
-
import { StreamChannel } from "./stream-channel.js";
|
|
2
|
+
import { StreamChannel, isStreamChannel } from "./stream-channel.js";
|
|
3
3
|
import { ConvertToProtocolEventOptions, STREAM_EVENTS_V3_MODES, convertToProtocolEvent, isCheckpointEnvelope } from "./convert.js";
|
|
4
4
|
import { REJECT_VALUES, RESOLVE_VALUES, StreamHandle, StreamMux, SubgraphDiscovery } from "./mux.js";
|
|
5
5
|
import { LifecycleEntry, LifecycleTransformerOptions } from "./transformers/types.js";
|
|
@@ -8,5 +8,6 @@ import { createMessagesTransformer } from "./transformers/messages.js";
|
|
|
8
8
|
import { SubgraphDiscoveryProjection, SubgraphDiscoveryTransformerOptions, createSubgraphDiscoveryTransformer, filterSubgraphHandles } from "./transformers/subgraphs.js";
|
|
9
9
|
import { createValuesTransformer } from "./transformers/values.js";
|
|
10
10
|
import { CreateGraphRunStreamOptions, GraphRunStream, SET_LIFECYCLE_ITERABLE, SET_MESSAGES_ITERABLE, SET_VALUES_LOG, SubgraphRunStream, createGraphRunStream } from "./run-stream.js";
|
|
11
|
+
import { MatchableEvent, SUPPORTED_CHANNELS, inferChannel, isPrefixMatch, isSupportedChannel, matchesSubscription, normalizeNamespaceSegment } from "./subscription.js";
|
|
11
12
|
import { ChatModelStream as ChatModelStreamImpl } from "@langchain/core/language_models/stream";
|
|
12
13
|
export { ChatModelStreamImpl };
|
package/dist/stream/index.js
CHANGED
|
@@ -8,5 +8,6 @@ import "./transformers/values.js";
|
|
|
8
8
|
import "./transformers/index.js";
|
|
9
9
|
import "./types.js";
|
|
10
10
|
import "./run-stream.js";
|
|
11
|
+
import "./subscription.js";
|
|
11
12
|
import { ChatModelStream as ChatModelStreamImpl } from "@langchain/core/language_models/stream";
|
|
12
13
|
export { ChatModelStreamImpl };
|
package/dist/stream/mux.cjs
CHANGED
|
@@ -2,6 +2,15 @@ const require_constants = require("../constants.cjs");
|
|
|
2
2
|
const require_convert = require("./convert.cjs");
|
|
3
3
|
const require_stream_channel = require("./stream-channel.cjs");
|
|
4
4
|
//#region src/stream/mux.ts
|
|
5
|
+
/** Wire prefix for user-defined {@link StreamChannel} auto-forwards. */
|
|
6
|
+
const EXTENSION_CHANNEL_PREFIX = "custom:";
|
|
7
|
+
/**
|
|
8
|
+
* Protocol method for a user-defined (extension) {@link StreamChannel}.
|
|
9
|
+
* Matches Python's `StreamMux._bind_and_wire` (`f"custom:{value.name}"`).
|
|
10
|
+
*/
|
|
11
|
+
function extensionChannelMethod(channelName) {
|
|
12
|
+
return `${EXTENSION_CHANNEL_PREFIX}${channelName}`;
|
|
13
|
+
}
|
|
5
14
|
/**
|
|
6
15
|
* Structural `PromiseLike<T>` predicate — true for thenables including
|
|
7
16
|
* native promises, user-constructed `{ then }` objects, and helper
|
|
@@ -104,8 +113,8 @@ var StreamMux = class {
|
|
|
104
113
|
* Two projection shapes are recognised:
|
|
105
114
|
*
|
|
106
115
|
* - {@link StreamChannel} values — named channels forward each `push()`
|
|
107
|
-
* immediately as a protocol event
|
|
108
|
-
*
|
|
116
|
+
* immediately as a `custom:<channelName>` protocol event. Unnamed
|
|
117
|
+
* channels remain in-process-only.
|
|
109
118
|
*
|
|
110
119
|
* - `PromiseLike<unknown>` values — tracked as final-value
|
|
111
120
|
* projections and flushed on {@link close} as a single
|
|
@@ -124,11 +133,12 @@ var StreamMux = class {
|
|
|
124
133
|
if (require_stream_channel.isStreamChannel(value)) {
|
|
125
134
|
this.#channels.push(value);
|
|
126
135
|
if (typeof value.channelName !== "string") continue;
|
|
136
|
+
const method = extensionChannelMethod(value.channelName);
|
|
127
137
|
value._wire((item) => {
|
|
128
138
|
this._events.push({
|
|
129
139
|
type: "event",
|
|
130
140
|
seq: this.#nextEmitSeq++,
|
|
131
|
-
method
|
|
141
|
+
method,
|
|
132
142
|
params: {
|
|
133
143
|
namespace: this.#currentNamespace,
|
|
134
144
|
timestamp: Date.now(),
|
package/dist/stream/mux.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mux.cjs","names":["StreamChannel","#transformers","#channels","#streamMap","#latestValues","#interrupts","#finalValues","#closed","#error","isStreamChannel","#nextEmitSeq","#currentNamespace","#interrupted","isInterrupted","INTERRUPT","convertToProtocolEvent"],"sources":["../../src/stream/mux.ts"],"sourcesContent":["/**\n * StreamMux — central dispatcher with transformer pipeline.\n *\n * Routes raw stream chunks through registered StreamTransformers, then appends\n * the resulting ProtocolEvents to the main local channel. Also tracks\n * namespace discovery for SubgraphRunStream creation.\n *\n * lifecycle:\n * graph.streamEvents(input, { version: \"v3\" })\n * ├─ StreamMux starts pumping from graph.stream(…, { subgraphs: true })\n * ├─ For each ProtocolEvent:\n * │ ├─ transformer_1.process(event)\n * │ ├─ transformer_2.process(event)\n * │ └─ event appended to _events (unless suppressed)\n * └─ On close: transformer_n.finalize() called in registration order\n */\n\nimport type { StreamChunk } from \"../pregel/stream.js\";\nimport { INTERRUPT, isInterrupted, type Interrupt } from \"../constants.js\";\nimport { convertToProtocolEvent, STREAM_EVENTS_V3_MODES } from \"./convert.js\";\nimport { StreamChannel, isStreamChannel } from \"./stream-channel.js\";\nimport type {\n InterruptPayload,\n Namespace,\n ProtocolEvent,\n StreamEmitter,\n StreamTransformer,\n} from \"./types.js\";\n\nexport { STREAM_EVENTS_V3_MODES };\n\n/**\n * Structural `PromiseLike<T>` predicate — true for thenables including\n * native promises, user-constructed `{ then }` objects, and helper\n * wrappers. Used by {@link StreamMux.wireChannels} to detect final-value\n * projections distinctly from streaming `StreamChannel` values.\n */\nfunction isPromiseLike(value: unknown): value is PromiseLike<unknown> {\n return (\n value != null &&\n (typeof value === \"object\" || typeof value === \"function\") &&\n typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\n/**\n * Symbol key used by {@link StreamMux} to resolve the values promise on a\n * stream handle. Using a symbol keeps this off the public autocomplete surface.\n */\nexport const RESOLVE_VALUES: unique symbol = Symbol(\"resolveValues\");\n\n/**\n * Symbol key used by {@link StreamMux} to reject the values promise on a\n * stream handle. Using a symbol keeps this off the public autocomplete surface.\n */\nexport const REJECT_VALUES: unique symbol = Symbol(\"rejectValues\");\n\n/**\n * Minimal interface that {@link StreamMux} requires from stream handles\n * for lifecycle resolution. This avoids a direct dependency on\n * `GraphRunStream` / `SubgraphRunStream`.\n */\nexport interface StreamHandle {\n [RESOLVE_VALUES](values: unknown): void;\n [REJECT_VALUES](err: unknown): void;\n}\n\n/**\n * Factory function that creates a subgraph stream handle for a newly\n * discovered namespace.\n *\n * Historically consumed by {@link StreamMux} at construction time;\n * today factories are consumed by\n * `createSubgraphDiscoveryTransformer` (via its `createStream`\n * option). This shape is retained for consumers that still thread a\n * mux reference through the factory — the narrower transformer\n * option omits `mux` because it captures the mux in a closure.\n */\nexport type SubgraphStreamFactory = (\n path: Namespace,\n mux: StreamMux,\n discoveryStart: number,\n eventStart: number\n) => StreamHandle;\n\n/**\n * A discovered subgraph namespace paired with its run stream handle.\n */\nexport type SubgraphDiscovery = {\n ns: Namespace;\n stream: StreamHandle;\n};\n\n/**\n * Central event dispatcher that routes {@link ProtocolEvent}s through a\n * pipeline of {@link StreamTransformer}s, manages namespace discovery for\n * subgraph streams, and exposes async iteration over filtered event\n * sequences.\n *\n * One `StreamMux` instance exists per top-level\n * `streamEvents(..., { version: \"v3\" })` invocation.\n */\nexport class StreamMux {\n /** @internal All protocol events in arrival order (after reducer pipeline). */\n readonly _events = StreamChannel.local<ProtocolEvent>();\n\n /** @internal New-namespace discovery notifications. */\n readonly _discoveries = StreamChannel.local<SubgraphDiscovery>();\n\n /** Monotonic counter for auto-forwarded channel events. */\n #nextEmitSeq = 0;\n\n /** Whether the mux has been closed or failed. */\n #closed = false;\n\n /** The error passed to {@link fail}, if any. */\n #error: unknown;\n\n /** Whether the run was interrupted. */\n #interrupted = false;\n\n /**\n * Namespace of the event currently being processed by\n * {@link push}. Read by {@link StreamChannel} wiring callbacks so\n * auto-forwarded events inherit the triggering event's namespace.\n */\n #currentNamespace: Namespace = [];\n\n readonly #transformers: StreamTransformer<unknown>[] = [];\n readonly #channels: StreamChannel<unknown>[] = [];\n readonly #streamMap = new Map<string, StreamHandle>();\n readonly #latestValues = new Map<string, Record<string, unknown>>();\n readonly #interrupts: InterruptPayload[] = [];\n\n /**\n * Final-value projection keys tracked for remote surfacing. Populated\n * by {@link wireChannels} when a transformer's projection contains a\n * `PromiseLike` value. Each entry is flushed as a `custom:<name>`\n * protocol event during {@link close} so that remote clients can\n * observe final-value transformers via `thread.extensions.<name>`.\n */\n readonly #finalValues: Array<{ name: string; promise: Promise<unknown> }> =\n [];\n\n /**\n * Associates a pre-existing stream handle with a namespace so that\n * {@link close} can resolve its values promise later.\n *\n * @param path - The namespace path to register.\n * @param stream - The run stream handle for that namespace.\n */\n register(path: Namespace, stream: StreamHandle): void {\n this.#streamMap.set(nsKey(path), stream);\n }\n\n /**\n * Registers a transformer and replays all buffered events through it so\n * it catches up with events already processed by the mux. When the event\n * log is empty (typical at construction time) the replay is a no-op.\n *\n * The transformer must already have been initialised (i.e. `init()` called\n * and any projection wired). The sequence is:\n *\n * 1. Snapshot the current event log length.\n * 2. Append the transformer so future {@link push} calls reach it.\n * 3. Replay events `[0, snapshot)` through `process()`.\n * 4. If the mux is already closed, call `finalize()` (or `fail()`)\n * immediately so the transformer's log/channel terminates cleanly.\n *\n * @param transformer - An already-initialised transformer to register.\n */\n addTransformer(transformer: StreamTransformer<unknown>): void {\n const snapshot = this._events.size;\n this.#transformers.push(transformer);\n\n // Hand the transformer a narrow emitter handle *before* replay so\n // synthetic-emission transformers (e.g. deepagents\n // `SubagentTransformer`) can inject events into the mux during\n // their own `process()` calls — including the initial replay\n // triggered just below.\n if (transformer.onRegister) {\n const emitter: StreamEmitter = {\n // Transformer-originated events use a placeholder `seq` of\n // `0`. `push()` is the single authority for sequence numbers\n // and will re-stamp this event with the next monotonically\n // increasing value.\n push: (ns, event) => this.push(ns, event),\n };\n transformer.onRegister(emitter);\n }\n\n for (let i = 0; i < snapshot; i += 1) {\n transformer.process(this._events.get(i));\n }\n\n if (this.#closed) {\n if (this.#error !== undefined) {\n transformer.fail?.(this.#error);\n } else {\n transformer.finalize?.();\n }\n }\n }\n\n /**\n * Scans a transformer projection for streaming and final-value primitives.\n * Remote stream channels are wired to auto-forward to the protocol event\n * stream; local stream channels are tracked for lifecycle only.\n *\n * Two projection shapes are recognised:\n *\n * - {@link StreamChannel} values — named channels forward each `push()`\n * immediately as a protocol event on the channel's declared\n * `channelName` method. Unnamed channels remain in-process-only.\n *\n * - `PromiseLike<unknown>` values — tracked as final-value\n * projections and flushed on {@link close} as a single\n * `custom:<key>` event, where `<key>` is the projection key.\n * This mirrors the in-process `await run.extensions.<key>`\n * ergonomics on remote clients via\n * `await thread.extensions.<key>`.\n *\n * Plain values that are neither are ignored — they remain in-process-only,\n * matching prior behaviour.\n *\n * @param projection - The object returned by `transformer.init()`.\n */\n wireChannels(projection: Record<string, unknown>): void {\n for (const [key, value] of Object.entries(projection)) {\n if (isStreamChannel(value)) {\n this.#channels.push(value);\n if (typeof value.channelName !== \"string\") {\n continue;\n }\n value._wire((item: unknown) => {\n this._events.push({\n type: \"event\",\n seq: this.#nextEmitSeq++,\n method: value.channelName as ProtocolEvent[\"method\"],\n params: {\n namespace: this.#currentNamespace,\n timestamp: Date.now(),\n data: item,\n },\n });\n });\n continue;\n }\n if (isPromiseLike(value)) {\n this.#finalValues.push({\n name: key,\n promise: Promise.resolve(value),\n });\n }\n }\n }\n\n /**\n * Distributes an event through the transformer pipeline, then appends it to\n * the main event log.\n *\n * Subgraph discovery (materializing a {@link StreamHandle} for each\n * newly observed top-level namespace) is handled by the\n * {@link createSubgraphDiscoveryTransformer} when installed, not here.\n *\n * @param ns - The namespace path that produced the event.\n * @param event - The protocol event to process and store.\n */\n push(ns: Namespace, event: ProtocolEvent): void {\n if (event.method === \"values\") {\n this.#latestValues.set(\n nsKey(ns),\n event.params.data as Record<string, unknown>\n );\n }\n\n // Save the outer namespace so re-entrant `push()` calls (e.g. from\n // `StreamTransformer.onRegister` emitters synthesizing events\n // inside a transformer's `process()`) can set their own namespace\n // without clobbering the outer scope's `StreamChannel` routing\n // when control returns to the outer transformer loop.\n const outerNamespace = this.#currentNamespace;\n this.#currentNamespace = ns;\n\n let keep = true;\n for (const transformer of this.#transformers) {\n if (!transformer.process(event)) {\n keep = false;\n }\n }\n\n this.#currentNamespace = outerNamespace;\n\n if (keep) {\n // The mux is the single authority for sequence numbers. Callers\n // (the `pump`, transformer emitters, channel forwarders) pass a\n // placeholder `seq`; we re-stamp every event here so the log is\n // strictly monotonic across all origination paths. Stamping\n // happens *after* `process()` so that any channel-forwarded\n // events pushed during processing get earlier sequence numbers\n // than the triggering event, matching their in-order appearance\n // in `_events`.\n this._events.push({ ...event, seq: this.#nextEmitSeq++ });\n }\n }\n\n /**\n * Gracefully ends the stream: resolves values promises on all known\n * streams, finalizes every transformer, auto-closes streaming\n * channels, flushes any final-value projections as `custom:<name>`\n * events, and closes both event logs.\n *\n * When final-value projections are present, `_events.close()` is\n * deferred until every tracked projection promise has settled so\n * remote consumers observe the flushed values before their event\n * stream ends. Callers do not need to await — `close()` returns\n * synchronously and any downstream consumer iterating\n * {@link _events} naturally waits for the final events.\n */\n close(): void {\n this.#closed = true;\n for (const [key, values] of this.#latestValues.entries()) {\n const ns = key ? key.split(\"\\x00\") : [];\n const stream = this.#streamMap.get(nsKey(ns));\n stream?.[RESOLVE_VALUES](values);\n }\n\n const finalizePromises: PromiseLike<void>[] = [];\n for (const transformer of this.#transformers) {\n const result = transformer.finalize?.();\n if (\n result != null &&\n typeof (result as PromiseLike<void>).then === \"function\"\n ) {\n finalizePromises.push(result as PromiseLike<void>);\n }\n }\n\n for (const channel of this.#channels) {\n channel._close();\n }\n\n const finalValues = this.#finalValues;\n if (finalValues.length === 0 && finalizePromises.length === 0) {\n this._events.close();\n this._discoveries.close();\n } else {\n void Promise.allSettled([\n ...finalizePromises,\n ...finalValues.map(async ({ name, promise }) => {\n try {\n const resolved = await promise;\n if (!this._events.done) {\n this._events.push({\n type: \"event\",\n seq: this.#nextEmitSeq++,\n method: \"custom\",\n params: {\n namespace: [],\n timestamp: Date.now(),\n data: { name, payload: resolved },\n },\n });\n }\n } catch {\n // Rejected final-value projections are intentionally dropped\n // so a single failing extension can't poison the protocol\n // stream. The corresponding in-process Promise still\n // surfaces the rejection to its direct awaiters via the\n // transformer's own `fail()` hook.\n }\n }),\n ]).then(() => {\n this._events.close();\n this._discoveries.close();\n });\n }\n\n for (const stream of this.#streamMap.values()) {\n stream[RESOLVE_VALUES](undefined);\n }\n }\n\n /**\n * Propagates a failure to all transformers, channels, event logs, and\n * stream handles.\n *\n * @param err - The error that caused the run to fail.\n */\n fail(err: unknown): void {\n this.#closed = true;\n this.#error = err;\n for (const transformer of this.#transformers) {\n transformer.fail?.(err);\n }\n for (const channel of this.#channels) {\n channel._fail(err);\n }\n this._events.fail(err);\n this._discoveries.fail(err);\n for (const stream of this.#streamMap.values()) {\n stream[REJECT_VALUES](err);\n }\n }\n\n /**\n * Records that the run was interrupted, appending the supplied payloads\n * for later retrieval.\n *\n * @param interrupts - The interrupt payloads to store.\n */\n markInterrupted(interrupts: InterruptPayload[]): void {\n this.#interrupted = true;\n this.#interrupts.push(...interrupts);\n }\n\n /**\n * Whether the run ended due to an interrupt.\n *\n * @returns `true` if {@link markInterrupted} was called.\n */\n get interrupted(): boolean {\n return this.#interrupted;\n }\n\n /**\n * All interrupt payloads collected during the run.\n *\n * @returns A readonly view of the accumulated interrupt payloads.\n */\n get interrupts(): readonly InterruptPayload[] {\n return this.#interrupts;\n }\n\n /**\n * Returns an async iterator that yields only events whose namespace\n * starts with {@link path}.\n *\n * @param path - Namespace prefix to filter on.\n * @param startAt - Zero-based index into the event log to begin from.\n * @returns An async iterator over matching {@link ProtocolEvent}s.\n */\n subscribeEvents(path: Namespace, startAt = 0): AsyncIterator<ProtocolEvent> {\n const base = this._events.iterate(startAt);\n return {\n async next(): Promise<IteratorResult<ProtocolEvent>> {\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const result = await base.next();\n if (result.done) return result;\n if (hasPrefix(result.value.params.namespace, path)) {\n return result;\n }\n }\n },\n };\n }\n}\n\n/**\n * Background consumer that drains a raw `graph.stream()` source into a\n * {@link StreamMux}. Converts each chunk to a {@link ProtocolEvent} and\n * pushes it; calls {@link StreamMux.close} on normal completion or\n * {@link StreamMux.fail} on error.\n *\n * @param source - The async iterable of raw stream chunks from the engine.\n * @param mux - The mux instance to feed.\n * @returns A promise that resolves when the source is fully consumed.\n */\nexport async function pump(\n source: AsyncIterable<StreamChunk>,\n mux: StreamMux\n): Promise<void> {\n let seq = 0;\n try {\n for await (const chunk of source) {\n const [ns, mode, payload] = chunk;\n\n // Detect interrupt payloads attached to values-mode chunks.\n if (mode === \"values\" && isInterrupted(payload)) {\n const interrupts = (payload as { [INTERRUPT]: Interrupt[] })[INTERRUPT];\n mux.markInterrupted(\n interrupts.map((i) => ({\n interruptId: i.id ?? \"\",\n payload: i.value,\n }))\n );\n }\n\n const events = convertToProtocolEvent({\n namespace: ns,\n mode,\n payload,\n seq,\n });\n seq += events.length;\n for (const event of events) {\n mux.push(ns, event);\n }\n }\n } catch (err) {\n mux.fail(err);\n return;\n }\n mux.close();\n}\n\n/**\n * Serialises a {@link Namespace} array into a single string key using the\n * null byte (`\\x00`) as separator, suitable for `Map`/`Set` lookups.\n *\n * @param ns - The namespace segments to join.\n * @returns A null-byte-joined string key.\n */\nexport function nsKey(ns: Namespace): string {\n return ns.join(\"\\x00\");\n}\n\n/**\n * Tests whether {@link ns} starts with every segment in {@link prefix}.\n *\n * @param ns - The full namespace to check.\n * @param prefix - The prefix to match against.\n * @returns `true` if `ns` begins with `prefix` segment-by-segment.\n */\nexport function hasPrefix(ns: Namespace, prefix: Namespace): boolean {\n if (prefix.length > ns.length) return false;\n for (let i = 0; i < prefix.length; i += 1) {\n if (ns[i] !== prefix[i]) return false;\n }\n return true;\n}\n"],"mappings":";;;;;;;;;;AAqCA,SAAS,cAAc,OAA+C;AACpE,QACE,SAAS,SACR,OAAO,UAAU,YAAY,OAAO,UAAU,eAC/C,OAAQ,MAA6B,SAAS;;;;;;AAQlD,MAAa,iBAAgC,OAAO,gBAAgB;;;;;AAMpE,MAAa,gBAA+B,OAAO,eAAe;;;;;;;;;;AA+ClE,IAAa,YAAb,MAAuB;;CAErB,UAAmBA,uBAAAA,cAAc,OAAsB;;CAGvD,eAAwBA,uBAAAA,cAAc,OAA0B;;CAGhE,eAAe;;CAGf,UAAU;;CAGV;;CAGA,eAAe;;;;;;CAOf,oBAA+B,EAAE;CAEjC,gBAAuD,EAAE;CACzD,YAA+C,EAAE;CACjD,6BAAsB,IAAI,KAA2B;CACrD,gCAAyB,IAAI,KAAsC;CACnE,cAA2C,EAAE;;;;;;;;CAS7C,eACE,EAAE;;;;;;;;CASJ,SAAS,MAAiB,QAA4B;AACpD,QAAA,UAAgB,IAAI,MAAM,KAAK,EAAE,OAAO;;;;;;;;;;;;;;;;;;CAmB1C,eAAe,aAA+C;EAC5D,MAAM,WAAW,KAAK,QAAQ;AAC9B,QAAA,aAAmB,KAAK,YAAY;AAOpC,MAAI,YAAY,WAQd,aAAY,WAPmB,EAK7B,OAAO,IAAI,UAAU,KAAK,KAAK,IAAI,MAAM,EAC1C,CAC8B;AAGjC,OAAK,IAAI,IAAI,GAAG,IAAI,UAAU,KAAK,EACjC,aAAY,QAAQ,KAAK,QAAQ,IAAI,EAAE,CAAC;AAG1C,MAAI,MAAA,OACF,KAAI,MAAA,UAAgB,KAAA,EAClB,aAAY,OAAO,MAAA,MAAY;MAE/B,aAAY,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;CA4B9B,aAAa,YAA2C;AACtD,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,WAAW,EAAE;AACrD,OAAIS,uBAAAA,gBAAgB,MAAM,EAAE;AAC1B,UAAA,SAAe,KAAK,MAAM;AAC1B,QAAI,OAAO,MAAM,gBAAgB,SAC/B;AAEF,UAAM,OAAO,SAAkB;AAC7B,UAAK,QAAQ,KAAK;MAChB,MAAM;MACN,KAAK,MAAA;MACL,QAAQ,MAAM;MACd,QAAQ;OACN,WAAW,MAAA;OACX,WAAW,KAAK,KAAK;OACrB,MAAM;OACP;MACF,CAAC;MACF;AACF;;AAEF,OAAI,cAAc,MAAM,CACtB,OAAA,YAAkB,KAAK;IACrB,MAAM;IACN,SAAS,QAAQ,QAAQ,MAAM;IAChC,CAAC;;;;;;;;;;;;;;CAgBR,KAAK,IAAe,OAA4B;AAC9C,MAAI,MAAM,WAAW,SACnB,OAAA,aAAmB,IACjB,MAAM,GAAG,EACT,MAAM,OAAO,KACd;EAQH,MAAM,iBAAiB,MAAA;AACvB,QAAA,mBAAyB;EAEzB,IAAI,OAAO;AACX,OAAK,MAAM,eAAe,MAAA,aACxB,KAAI,CAAC,YAAY,QAAQ,MAAM,CAC7B,QAAO;AAIX,QAAA,mBAAyB;AAEzB,MAAI,KASF,MAAK,QAAQ,KAAK;GAAE,GAAG;GAAO,KAAK,MAAA;GAAqB,CAAC;;;;;;;;;;;;;;;CAiB7D,QAAc;AACZ,QAAA,SAAe;AACf,OAAK,MAAM,CAAC,KAAK,WAAW,MAAA,aAAmB,SAAS,EAAE;GACxD,MAAM,KAAK,MAAM,IAAI,MAAM,KAAO,GAAG,EAAE;AACxB,SAAA,UAAgB,IAAI,MAAM,GAAG,CAAC,GACpC,gBAAgB,OAAO;;EAGlC,MAAM,mBAAwC,EAAE;AAChD,OAAK,MAAM,eAAe,MAAA,cAAoB;GAC5C,MAAM,SAAS,YAAY,YAAY;AACvC,OACE,UAAU,QACV,OAAQ,OAA6B,SAAS,WAE9C,kBAAiB,KAAK,OAA4B;;AAItD,OAAK,MAAM,WAAW,MAAA,SACpB,SAAQ,QAAQ;EAGlB,MAAM,cAAc,MAAA;AACpB,MAAI,YAAY,WAAW,KAAK,iBAAiB,WAAW,GAAG;AAC7D,QAAK,QAAQ,OAAO;AACpB,QAAK,aAAa,OAAO;QAEpB,SAAQ,WAAW,CACtB,GAAG,kBACH,GAAG,YAAY,IAAI,OAAO,EAAE,MAAM,cAAc;AAC9C,OAAI;IACF,MAAM,WAAW,MAAM;AACvB,QAAI,CAAC,KAAK,QAAQ,KAChB,MAAK,QAAQ,KAAK;KAChB,MAAM;KACN,KAAK,MAAA;KACL,QAAQ;KACR,QAAQ;MACN,WAAW,EAAE;MACb,WAAW,KAAK,KAAK;MACrB,MAAM;OAAE;OAAM,SAAS;OAAU;MAClC;KACF,CAAC;WAEE;IAOR,CACH,CAAC,CAAC,WAAW;AACZ,QAAK,QAAQ,OAAO;AACpB,QAAK,aAAa,OAAO;IACzB;AAGJ,OAAK,MAAM,UAAU,MAAA,UAAgB,QAAQ,CAC3C,QAAO,gBAAgB,KAAA,EAAU;;;;;;;;CAUrC,KAAK,KAAoB;AACvB,QAAA,SAAe;AACf,QAAA,QAAc;AACd,OAAK,MAAM,eAAe,MAAA,aACxB,aAAY,OAAO,IAAI;AAEzB,OAAK,MAAM,WAAW,MAAA,SACpB,SAAQ,MAAM,IAAI;AAEpB,OAAK,QAAQ,KAAK,IAAI;AACtB,OAAK,aAAa,KAAK,IAAI;AAC3B,OAAK,MAAM,UAAU,MAAA,UAAgB,QAAQ,CAC3C,QAAO,eAAe,IAAI;;;;;;;;CAU9B,gBAAgB,YAAsC;AACpD,QAAA,cAAoB;AACpB,QAAA,WAAiB,KAAK,GAAG,WAAW;;;;;;;CAQtC,IAAI,cAAuB;AACzB,SAAO,MAAA;;;;;;;CAQT,IAAI,aAA0C;AAC5C,SAAO,MAAA;;;;;;;;;;CAWT,gBAAgB,MAAiB,UAAU,GAAiC;EAC1E,MAAM,OAAO,KAAK,QAAQ,QAAQ,QAAQ;AAC1C,SAAO,EACL,MAAM,OAA+C;AAEnD,UAAO,MAAM;IACX,MAAM,SAAS,MAAM,KAAK,MAAM;AAChC,QAAI,OAAO,KAAM,QAAO;AACxB,QAAI,UAAU,OAAO,MAAM,OAAO,WAAW,KAAK,CAChD,QAAO;;KAId;;;;;;;;;;;;;AAcL,eAAsB,KACpB,QACA,KACe;CACf,IAAI,MAAM;AACV,KAAI;AACF,aAAW,MAAM,SAAS,QAAQ;GAChC,MAAM,CAAC,IAAI,MAAM,WAAW;AAG5B,OAAI,SAAS,YAAYI,kBAAAA,cAAc,QAAQ,EAAE;IAC/C,MAAM,aAAc,QAAyCC,kBAAAA;AAC7D,QAAI,gBACF,WAAW,KAAK,OAAO;KACrB,aAAa,EAAE,MAAM;KACrB,SAAS,EAAE;KACZ,EAAE,CACJ;;GAGH,MAAM,SAASC,gBAAAA,uBAAuB;IACpC,WAAW;IACX;IACA;IACA;IACD,CAAC;AACF,UAAO,OAAO;AACd,QAAK,MAAM,SAAS,OAClB,KAAI,KAAK,IAAI,MAAM;;UAGhB,KAAK;AACZ,MAAI,KAAK,IAAI;AACb;;AAEF,KAAI,OAAO;;;;;;;;;AAUb,SAAgB,MAAM,IAAuB;AAC3C,QAAO,GAAG,KAAK,KAAO;;;;;;;;;AAUxB,SAAgB,UAAU,IAAe,QAA4B;AACnE,KAAI,OAAO,SAAS,GAAG,OAAQ,QAAO;AACtC,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,EACtC,KAAI,GAAG,OAAO,OAAO,GAAI,QAAO;AAElC,QAAO"}
|
|
1
|
+
{"version":3,"file":"mux.cjs","names":["StreamChannel","#transformers","#channels","#streamMap","#latestValues","#interrupts","#finalValues","#closed","#error","isStreamChannel","#nextEmitSeq","#currentNamespace","#interrupted","isInterrupted","INTERRUPT","convertToProtocolEvent"],"sources":["../../src/stream/mux.ts"],"sourcesContent":["/**\n * StreamMux — central dispatcher with transformer pipeline.\n *\n * Routes raw stream chunks through registered StreamTransformers, then appends\n * the resulting ProtocolEvents to the main local channel. Also tracks\n * namespace discovery for SubgraphRunStream creation.\n *\n * lifecycle:\n * graph.streamEvents(input, { version: \"v3\" })\n * ├─ StreamMux starts pumping from graph.stream(…, { subgraphs: true })\n * ├─ For each ProtocolEvent:\n * │ ├─ transformer_1.process(event)\n * │ ├─ transformer_2.process(event)\n * │ └─ event appended to _events (unless suppressed)\n * └─ On close: transformer_n.finalize() called in registration order\n */\n\nimport type { StreamChunk } from \"../pregel/stream.js\";\nimport { INTERRUPT, isInterrupted, type Interrupt } from \"../constants.js\";\nimport { convertToProtocolEvent, STREAM_EVENTS_V3_MODES } from \"./convert.js\";\nimport { StreamChannel, isStreamChannel } from \"./stream-channel.js\";\nimport type {\n InterruptPayload,\n Namespace,\n ProtocolEvent,\n StreamEmitter,\n StreamTransformer,\n} from \"./types.js\";\n\nexport { STREAM_EVENTS_V3_MODES };\n\n/** Wire prefix for user-defined {@link StreamChannel} auto-forwards. */\nconst EXTENSION_CHANNEL_PREFIX = \"custom:\";\n\n/**\n * Protocol method for a user-defined (extension) {@link StreamChannel}.\n * Matches Python's `StreamMux._bind_and_wire` (`f\"custom:{value.name}\"`).\n */\nfunction extensionChannelMethod(channelName: string): ProtocolEvent[\"method\"] {\n return `${EXTENSION_CHANNEL_PREFIX}${channelName}`;\n}\n\n/**\n * Structural `PromiseLike<T>` predicate — true for thenables including\n * native promises, user-constructed `{ then }` objects, and helper\n * wrappers. Used by {@link StreamMux.wireChannels} to detect final-value\n * projections distinctly from streaming `StreamChannel` values.\n */\nfunction isPromiseLike(value: unknown): value is PromiseLike<unknown> {\n return (\n value != null &&\n (typeof value === \"object\" || typeof value === \"function\") &&\n typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\n/**\n * Symbol key used by {@link StreamMux} to resolve the values promise on a\n * stream handle. Using a symbol keeps this off the public autocomplete surface.\n */\nexport const RESOLVE_VALUES: unique symbol = Symbol(\"resolveValues\");\n\n/**\n * Symbol key used by {@link StreamMux} to reject the values promise on a\n * stream handle. Using a symbol keeps this off the public autocomplete surface.\n */\nexport const REJECT_VALUES: unique symbol = Symbol(\"rejectValues\");\n\n/**\n * Minimal interface that {@link StreamMux} requires from stream handles\n * for lifecycle resolution. This avoids a direct dependency on\n * `GraphRunStream` / `SubgraphRunStream`.\n */\nexport interface StreamHandle {\n [RESOLVE_VALUES](values: unknown): void;\n [REJECT_VALUES](err: unknown): void;\n}\n\n/**\n * Factory function that creates a subgraph stream handle for a newly\n * discovered namespace.\n *\n * Historically consumed by {@link StreamMux} at construction time;\n * today factories are consumed by\n * `createSubgraphDiscoveryTransformer` (via its `createStream`\n * option). This shape is retained for consumers that still thread a\n * mux reference through the factory — the narrower transformer\n * option omits `mux` because it captures the mux in a closure.\n */\nexport type SubgraphStreamFactory = (\n path: Namespace,\n mux: StreamMux,\n discoveryStart: number,\n eventStart: number\n) => StreamHandle;\n\n/**\n * A discovered subgraph namespace paired with its run stream handle.\n */\nexport type SubgraphDiscovery = {\n ns: Namespace;\n stream: StreamHandle;\n};\n\n/**\n * Central event dispatcher that routes {@link ProtocolEvent}s through a\n * pipeline of {@link StreamTransformer}s, manages namespace discovery for\n * subgraph streams, and exposes async iteration over filtered event\n * sequences.\n *\n * One `StreamMux` instance exists per top-level\n * `streamEvents(..., { version: \"v3\" })` invocation.\n */\nexport class StreamMux {\n /** @internal All protocol events in arrival order (after reducer pipeline). */\n readonly _events = StreamChannel.local<ProtocolEvent>();\n\n /** @internal New-namespace discovery notifications. */\n readonly _discoveries = StreamChannel.local<SubgraphDiscovery>();\n\n /** Monotonic counter for auto-forwarded channel events. */\n #nextEmitSeq = 0;\n\n /** Whether the mux has been closed or failed. */\n #closed = false;\n\n /** The error passed to {@link fail}, if any. */\n #error: unknown;\n\n /** Whether the run was interrupted. */\n #interrupted = false;\n\n /**\n * Namespace of the event currently being processed by\n * {@link push}. Read by {@link StreamChannel} wiring callbacks so\n * auto-forwarded events inherit the triggering event's namespace.\n */\n #currentNamespace: Namespace = [];\n\n readonly #transformers: StreamTransformer<unknown>[] = [];\n readonly #channels: StreamChannel<unknown>[] = [];\n readonly #streamMap = new Map<string, StreamHandle>();\n readonly #latestValues = new Map<string, Record<string, unknown>>();\n readonly #interrupts: InterruptPayload[] = [];\n\n /**\n * Final-value projection keys tracked for remote surfacing. Populated\n * by {@link wireChannels} when a transformer's projection contains a\n * `PromiseLike` value. Each entry is flushed as a `custom:<name>`\n * protocol event during {@link close} so that remote clients can\n * observe final-value transformers via `thread.extensions.<name>`.\n */\n readonly #finalValues: Array<{ name: string; promise: Promise<unknown> }> =\n [];\n\n /**\n * Associates a pre-existing stream handle with a namespace so that\n * {@link close} can resolve its values promise later.\n *\n * @param path - The namespace path to register.\n * @param stream - The run stream handle for that namespace.\n */\n register(path: Namespace, stream: StreamHandle): void {\n this.#streamMap.set(nsKey(path), stream);\n }\n\n /**\n * Registers a transformer and replays all buffered events through it so\n * it catches up with events already processed by the mux. When the event\n * log is empty (typical at construction time) the replay is a no-op.\n *\n * The transformer must already have been initialised (i.e. `init()` called\n * and any projection wired). The sequence is:\n *\n * 1. Snapshot the current event log length.\n * 2. Append the transformer so future {@link push} calls reach it.\n * 3. Replay events `[0, snapshot)` through `process()`.\n * 4. If the mux is already closed, call `finalize()` (or `fail()`)\n * immediately so the transformer's log/channel terminates cleanly.\n *\n * @param transformer - An already-initialised transformer to register.\n */\n addTransformer(transformer: StreamTransformer<unknown>): void {\n const snapshot = this._events.size;\n this.#transformers.push(transformer);\n\n // Hand the transformer a narrow emitter handle *before* replay so\n // synthetic-emission transformers (e.g. deepagents\n // `SubagentTransformer`) can inject events into the mux during\n // their own `process()` calls — including the initial replay\n // triggered just below.\n if (transformer.onRegister) {\n const emitter: StreamEmitter = {\n // Transformer-originated events use a placeholder `seq` of\n // `0`. `push()` is the single authority for sequence numbers\n // and will re-stamp this event with the next monotonically\n // increasing value.\n push: (ns, event) => this.push(ns, event),\n };\n transformer.onRegister(emitter);\n }\n\n for (let i = 0; i < snapshot; i += 1) {\n transformer.process(this._events.get(i));\n }\n\n if (this.#closed) {\n if (this.#error !== undefined) {\n transformer.fail?.(this.#error);\n } else {\n transformer.finalize?.();\n }\n }\n }\n\n /**\n * Scans a transformer projection for streaming and final-value primitives.\n * Remote stream channels are wired to auto-forward to the protocol event\n * stream; local stream channels are tracked for lifecycle only.\n *\n * Two projection shapes are recognised:\n *\n * - {@link StreamChannel} values — named channels forward each `push()`\n * immediately as a `custom:<channelName>` protocol event. Unnamed\n * channels remain in-process-only.\n *\n * - `PromiseLike<unknown>` values — tracked as final-value\n * projections and flushed on {@link close} as a single\n * `custom:<key>` event, where `<key>` is the projection key.\n * This mirrors the in-process `await run.extensions.<key>`\n * ergonomics on remote clients via\n * `await thread.extensions.<key>`.\n *\n * Plain values that are neither are ignored — they remain in-process-only,\n * matching prior behaviour.\n *\n * @param projection - The object returned by `transformer.init()`.\n */\n wireChannels(projection: Record<string, unknown>): void {\n for (const [key, value] of Object.entries(projection)) {\n if (isStreamChannel(value)) {\n this.#channels.push(value);\n if (typeof value.channelName !== \"string\") {\n continue;\n }\n const method = extensionChannelMethod(value.channelName);\n value._wire((item: unknown) => {\n this._events.push({\n type: \"event\",\n seq: this.#nextEmitSeq++,\n method,\n params: {\n namespace: this.#currentNamespace,\n timestamp: Date.now(),\n data: item,\n },\n });\n });\n continue;\n }\n if (isPromiseLike(value)) {\n this.#finalValues.push({\n name: key,\n promise: Promise.resolve(value),\n });\n }\n }\n }\n\n /**\n * Distributes an event through the transformer pipeline, then appends it to\n * the main event log.\n *\n * Subgraph discovery (materializing a {@link StreamHandle} for each\n * newly observed top-level namespace) is handled by the\n * {@link createSubgraphDiscoveryTransformer} when installed, not here.\n *\n * @param ns - The namespace path that produced the event.\n * @param event - The protocol event to process and store.\n */\n push(ns: Namespace, event: ProtocolEvent): void {\n if (event.method === \"values\") {\n this.#latestValues.set(\n nsKey(ns),\n event.params.data as Record<string, unknown>\n );\n }\n\n // Save the outer namespace so re-entrant `push()` calls (e.g. from\n // `StreamTransformer.onRegister` emitters synthesizing events\n // inside a transformer's `process()`) can set their own namespace\n // without clobbering the outer scope's `StreamChannel` routing\n // when control returns to the outer transformer loop.\n const outerNamespace = this.#currentNamespace;\n this.#currentNamespace = ns;\n\n let keep = true;\n for (const transformer of this.#transformers) {\n if (!transformer.process(event)) {\n keep = false;\n }\n }\n\n this.#currentNamespace = outerNamespace;\n\n if (keep) {\n // The mux is the single authority for sequence numbers. Callers\n // (the `pump`, transformer emitters, channel forwarders) pass a\n // placeholder `seq`; we re-stamp every event here so the log is\n // strictly monotonic across all origination paths. Stamping\n // happens *after* `process()` so that any channel-forwarded\n // events pushed during processing get earlier sequence numbers\n // than the triggering event, matching their in-order appearance\n // in `_events`.\n this._events.push({ ...event, seq: this.#nextEmitSeq++ });\n }\n }\n\n /**\n * Gracefully ends the stream: resolves values promises on all known\n * streams, finalizes every transformer, auto-closes streaming\n * channels, flushes any final-value projections as `custom:<name>`\n * events, and closes both event logs.\n *\n * When final-value projections are present, `_events.close()` is\n * deferred until every tracked projection promise has settled so\n * remote consumers observe the flushed values before their event\n * stream ends. Callers do not need to await — `close()` returns\n * synchronously and any downstream consumer iterating\n * {@link _events} naturally waits for the final events.\n */\n close(): void {\n this.#closed = true;\n for (const [key, values] of this.#latestValues.entries()) {\n const ns = key ? key.split(\"\\x00\") : [];\n const stream = this.#streamMap.get(nsKey(ns));\n stream?.[RESOLVE_VALUES](values);\n }\n\n const finalizePromises: PromiseLike<void>[] = [];\n for (const transformer of this.#transformers) {\n const result = transformer.finalize?.();\n if (\n result != null &&\n typeof (result as PromiseLike<void>).then === \"function\"\n ) {\n finalizePromises.push(result as PromiseLike<void>);\n }\n }\n\n for (const channel of this.#channels) {\n channel._close();\n }\n\n const finalValues = this.#finalValues;\n if (finalValues.length === 0 && finalizePromises.length === 0) {\n this._events.close();\n this._discoveries.close();\n } else {\n void Promise.allSettled([\n ...finalizePromises,\n ...finalValues.map(async ({ name, promise }) => {\n try {\n const resolved = await promise;\n if (!this._events.done) {\n this._events.push({\n type: \"event\",\n seq: this.#nextEmitSeq++,\n method: \"custom\",\n params: {\n namespace: [],\n timestamp: Date.now(),\n data: { name, payload: resolved },\n },\n });\n }\n } catch {\n // Rejected final-value projections are intentionally dropped\n // so a single failing extension can't poison the protocol\n // stream. The corresponding in-process Promise still\n // surfaces the rejection to its direct awaiters via the\n // transformer's own `fail()` hook.\n }\n }),\n ]).then(() => {\n this._events.close();\n this._discoveries.close();\n });\n }\n\n for (const stream of this.#streamMap.values()) {\n stream[RESOLVE_VALUES](undefined);\n }\n }\n\n /**\n * Propagates a failure to all transformers, channels, event logs, and\n * stream handles.\n *\n * @param err - The error that caused the run to fail.\n */\n fail(err: unknown): void {\n this.#closed = true;\n this.#error = err;\n for (const transformer of this.#transformers) {\n transformer.fail?.(err);\n }\n for (const channel of this.#channels) {\n channel._fail(err);\n }\n this._events.fail(err);\n this._discoveries.fail(err);\n for (const stream of this.#streamMap.values()) {\n stream[REJECT_VALUES](err);\n }\n }\n\n /**\n * Records that the run was interrupted, appending the supplied payloads\n * for later retrieval.\n *\n * @param interrupts - The interrupt payloads to store.\n */\n markInterrupted(interrupts: InterruptPayload[]): void {\n this.#interrupted = true;\n this.#interrupts.push(...interrupts);\n }\n\n /**\n * Whether the run ended due to an interrupt.\n *\n * @returns `true` if {@link markInterrupted} was called.\n */\n get interrupted(): boolean {\n return this.#interrupted;\n }\n\n /**\n * All interrupt payloads collected during the run.\n *\n * @returns A readonly view of the accumulated interrupt payloads.\n */\n get interrupts(): readonly InterruptPayload[] {\n return this.#interrupts;\n }\n\n /**\n * Returns an async iterator that yields only events whose namespace\n * starts with {@link path}.\n *\n * @param path - Namespace prefix to filter on.\n * @param startAt - Zero-based index into the event log to begin from.\n * @returns An async iterator over matching {@link ProtocolEvent}s.\n */\n subscribeEvents(path: Namespace, startAt = 0): AsyncIterator<ProtocolEvent> {\n const base = this._events.iterate(startAt);\n return {\n async next(): Promise<IteratorResult<ProtocolEvent>> {\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const result = await base.next();\n if (result.done) return result;\n if (hasPrefix(result.value.params.namespace, path)) {\n return result;\n }\n }\n },\n };\n }\n}\n\n/**\n * Background consumer that drains a raw `graph.stream()` source into a\n * {@link StreamMux}. Converts each chunk to a {@link ProtocolEvent} and\n * pushes it; calls {@link StreamMux.close} on normal completion or\n * {@link StreamMux.fail} on error.\n *\n * @param source - The async iterable of raw stream chunks from the engine.\n * @param mux - The mux instance to feed.\n * @returns A promise that resolves when the source is fully consumed.\n */\nexport async function pump(\n source: AsyncIterable<StreamChunk>,\n mux: StreamMux\n): Promise<void> {\n let seq = 0;\n try {\n for await (const chunk of source) {\n const [ns, mode, payload] = chunk;\n\n // Detect interrupt payloads attached to values-mode chunks.\n if (mode === \"values\" && isInterrupted(payload)) {\n const interrupts = (payload as { [INTERRUPT]: Interrupt[] })[INTERRUPT];\n mux.markInterrupted(\n interrupts.map((i) => ({\n interruptId: i.id ?? \"\",\n payload: i.value,\n }))\n );\n }\n\n const events = convertToProtocolEvent({\n namespace: ns,\n mode,\n payload,\n seq,\n });\n seq += events.length;\n for (const event of events) {\n mux.push(ns, event);\n }\n }\n } catch (err) {\n mux.fail(err);\n return;\n }\n mux.close();\n}\n\n/**\n * Serialises a {@link Namespace} array into a single string key using the\n * null byte (`\\x00`) as separator, suitable for `Map`/`Set` lookups.\n *\n * @param ns - The namespace segments to join.\n * @returns A null-byte-joined string key.\n */\nexport function nsKey(ns: Namespace): string {\n return ns.join(\"\\x00\");\n}\n\n/**\n * Tests whether {@link ns} starts with every segment in {@link prefix}.\n *\n * @param ns - The full namespace to check.\n * @param prefix - The prefix to match against.\n * @returns `true` if `ns` begins with `prefix` segment-by-segment.\n */\nexport function hasPrefix(ns: Namespace, prefix: Namespace): boolean {\n if (prefix.length > ns.length) return false;\n for (let i = 0; i < prefix.length; i += 1) {\n if (ns[i] !== prefix[i]) return false;\n }\n return true;\n}\n"],"mappings":"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"}
|
package/dist/stream/mux.d.cts
CHANGED
|
@@ -77,8 +77,8 @@ declare class StreamMux {
|
|
|
77
77
|
* Two projection shapes are recognised:
|
|
78
78
|
*
|
|
79
79
|
* - {@link StreamChannel} values — named channels forward each `push()`
|
|
80
|
-
* immediately as a protocol event
|
|
81
|
-
*
|
|
80
|
+
* immediately as a `custom:<channelName>` protocol event. Unnamed
|
|
81
|
+
* channels remain in-process-only.
|
|
82
82
|
*
|
|
83
83
|
* - `PromiseLike<unknown>` values — tracked as final-value
|
|
84
84
|
* projections and flushed on {@link close} as a single
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mux.d.cts","names":[],"sources":["../../src/stream/mux.ts"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"mux.d.cts","names":[],"sources":["../../src/stream/mux.ts"],"mappings":";;;;;;;;;cA4Da,cAAA;;;;;cAMA,aAAA;;;;;AA+Cb;UAxCiB,YAAA;EAAA,CACd,cAAA,EAAgB,MAAA;EAAA,CAChB,aAAA,EAAe,GAAA;AAAA;;;;KAwBN,iBAAA;EACV,EAAA,EAAI,SAAA;EACJ,MAAA,EAAQ,YAAA;AAAA;;;;;;;;;;cAYG,SAAA;EAAA;EA6HX;EAAA,SA3HS,OAAA,EAAO,aAAA,CAAA,aAAA;EA2HH;EAAA,SAxHJ,YAAA,EAAY,aAAA,CAAA,iBAAA;EAkKZ;;;;;;;EAtHT,QAAA,CAAS,IAAA,EAAM,SAAA,EAAW,MAAA,EAAQ,YAAA;EAqQN;;;;;;;;;;;;;;;;EAjP5B,cAAA,CAAe,WAAA,EAAa,iBAAA;;;;;;;;;;;;;;;;;;;;;;;;EAwD5B,YAAA,CAAa,UAAA,EAAY,MAAA;;;;;;;;;;;;EA0CzB,IAAA,CAAK,EAAA,EAAI,SAAA,EAAW,KAAA,EAAO,aAAA;;;;;;;;;;;;;;EAmD3B,KAAA,CAAA;;;;;;;EAsEA,IAAA,CAAK,GAAA;;;;;;;EAsBL,eAAA,CAAgB,UAAA,EAAY,gBAAA;;;;;;MAUxB,WAAA,CAAA;;;;;;MASA,UAAA,CAAA,YAAuB,gBAAA;;;;;;;;;EAY3B,eAAA,CAAgB,IAAA,EAAM,SAAA,EAAW,OAAA,YAAc,aAAA,CAAc,aAAA;AAAA"}
|
package/dist/stream/mux.d.ts
CHANGED
|
@@ -77,8 +77,8 @@ declare class StreamMux {
|
|
|
77
77
|
* Two projection shapes are recognised:
|
|
78
78
|
*
|
|
79
79
|
* - {@link StreamChannel} values — named channels forward each `push()`
|
|
80
|
-
* immediately as a protocol event
|
|
81
|
-
*
|
|
80
|
+
* immediately as a `custom:<channelName>` protocol event. Unnamed
|
|
81
|
+
* channels remain in-process-only.
|
|
82
82
|
*
|
|
83
83
|
* - `PromiseLike<unknown>` values — tracked as final-value
|
|
84
84
|
* projections and flushed on {@link close} as a single
|
package/dist/stream/mux.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mux.d.ts","names":[],"sources":["../../src/stream/mux.ts"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"mux.d.ts","names":[],"sources":["../../src/stream/mux.ts"],"mappings":";;;;;;;;;cA4Da,cAAA;AAuCb;;;;AAAA,cAjCa,aAAA;;;;;;UAOI,YAAA;EAAA,CACd,cAAA,EAAgB,MAAA;EAAA,CAChB,aAAA,EAAe,GAAA;AAAA;;;;KAwBN,iBAAA;EACV,EAAA,EAAI,SAAA;EACJ,MAAA,EAAQ,YAAA;AAAA;;;;;;;;;;cAYG,SAAA;EAAA;EAqEI;EAAA,SAnEN,OAAA,EAAO,aAAA,CAAA,aAAA;EA2HS;EAAA,SAxHhB,YAAA,EAAY,aAAA,CAAA,iBAAA;EAkKrB;;;;;;;EAtHA,QAAA,CAAS,IAAA,EAAM,SAAA,EAAW,MAAA,EAAQ,YAAA;EAqQlC;;;;;;;;;;;;;;;;EAjPA,cAAA,CAAe,WAAA,EAAa,iBAAA;;;;;;;;;;;;;;;;;;;;;;;;EAwD5B,YAAA,CAAa,UAAA,EAAY,MAAA;;;;;;;;;;;;EA0CzB,IAAA,CAAK,EAAA,EAAI,SAAA,EAAW,KAAA,EAAO,aAAA;;;;;;;;;;;;;;EAmD3B,KAAA,CAAA;;;;;;;EAsEA,IAAA,CAAK,GAAA;;;;;;;EAsBL,eAAA,CAAgB,UAAA,EAAY,gBAAA;;;;;;MAUxB,WAAA,CAAA;;;;;;MASA,UAAA,CAAA,YAAuB,gBAAA;;;;;;;;;EAY3B,eAAA,CAAgB,IAAA,EAAM,SAAA,EAAW,OAAA,YAAc,aAAA,CAAc,aAAA;AAAA"}
|
package/dist/stream/mux.js
CHANGED
|
@@ -2,6 +2,15 @@ import { INTERRUPT, isInterrupted } from "../constants.js";
|
|
|
2
2
|
import { convertToProtocolEvent } from "./convert.js";
|
|
3
3
|
import { StreamChannel, isStreamChannel } from "./stream-channel.js";
|
|
4
4
|
//#region src/stream/mux.ts
|
|
5
|
+
/** Wire prefix for user-defined {@link StreamChannel} auto-forwards. */
|
|
6
|
+
const EXTENSION_CHANNEL_PREFIX = "custom:";
|
|
7
|
+
/**
|
|
8
|
+
* Protocol method for a user-defined (extension) {@link StreamChannel}.
|
|
9
|
+
* Matches Python's `StreamMux._bind_and_wire` (`f"custom:{value.name}"`).
|
|
10
|
+
*/
|
|
11
|
+
function extensionChannelMethod(channelName) {
|
|
12
|
+
return `${EXTENSION_CHANNEL_PREFIX}${channelName}`;
|
|
13
|
+
}
|
|
5
14
|
/**
|
|
6
15
|
* Structural `PromiseLike<T>` predicate — true for thenables including
|
|
7
16
|
* native promises, user-constructed `{ then }` objects, and helper
|
|
@@ -104,8 +113,8 @@ var StreamMux = class {
|
|
|
104
113
|
* Two projection shapes are recognised:
|
|
105
114
|
*
|
|
106
115
|
* - {@link StreamChannel} values — named channels forward each `push()`
|
|
107
|
-
* immediately as a protocol event
|
|
108
|
-
*
|
|
116
|
+
* immediately as a `custom:<channelName>` protocol event. Unnamed
|
|
117
|
+
* channels remain in-process-only.
|
|
109
118
|
*
|
|
110
119
|
* - `PromiseLike<unknown>` values — tracked as final-value
|
|
111
120
|
* projections and flushed on {@link close} as a single
|
|
@@ -124,11 +133,12 @@ var StreamMux = class {
|
|
|
124
133
|
if (isStreamChannel(value)) {
|
|
125
134
|
this.#channels.push(value);
|
|
126
135
|
if (typeof value.channelName !== "string") continue;
|
|
136
|
+
const method = extensionChannelMethod(value.channelName);
|
|
127
137
|
value._wire((item) => {
|
|
128
138
|
this._events.push({
|
|
129
139
|
type: "event",
|
|
130
140
|
seq: this.#nextEmitSeq++,
|
|
131
|
-
method
|
|
141
|
+
method,
|
|
132
142
|
params: {
|
|
133
143
|
namespace: this.#currentNamespace,
|
|
134
144
|
timestamp: Date.now(),
|
package/dist/stream/mux.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mux.js","names":["#transformers","#channels","#streamMap","#latestValues","#interrupts","#finalValues","#closed","#error","#nextEmitSeq","#currentNamespace","#interrupted"],"sources":["../../src/stream/mux.ts"],"sourcesContent":["/**\n * StreamMux — central dispatcher with transformer pipeline.\n *\n * Routes raw stream chunks through registered StreamTransformers, then appends\n * the resulting ProtocolEvents to the main local channel. Also tracks\n * namespace discovery for SubgraphRunStream creation.\n *\n * lifecycle:\n * graph.streamEvents(input, { version: \"v3\" })\n * ├─ StreamMux starts pumping from graph.stream(…, { subgraphs: true })\n * ├─ For each ProtocolEvent:\n * │ ├─ transformer_1.process(event)\n * │ ├─ transformer_2.process(event)\n * │ └─ event appended to _events (unless suppressed)\n * └─ On close: transformer_n.finalize() called in registration order\n */\n\nimport type { StreamChunk } from \"../pregel/stream.js\";\nimport { INTERRUPT, isInterrupted, type Interrupt } from \"../constants.js\";\nimport { convertToProtocolEvent, STREAM_EVENTS_V3_MODES } from \"./convert.js\";\nimport { StreamChannel, isStreamChannel } from \"./stream-channel.js\";\nimport type {\n InterruptPayload,\n Namespace,\n ProtocolEvent,\n StreamEmitter,\n StreamTransformer,\n} from \"./types.js\";\n\nexport { STREAM_EVENTS_V3_MODES };\n\n/**\n * Structural `PromiseLike<T>` predicate — true for thenables including\n * native promises, user-constructed `{ then }` objects, and helper\n * wrappers. Used by {@link StreamMux.wireChannels} to detect final-value\n * projections distinctly from streaming `StreamChannel` values.\n */\nfunction isPromiseLike(value: unknown): value is PromiseLike<unknown> {\n return (\n value != null &&\n (typeof value === \"object\" || typeof value === \"function\") &&\n typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\n/**\n * Symbol key used by {@link StreamMux} to resolve the values promise on a\n * stream handle. Using a symbol keeps this off the public autocomplete surface.\n */\nexport const RESOLVE_VALUES: unique symbol = Symbol(\"resolveValues\");\n\n/**\n * Symbol key used by {@link StreamMux} to reject the values promise on a\n * stream handle. Using a symbol keeps this off the public autocomplete surface.\n */\nexport const REJECT_VALUES: unique symbol = Symbol(\"rejectValues\");\n\n/**\n * Minimal interface that {@link StreamMux} requires from stream handles\n * for lifecycle resolution. This avoids a direct dependency on\n * `GraphRunStream` / `SubgraphRunStream`.\n */\nexport interface StreamHandle {\n [RESOLVE_VALUES](values: unknown): void;\n [REJECT_VALUES](err: unknown): void;\n}\n\n/**\n * Factory function that creates a subgraph stream handle for a newly\n * discovered namespace.\n *\n * Historically consumed by {@link StreamMux} at construction time;\n * today factories are consumed by\n * `createSubgraphDiscoveryTransformer` (via its `createStream`\n * option). This shape is retained for consumers that still thread a\n * mux reference through the factory — the narrower transformer\n * option omits `mux` because it captures the mux in a closure.\n */\nexport type SubgraphStreamFactory = (\n path: Namespace,\n mux: StreamMux,\n discoveryStart: number,\n eventStart: number\n) => StreamHandle;\n\n/**\n * A discovered subgraph namespace paired with its run stream handle.\n */\nexport type SubgraphDiscovery = {\n ns: Namespace;\n stream: StreamHandle;\n};\n\n/**\n * Central event dispatcher that routes {@link ProtocolEvent}s through a\n * pipeline of {@link StreamTransformer}s, manages namespace discovery for\n * subgraph streams, and exposes async iteration over filtered event\n * sequences.\n *\n * One `StreamMux` instance exists per top-level\n * `streamEvents(..., { version: \"v3\" })` invocation.\n */\nexport class StreamMux {\n /** @internal All protocol events in arrival order (after reducer pipeline). */\n readonly _events = StreamChannel.local<ProtocolEvent>();\n\n /** @internal New-namespace discovery notifications. */\n readonly _discoveries = StreamChannel.local<SubgraphDiscovery>();\n\n /** Monotonic counter for auto-forwarded channel events. */\n #nextEmitSeq = 0;\n\n /** Whether the mux has been closed or failed. */\n #closed = false;\n\n /** The error passed to {@link fail}, if any. */\n #error: unknown;\n\n /** Whether the run was interrupted. */\n #interrupted = false;\n\n /**\n * Namespace of the event currently being processed by\n * {@link push}. Read by {@link StreamChannel} wiring callbacks so\n * auto-forwarded events inherit the triggering event's namespace.\n */\n #currentNamespace: Namespace = [];\n\n readonly #transformers: StreamTransformer<unknown>[] = [];\n readonly #channels: StreamChannel<unknown>[] = [];\n readonly #streamMap = new Map<string, StreamHandle>();\n readonly #latestValues = new Map<string, Record<string, unknown>>();\n readonly #interrupts: InterruptPayload[] = [];\n\n /**\n * Final-value projection keys tracked for remote surfacing. Populated\n * by {@link wireChannels} when a transformer's projection contains a\n * `PromiseLike` value. Each entry is flushed as a `custom:<name>`\n * protocol event during {@link close} so that remote clients can\n * observe final-value transformers via `thread.extensions.<name>`.\n */\n readonly #finalValues: Array<{ name: string; promise: Promise<unknown> }> =\n [];\n\n /**\n * Associates a pre-existing stream handle with a namespace so that\n * {@link close} can resolve its values promise later.\n *\n * @param path - The namespace path to register.\n * @param stream - The run stream handle for that namespace.\n */\n register(path: Namespace, stream: StreamHandle): void {\n this.#streamMap.set(nsKey(path), stream);\n }\n\n /**\n * Registers a transformer and replays all buffered events through it so\n * it catches up with events already processed by the mux. When the event\n * log is empty (typical at construction time) the replay is a no-op.\n *\n * The transformer must already have been initialised (i.e. `init()` called\n * and any projection wired). The sequence is:\n *\n * 1. Snapshot the current event log length.\n * 2. Append the transformer so future {@link push} calls reach it.\n * 3. Replay events `[0, snapshot)` through `process()`.\n * 4. If the mux is already closed, call `finalize()` (or `fail()`)\n * immediately so the transformer's log/channel terminates cleanly.\n *\n * @param transformer - An already-initialised transformer to register.\n */\n addTransformer(transformer: StreamTransformer<unknown>): void {\n const snapshot = this._events.size;\n this.#transformers.push(transformer);\n\n // Hand the transformer a narrow emitter handle *before* replay so\n // synthetic-emission transformers (e.g. deepagents\n // `SubagentTransformer`) can inject events into the mux during\n // their own `process()` calls — including the initial replay\n // triggered just below.\n if (transformer.onRegister) {\n const emitter: StreamEmitter = {\n // Transformer-originated events use a placeholder `seq` of\n // `0`. `push()` is the single authority for sequence numbers\n // and will re-stamp this event with the next monotonically\n // increasing value.\n push: (ns, event) => this.push(ns, event),\n };\n transformer.onRegister(emitter);\n }\n\n for (let i = 0; i < snapshot; i += 1) {\n transformer.process(this._events.get(i));\n }\n\n if (this.#closed) {\n if (this.#error !== undefined) {\n transformer.fail?.(this.#error);\n } else {\n transformer.finalize?.();\n }\n }\n }\n\n /**\n * Scans a transformer projection for streaming and final-value primitives.\n * Remote stream channels are wired to auto-forward to the protocol event\n * stream; local stream channels are tracked for lifecycle only.\n *\n * Two projection shapes are recognised:\n *\n * - {@link StreamChannel} values — named channels forward each `push()`\n * immediately as a protocol event on the channel's declared\n * `channelName` method. Unnamed channels remain in-process-only.\n *\n * - `PromiseLike<unknown>` values — tracked as final-value\n * projections and flushed on {@link close} as a single\n * `custom:<key>` event, where `<key>` is the projection key.\n * This mirrors the in-process `await run.extensions.<key>`\n * ergonomics on remote clients via\n * `await thread.extensions.<key>`.\n *\n * Plain values that are neither are ignored — they remain in-process-only,\n * matching prior behaviour.\n *\n * @param projection - The object returned by `transformer.init()`.\n */\n wireChannels(projection: Record<string, unknown>): void {\n for (const [key, value] of Object.entries(projection)) {\n if (isStreamChannel(value)) {\n this.#channels.push(value);\n if (typeof value.channelName !== \"string\") {\n continue;\n }\n value._wire((item: unknown) => {\n this._events.push({\n type: \"event\",\n seq: this.#nextEmitSeq++,\n method: value.channelName as ProtocolEvent[\"method\"],\n params: {\n namespace: this.#currentNamespace,\n timestamp: Date.now(),\n data: item,\n },\n });\n });\n continue;\n }\n if (isPromiseLike(value)) {\n this.#finalValues.push({\n name: key,\n promise: Promise.resolve(value),\n });\n }\n }\n }\n\n /**\n * Distributes an event through the transformer pipeline, then appends it to\n * the main event log.\n *\n * Subgraph discovery (materializing a {@link StreamHandle} for each\n * newly observed top-level namespace) is handled by the\n * {@link createSubgraphDiscoveryTransformer} when installed, not here.\n *\n * @param ns - The namespace path that produced the event.\n * @param event - The protocol event to process and store.\n */\n push(ns: Namespace, event: ProtocolEvent): void {\n if (event.method === \"values\") {\n this.#latestValues.set(\n nsKey(ns),\n event.params.data as Record<string, unknown>\n );\n }\n\n // Save the outer namespace so re-entrant `push()` calls (e.g. from\n // `StreamTransformer.onRegister` emitters synthesizing events\n // inside a transformer's `process()`) can set their own namespace\n // without clobbering the outer scope's `StreamChannel` routing\n // when control returns to the outer transformer loop.\n const outerNamespace = this.#currentNamespace;\n this.#currentNamespace = ns;\n\n let keep = true;\n for (const transformer of this.#transformers) {\n if (!transformer.process(event)) {\n keep = false;\n }\n }\n\n this.#currentNamespace = outerNamespace;\n\n if (keep) {\n // The mux is the single authority for sequence numbers. Callers\n // (the `pump`, transformer emitters, channel forwarders) pass a\n // placeholder `seq`; we re-stamp every event here so the log is\n // strictly monotonic across all origination paths. Stamping\n // happens *after* `process()` so that any channel-forwarded\n // events pushed during processing get earlier sequence numbers\n // than the triggering event, matching their in-order appearance\n // in `_events`.\n this._events.push({ ...event, seq: this.#nextEmitSeq++ });\n }\n }\n\n /**\n * Gracefully ends the stream: resolves values promises on all known\n * streams, finalizes every transformer, auto-closes streaming\n * channels, flushes any final-value projections as `custom:<name>`\n * events, and closes both event logs.\n *\n * When final-value projections are present, `_events.close()` is\n * deferred until every tracked projection promise has settled so\n * remote consumers observe the flushed values before their event\n * stream ends. Callers do not need to await — `close()` returns\n * synchronously and any downstream consumer iterating\n * {@link _events} naturally waits for the final events.\n */\n close(): void {\n this.#closed = true;\n for (const [key, values] of this.#latestValues.entries()) {\n const ns = key ? key.split(\"\\x00\") : [];\n const stream = this.#streamMap.get(nsKey(ns));\n stream?.[RESOLVE_VALUES](values);\n }\n\n const finalizePromises: PromiseLike<void>[] = [];\n for (const transformer of this.#transformers) {\n const result = transformer.finalize?.();\n if (\n result != null &&\n typeof (result as PromiseLike<void>).then === \"function\"\n ) {\n finalizePromises.push(result as PromiseLike<void>);\n }\n }\n\n for (const channel of this.#channels) {\n channel._close();\n }\n\n const finalValues = this.#finalValues;\n if (finalValues.length === 0 && finalizePromises.length === 0) {\n this._events.close();\n this._discoveries.close();\n } else {\n void Promise.allSettled([\n ...finalizePromises,\n ...finalValues.map(async ({ name, promise }) => {\n try {\n const resolved = await promise;\n if (!this._events.done) {\n this._events.push({\n type: \"event\",\n seq: this.#nextEmitSeq++,\n method: \"custom\",\n params: {\n namespace: [],\n timestamp: Date.now(),\n data: { name, payload: resolved },\n },\n });\n }\n } catch {\n // Rejected final-value projections are intentionally dropped\n // so a single failing extension can't poison the protocol\n // stream. The corresponding in-process Promise still\n // surfaces the rejection to its direct awaiters via the\n // transformer's own `fail()` hook.\n }\n }),\n ]).then(() => {\n this._events.close();\n this._discoveries.close();\n });\n }\n\n for (const stream of this.#streamMap.values()) {\n stream[RESOLVE_VALUES](undefined);\n }\n }\n\n /**\n * Propagates a failure to all transformers, channels, event logs, and\n * stream handles.\n *\n * @param err - The error that caused the run to fail.\n */\n fail(err: unknown): void {\n this.#closed = true;\n this.#error = err;\n for (const transformer of this.#transformers) {\n transformer.fail?.(err);\n }\n for (const channel of this.#channels) {\n channel._fail(err);\n }\n this._events.fail(err);\n this._discoveries.fail(err);\n for (const stream of this.#streamMap.values()) {\n stream[REJECT_VALUES](err);\n }\n }\n\n /**\n * Records that the run was interrupted, appending the supplied payloads\n * for later retrieval.\n *\n * @param interrupts - The interrupt payloads to store.\n */\n markInterrupted(interrupts: InterruptPayload[]): void {\n this.#interrupted = true;\n this.#interrupts.push(...interrupts);\n }\n\n /**\n * Whether the run ended due to an interrupt.\n *\n * @returns `true` if {@link markInterrupted} was called.\n */\n get interrupted(): boolean {\n return this.#interrupted;\n }\n\n /**\n * All interrupt payloads collected during the run.\n *\n * @returns A readonly view of the accumulated interrupt payloads.\n */\n get interrupts(): readonly InterruptPayload[] {\n return this.#interrupts;\n }\n\n /**\n * Returns an async iterator that yields only events whose namespace\n * starts with {@link path}.\n *\n * @param path - Namespace prefix to filter on.\n * @param startAt - Zero-based index into the event log to begin from.\n * @returns An async iterator over matching {@link ProtocolEvent}s.\n */\n subscribeEvents(path: Namespace, startAt = 0): AsyncIterator<ProtocolEvent> {\n const base = this._events.iterate(startAt);\n return {\n async next(): Promise<IteratorResult<ProtocolEvent>> {\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const result = await base.next();\n if (result.done) return result;\n if (hasPrefix(result.value.params.namespace, path)) {\n return result;\n }\n }\n },\n };\n }\n}\n\n/**\n * Background consumer that drains a raw `graph.stream()` source into a\n * {@link StreamMux}. Converts each chunk to a {@link ProtocolEvent} and\n * pushes it; calls {@link StreamMux.close} on normal completion or\n * {@link StreamMux.fail} on error.\n *\n * @param source - The async iterable of raw stream chunks from the engine.\n * @param mux - The mux instance to feed.\n * @returns A promise that resolves when the source is fully consumed.\n */\nexport async function pump(\n source: AsyncIterable<StreamChunk>,\n mux: StreamMux\n): Promise<void> {\n let seq = 0;\n try {\n for await (const chunk of source) {\n const [ns, mode, payload] = chunk;\n\n // Detect interrupt payloads attached to values-mode chunks.\n if (mode === \"values\" && isInterrupted(payload)) {\n const interrupts = (payload as { [INTERRUPT]: Interrupt[] })[INTERRUPT];\n mux.markInterrupted(\n interrupts.map((i) => ({\n interruptId: i.id ?? \"\",\n payload: i.value,\n }))\n );\n }\n\n const events = convertToProtocolEvent({\n namespace: ns,\n mode,\n payload,\n seq,\n });\n seq += events.length;\n for (const event of events) {\n mux.push(ns, event);\n }\n }\n } catch (err) {\n mux.fail(err);\n return;\n }\n mux.close();\n}\n\n/**\n * Serialises a {@link Namespace} array into a single string key using the\n * null byte (`\\x00`) as separator, suitable for `Map`/`Set` lookups.\n *\n * @param ns - The namespace segments to join.\n * @returns A null-byte-joined string key.\n */\nexport function nsKey(ns: Namespace): string {\n return ns.join(\"\\x00\");\n}\n\n/**\n * Tests whether {@link ns} starts with every segment in {@link prefix}.\n *\n * @param ns - The full namespace to check.\n * @param prefix - The prefix to match against.\n * @returns `true` if `ns` begins with `prefix` segment-by-segment.\n */\nexport function hasPrefix(ns: Namespace, prefix: Namespace): boolean {\n if (prefix.length > ns.length) return false;\n for (let i = 0; i < prefix.length; i += 1) {\n if (ns[i] !== prefix[i]) return false;\n }\n return true;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mux.js","names":["#transformers","#channels","#streamMap","#latestValues","#interrupts","#finalValues","#closed","#error","#nextEmitSeq","#currentNamespace","#interrupted"],"sources":["../../src/stream/mux.ts"],"sourcesContent":["/**\n * StreamMux — central dispatcher with transformer pipeline.\n *\n * Routes raw stream chunks through registered StreamTransformers, then appends\n * the resulting ProtocolEvents to the main local channel. Also tracks\n * namespace discovery for SubgraphRunStream creation.\n *\n * lifecycle:\n * graph.streamEvents(input, { version: \"v3\" })\n * ├─ StreamMux starts pumping from graph.stream(…, { subgraphs: true })\n * ├─ For each ProtocolEvent:\n * │ ├─ transformer_1.process(event)\n * │ ├─ transformer_2.process(event)\n * │ └─ event appended to _events (unless suppressed)\n * └─ On close: transformer_n.finalize() called in registration order\n */\n\nimport type { StreamChunk } from \"../pregel/stream.js\";\nimport { INTERRUPT, isInterrupted, type Interrupt } from \"../constants.js\";\nimport { convertToProtocolEvent, STREAM_EVENTS_V3_MODES } from \"./convert.js\";\nimport { StreamChannel, isStreamChannel } from \"./stream-channel.js\";\nimport type {\n InterruptPayload,\n Namespace,\n ProtocolEvent,\n StreamEmitter,\n StreamTransformer,\n} from \"./types.js\";\n\nexport { STREAM_EVENTS_V3_MODES };\n\n/** Wire prefix for user-defined {@link StreamChannel} auto-forwards. */\nconst EXTENSION_CHANNEL_PREFIX = \"custom:\";\n\n/**\n * Protocol method for a user-defined (extension) {@link StreamChannel}.\n * Matches Python's `StreamMux._bind_and_wire` (`f\"custom:{value.name}\"`).\n */\nfunction extensionChannelMethod(channelName: string): ProtocolEvent[\"method\"] {\n return `${EXTENSION_CHANNEL_PREFIX}${channelName}`;\n}\n\n/**\n * Structural `PromiseLike<T>` predicate — true for thenables including\n * native promises, user-constructed `{ then }` objects, and helper\n * wrappers. Used by {@link StreamMux.wireChannels} to detect final-value\n * projections distinctly from streaming `StreamChannel` values.\n */\nfunction isPromiseLike(value: unknown): value is PromiseLike<unknown> {\n return (\n value != null &&\n (typeof value === \"object\" || typeof value === \"function\") &&\n typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\n/**\n * Symbol key used by {@link StreamMux} to resolve the values promise on a\n * stream handle. Using a symbol keeps this off the public autocomplete surface.\n */\nexport const RESOLVE_VALUES: unique symbol = Symbol(\"resolveValues\");\n\n/**\n * Symbol key used by {@link StreamMux} to reject the values promise on a\n * stream handle. Using a symbol keeps this off the public autocomplete surface.\n */\nexport const REJECT_VALUES: unique symbol = Symbol(\"rejectValues\");\n\n/**\n * Minimal interface that {@link StreamMux} requires from stream handles\n * for lifecycle resolution. This avoids a direct dependency on\n * `GraphRunStream` / `SubgraphRunStream`.\n */\nexport interface StreamHandle {\n [RESOLVE_VALUES](values: unknown): void;\n [REJECT_VALUES](err: unknown): void;\n}\n\n/**\n * Factory function that creates a subgraph stream handle for a newly\n * discovered namespace.\n *\n * Historically consumed by {@link StreamMux} at construction time;\n * today factories are consumed by\n * `createSubgraphDiscoveryTransformer` (via its `createStream`\n * option). This shape is retained for consumers that still thread a\n * mux reference through the factory — the narrower transformer\n * option omits `mux` because it captures the mux in a closure.\n */\nexport type SubgraphStreamFactory = (\n path: Namespace,\n mux: StreamMux,\n discoveryStart: number,\n eventStart: number\n) => StreamHandle;\n\n/**\n * A discovered subgraph namespace paired with its run stream handle.\n */\nexport type SubgraphDiscovery = {\n ns: Namespace;\n stream: StreamHandle;\n};\n\n/**\n * Central event dispatcher that routes {@link ProtocolEvent}s through a\n * pipeline of {@link StreamTransformer}s, manages namespace discovery for\n * subgraph streams, and exposes async iteration over filtered event\n * sequences.\n *\n * One `StreamMux` instance exists per top-level\n * `streamEvents(..., { version: \"v3\" })` invocation.\n */\nexport class StreamMux {\n /** @internal All protocol events in arrival order (after reducer pipeline). */\n readonly _events = StreamChannel.local<ProtocolEvent>();\n\n /** @internal New-namespace discovery notifications. */\n readonly _discoveries = StreamChannel.local<SubgraphDiscovery>();\n\n /** Monotonic counter for auto-forwarded channel events. */\n #nextEmitSeq = 0;\n\n /** Whether the mux has been closed or failed. */\n #closed = false;\n\n /** The error passed to {@link fail}, if any. */\n #error: unknown;\n\n /** Whether the run was interrupted. */\n #interrupted = false;\n\n /**\n * Namespace of the event currently being processed by\n * {@link push}. Read by {@link StreamChannel} wiring callbacks so\n * auto-forwarded events inherit the triggering event's namespace.\n */\n #currentNamespace: Namespace = [];\n\n readonly #transformers: StreamTransformer<unknown>[] = [];\n readonly #channels: StreamChannel<unknown>[] = [];\n readonly #streamMap = new Map<string, StreamHandle>();\n readonly #latestValues = new Map<string, Record<string, unknown>>();\n readonly #interrupts: InterruptPayload[] = [];\n\n /**\n * Final-value projection keys tracked for remote surfacing. Populated\n * by {@link wireChannels} when a transformer's projection contains a\n * `PromiseLike` value. Each entry is flushed as a `custom:<name>`\n * protocol event during {@link close} so that remote clients can\n * observe final-value transformers via `thread.extensions.<name>`.\n */\n readonly #finalValues: Array<{ name: string; promise: Promise<unknown> }> =\n [];\n\n /**\n * Associates a pre-existing stream handle with a namespace so that\n * {@link close} can resolve its values promise later.\n *\n * @param path - The namespace path to register.\n * @param stream - The run stream handle for that namespace.\n */\n register(path: Namespace, stream: StreamHandle): void {\n this.#streamMap.set(nsKey(path), stream);\n }\n\n /**\n * Registers a transformer and replays all buffered events through it so\n * it catches up with events already processed by the mux. When the event\n * log is empty (typical at construction time) the replay is a no-op.\n *\n * The transformer must already have been initialised (i.e. `init()` called\n * and any projection wired). The sequence is:\n *\n * 1. Snapshot the current event log length.\n * 2. Append the transformer so future {@link push} calls reach it.\n * 3. Replay events `[0, snapshot)` through `process()`.\n * 4. If the mux is already closed, call `finalize()` (or `fail()`)\n * immediately so the transformer's log/channel terminates cleanly.\n *\n * @param transformer - An already-initialised transformer to register.\n */\n addTransformer(transformer: StreamTransformer<unknown>): void {\n const snapshot = this._events.size;\n this.#transformers.push(transformer);\n\n // Hand the transformer a narrow emitter handle *before* replay so\n // synthetic-emission transformers (e.g. deepagents\n // `SubagentTransformer`) can inject events into the mux during\n // their own `process()` calls — including the initial replay\n // triggered just below.\n if (transformer.onRegister) {\n const emitter: StreamEmitter = {\n // Transformer-originated events use a placeholder `seq` of\n // `0`. `push()` is the single authority for sequence numbers\n // and will re-stamp this event with the next monotonically\n // increasing value.\n push: (ns, event) => this.push(ns, event),\n };\n transformer.onRegister(emitter);\n }\n\n for (let i = 0; i < snapshot; i += 1) {\n transformer.process(this._events.get(i));\n }\n\n if (this.#closed) {\n if (this.#error !== undefined) {\n transformer.fail?.(this.#error);\n } else {\n transformer.finalize?.();\n }\n }\n }\n\n /**\n * Scans a transformer projection for streaming and final-value primitives.\n * Remote stream channels are wired to auto-forward to the protocol event\n * stream; local stream channels are tracked for lifecycle only.\n *\n * Two projection shapes are recognised:\n *\n * - {@link StreamChannel} values — named channels forward each `push()`\n * immediately as a `custom:<channelName>` protocol event. Unnamed\n * channels remain in-process-only.\n *\n * - `PromiseLike<unknown>` values — tracked as final-value\n * projections and flushed on {@link close} as a single\n * `custom:<key>` event, where `<key>` is the projection key.\n * This mirrors the in-process `await run.extensions.<key>`\n * ergonomics on remote clients via\n * `await thread.extensions.<key>`.\n *\n * Plain values that are neither are ignored — they remain in-process-only,\n * matching prior behaviour.\n *\n * @param projection - The object returned by `transformer.init()`.\n */\n wireChannels(projection: Record<string, unknown>): void {\n for (const [key, value] of Object.entries(projection)) {\n if (isStreamChannel(value)) {\n this.#channels.push(value);\n if (typeof value.channelName !== \"string\") {\n continue;\n }\n const method = extensionChannelMethod(value.channelName);\n value._wire((item: unknown) => {\n this._events.push({\n type: \"event\",\n seq: this.#nextEmitSeq++,\n method,\n params: {\n namespace: this.#currentNamespace,\n timestamp: Date.now(),\n data: item,\n },\n });\n });\n continue;\n }\n if (isPromiseLike(value)) {\n this.#finalValues.push({\n name: key,\n promise: Promise.resolve(value),\n });\n }\n }\n }\n\n /**\n * Distributes an event through the transformer pipeline, then appends it to\n * the main event log.\n *\n * Subgraph discovery (materializing a {@link StreamHandle} for each\n * newly observed top-level namespace) is handled by the\n * {@link createSubgraphDiscoveryTransformer} when installed, not here.\n *\n * @param ns - The namespace path that produced the event.\n * @param event - The protocol event to process and store.\n */\n push(ns: Namespace, event: ProtocolEvent): void {\n if (event.method === \"values\") {\n this.#latestValues.set(\n nsKey(ns),\n event.params.data as Record<string, unknown>\n );\n }\n\n // Save the outer namespace so re-entrant `push()` calls (e.g. from\n // `StreamTransformer.onRegister` emitters synthesizing events\n // inside a transformer's `process()`) can set their own namespace\n // without clobbering the outer scope's `StreamChannel` routing\n // when control returns to the outer transformer loop.\n const outerNamespace = this.#currentNamespace;\n this.#currentNamespace = ns;\n\n let keep = true;\n for (const transformer of this.#transformers) {\n if (!transformer.process(event)) {\n keep = false;\n }\n }\n\n this.#currentNamespace = outerNamespace;\n\n if (keep) {\n // The mux is the single authority for sequence numbers. Callers\n // (the `pump`, transformer emitters, channel forwarders) pass a\n // placeholder `seq`; we re-stamp every event here so the log is\n // strictly monotonic across all origination paths. Stamping\n // happens *after* `process()` so that any channel-forwarded\n // events pushed during processing get earlier sequence numbers\n // than the triggering event, matching their in-order appearance\n // in `_events`.\n this._events.push({ ...event, seq: this.#nextEmitSeq++ });\n }\n }\n\n /**\n * Gracefully ends the stream: resolves values promises on all known\n * streams, finalizes every transformer, auto-closes streaming\n * channels, flushes any final-value projections as `custom:<name>`\n * events, and closes both event logs.\n *\n * When final-value projections are present, `_events.close()` is\n * deferred until every tracked projection promise has settled so\n * remote consumers observe the flushed values before their event\n * stream ends. Callers do not need to await — `close()` returns\n * synchronously and any downstream consumer iterating\n * {@link _events} naturally waits for the final events.\n */\n close(): void {\n this.#closed = true;\n for (const [key, values] of this.#latestValues.entries()) {\n const ns = key ? key.split(\"\\x00\") : [];\n const stream = this.#streamMap.get(nsKey(ns));\n stream?.[RESOLVE_VALUES](values);\n }\n\n const finalizePromises: PromiseLike<void>[] = [];\n for (const transformer of this.#transformers) {\n const result = transformer.finalize?.();\n if (\n result != null &&\n typeof (result as PromiseLike<void>).then === \"function\"\n ) {\n finalizePromises.push(result as PromiseLike<void>);\n }\n }\n\n for (const channel of this.#channels) {\n channel._close();\n }\n\n const finalValues = this.#finalValues;\n if (finalValues.length === 0 && finalizePromises.length === 0) {\n this._events.close();\n this._discoveries.close();\n } else {\n void Promise.allSettled([\n ...finalizePromises,\n ...finalValues.map(async ({ name, promise }) => {\n try {\n const resolved = await promise;\n if (!this._events.done) {\n this._events.push({\n type: \"event\",\n seq: this.#nextEmitSeq++,\n method: \"custom\",\n params: {\n namespace: [],\n timestamp: Date.now(),\n data: { name, payload: resolved },\n },\n });\n }\n } catch {\n // Rejected final-value projections are intentionally dropped\n // so a single failing extension can't poison the protocol\n // stream. The corresponding in-process Promise still\n // surfaces the rejection to its direct awaiters via the\n // transformer's own `fail()` hook.\n }\n }),\n ]).then(() => {\n this._events.close();\n this._discoveries.close();\n });\n }\n\n for (const stream of this.#streamMap.values()) {\n stream[RESOLVE_VALUES](undefined);\n }\n }\n\n /**\n * Propagates a failure to all transformers, channels, event logs, and\n * stream handles.\n *\n * @param err - The error that caused the run to fail.\n */\n fail(err: unknown): void {\n this.#closed = true;\n this.#error = err;\n for (const transformer of this.#transformers) {\n transformer.fail?.(err);\n }\n for (const channel of this.#channels) {\n channel._fail(err);\n }\n this._events.fail(err);\n this._discoveries.fail(err);\n for (const stream of this.#streamMap.values()) {\n stream[REJECT_VALUES](err);\n }\n }\n\n /**\n * Records that the run was interrupted, appending the supplied payloads\n * for later retrieval.\n *\n * @param interrupts - The interrupt payloads to store.\n */\n markInterrupted(interrupts: InterruptPayload[]): void {\n this.#interrupted = true;\n this.#interrupts.push(...interrupts);\n }\n\n /**\n * Whether the run ended due to an interrupt.\n *\n * @returns `true` if {@link markInterrupted} was called.\n */\n get interrupted(): boolean {\n return this.#interrupted;\n }\n\n /**\n * All interrupt payloads collected during the run.\n *\n * @returns A readonly view of the accumulated interrupt payloads.\n */\n get interrupts(): readonly InterruptPayload[] {\n return this.#interrupts;\n }\n\n /**\n * Returns an async iterator that yields only events whose namespace\n * starts with {@link path}.\n *\n * @param path - Namespace prefix to filter on.\n * @param startAt - Zero-based index into the event log to begin from.\n * @returns An async iterator over matching {@link ProtocolEvent}s.\n */\n subscribeEvents(path: Namespace, startAt = 0): AsyncIterator<ProtocolEvent> {\n const base = this._events.iterate(startAt);\n return {\n async next(): Promise<IteratorResult<ProtocolEvent>> {\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const result = await base.next();\n if (result.done) return result;\n if (hasPrefix(result.value.params.namespace, path)) {\n return result;\n }\n }\n },\n };\n }\n}\n\n/**\n * Background consumer that drains a raw `graph.stream()` source into a\n * {@link StreamMux}. Converts each chunk to a {@link ProtocolEvent} and\n * pushes it; calls {@link StreamMux.close} on normal completion or\n * {@link StreamMux.fail} on error.\n *\n * @param source - The async iterable of raw stream chunks from the engine.\n * @param mux - The mux instance to feed.\n * @returns A promise that resolves when the source is fully consumed.\n */\nexport async function pump(\n source: AsyncIterable<StreamChunk>,\n mux: StreamMux\n): Promise<void> {\n let seq = 0;\n try {\n for await (const chunk of source) {\n const [ns, mode, payload] = chunk;\n\n // Detect interrupt payloads attached to values-mode chunks.\n if (mode === \"values\" && isInterrupted(payload)) {\n const interrupts = (payload as { [INTERRUPT]: Interrupt[] })[INTERRUPT];\n mux.markInterrupted(\n interrupts.map((i) => ({\n interruptId: i.id ?? \"\",\n payload: i.value,\n }))\n );\n }\n\n const events = convertToProtocolEvent({\n namespace: ns,\n mode,\n payload,\n seq,\n });\n seq += events.length;\n for (const event of events) {\n mux.push(ns, event);\n }\n }\n } catch (err) {\n mux.fail(err);\n return;\n }\n mux.close();\n}\n\n/**\n * Serialises a {@link Namespace} array into a single string key using the\n * null byte (`\\x00`) as separator, suitable for `Map`/`Set` lookups.\n *\n * @param ns - The namespace segments to join.\n * @returns A null-byte-joined string key.\n */\nexport function nsKey(ns: Namespace): string {\n return ns.join(\"\\x00\");\n}\n\n/**\n * Tests whether {@link ns} starts with every segment in {@link prefix}.\n *\n * @param ns - The full namespace to check.\n * @param prefix - The prefix to match against.\n * @returns `true` if `ns` begins with `prefix` segment-by-segment.\n */\nexport function hasPrefix(ns: Namespace, prefix: Namespace): boolean {\n if (prefix.length > ns.length) return false;\n for (let i = 0; i < prefix.length; i += 1) {\n if (ns[i] !== prefix[i]) return false;\n }\n return true;\n}\n"],"mappings":"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"}
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* cursors. Local channels stay in-process only. Remote channels declare a
|
|
7
7
|
* protocol channel name; when registered with a {@link StreamMux} (via a
|
|
8
8
|
* transformer's `init()` return value), every {@link push} is automatically
|
|
9
|
-
* forwarded as a {@link ProtocolEvent} on
|
|
10
|
-
* available both in-process (via `run.extensions`) and to remote clients
|
|
11
|
-
* `session.subscribe("custom:<channelName>")`).
|
|
9
|
+
* forwarded as a {@link ProtocolEvent} on `custom:<channelName>` — making the
|
|
10
|
+
* data available both in-process (via `run.extensions`) and to remote clients
|
|
11
|
+
* (via `session.subscribe("custom:<channelName>")`).
|
|
12
12
|
*
|
|
13
13
|
* Lifecycle (`close` / `fail`) is managed by the mux automatically;
|
|
14
14
|
* transformers do not need to call them.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-channel.cjs","names":["#items","#wake","#onPush","#done","#error","#waiters"],"sources":["../../src/stream/stream-channel.ts"],"sourcesContent":["/**\n * StreamChannel — projection channel for local or remote streaming.\n *\n * A `StreamChannel<T>` is an append-only async stream with independent\n * cursors. Local channels stay in-process only. Remote channels declare a\n * protocol channel name; when registered with a {@link StreamMux} (via a\n * transformer's `init()` return value), every {@link push} is automatically\n * forwarded as a {@link ProtocolEvent} on the named channel — making the data\n * available both in-process (via `run.extensions`) and to remote clients (via\n * `session.subscribe(\"custom:<channelName>\")`).\n *\n * Lifecycle (`close` / `fail`) is managed by the mux automatically;\n * transformers do not need to call them.\n */\n\n/**\n * Branded symbol placed on every {@link StreamChannel} instance.\n *\n * Uses `Symbol.for` so the same symbol is shared across multiple\n * copies of this package that may coexist in a dependency graph\n * (e.g. when a user app imports `@langchain/langgraph` directly and a\n * wrapping library like `langchain` bundles its own copy). Using a\n * symbol brand instead of `instanceof` lets channels created against\n * one copy of the class be recognised by a mux from another.\n * @internal\n */\nexport const STREAM_CHANNEL_BRAND: unique symbol = Symbol.for(\n \"langgraph.stream_channel\"\n) as typeof STREAM_CHANNEL_BRAND;\n\nexport interface StreamChannelEventStreamOptions<T> {\n /**\n * SSE event name. Defaults to the channel's remote protocol name, if any.\n * Set this for local channels or when exposing the same channel under a\n * route-specific event name.\n */\n event?: string;\n /**\n * Cursor position to start streaming from. Useful for reconnects or\n * secondary subscribers that already consumed the first N buffered items and\n * only need replay from a known offset.\n */\n startAt?: number;\n /**\n * Serialize each item into the SSE `data:` field. Defaults to JSON. Use this\n * when a channel item needs a wire format other than its raw JSON shape, or\n * when the consumer expects line-oriented text payloads.\n */\n serialize?: (item: T) => string;\n}\n\n/**\n * A projection channel for {@link StreamTransformer}s.\n *\n * Implements `AsyncIterable<T>` so it can be iterated directly by\n * in-process consumers via `run.extensions.<key>`. Channels created with\n * {@link StreamChannel.remote} or `new StreamChannel(name)` are also\n * auto-forwarded to remote clients.\n *\n * @typeParam T - The type of items pushed into the channel.\n */\nexport class StreamChannel<T> implements AsyncIterable<T> {\n /** @internal Brand used by {@link StreamChannel.isInstance}. */\n readonly [STREAM_CHANNEL_BRAND] = true as const;\n\n /** Protocol channel name used for auto-forwarded events, if remote. */\n readonly channelName?: string;\n\n #items: T[] = [];\n #waiters: Array<() => void> = [];\n #done = false;\n #error: unknown;\n #onPush?: (item: T) => void;\n\n constructor(name?: string) {\n this.channelName = name;\n }\n\n /**\n * Create an in-process-only channel. Values remain available through\n * `run.extensions.<key>` but are not forwarded to remote clients.\n */\n static local<T>(): StreamChannel<T> {\n return new StreamChannel<T>();\n }\n\n /**\n * Create a channel whose pushes are forwarded to remote clients under\n * the given protocol channel name.\n */\n static remote<T>(name: string): StreamChannel<T> {\n return new StreamChannel<T>(name);\n }\n\n /**\n * Brand-based type guard that recognises any {@link StreamChannel}\n * instance, even ones originating from a different copy of this\n * package. Prefer this over `instanceof StreamChannel` when code\n * may observe channels that were constructed elsewhere.\n */\n static isInstance(value: unknown): value is StreamChannel<unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n STREAM_CHANNEL_BRAND in value &&\n (value as { [STREAM_CHANNEL_BRAND]: unknown })[STREAM_CHANNEL_BRAND] ===\n true\n );\n }\n\n /**\n * Append an item to the channel. If this is a remote channel wired to a\n * mux, the item is also injected into the main protocol event stream under\n * {@link channelName}.\n */\n push(item: T): void {\n this.#items.push(item);\n this.#wake();\n this.#onPush?.(item);\n }\n\n /**\n * Returns an async iterator starting at position {@link startAt}. Each call\n * returns an independent cursor so multiple consumers can iterate the same\n * channel concurrently.\n */\n iterate(startAt = 0): AsyncIterator<T> {\n let cursor = startAt;\n return {\n next: async (): Promise<IteratorResult<T>> => {\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (cursor < this.#items.length) {\n return { value: this.#items[cursor++], done: false };\n }\n if (this.#done) {\n if (this.#error) throw this.#error;\n return { value: undefined as unknown as T, done: true };\n }\n await new Promise<void>((resolve) => this.#waiters.push(resolve));\n }\n },\n };\n }\n\n /**\n * Creates an {@link AsyncIterable} backed by this channel, starting from\n * {@link startAt}.\n */\n toAsyncIterable(startAt = 0): AsyncIterable<T> {\n return {\n [Symbol.asyncIterator]: () => this.iterate(startAt),\n };\n }\n\n /**\n * Creates a web {@link ReadableStream} that emits channel items as\n * Server-Sent Events. Useful for returning a channel directly from\n * `new Response(channel.toEventStream())`.\n */\n toEventStream(\n options: StreamChannelEventStreamOptions<T> = {}\n ): ReadableStream<Uint8Array> {\n const encoder = new TextEncoder();\n const iterator = this.iterate(options.startAt);\n const event = options.event ?? this.channelName;\n const serialize =\n options.serialize ?? ((item: T) => JSON.stringify(item) ?? \"null\");\n\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n try {\n const next = await iterator.next();\n if (next.done) {\n controller.close();\n return;\n }\n\n const lines: string[] = [];\n if (event != null) {\n lines.push(`event: ${event}`);\n }\n for (const line of serialize(next.value).split(/\\r\\n|\\r|\\n/)) {\n lines.push(`data: ${line}`);\n }\n\n controller.enqueue(encoder.encode(`${lines.join(\"\\n\")}\\n\\n`));\n } catch (error) {\n controller.error(error);\n }\n },\n async cancel() {\n await iterator.return?.();\n },\n });\n }\n\n /**\n * Returns the item at the given zero-based index.\n *\n * @throws {RangeError} If the index is out of bounds.\n */\n get(index: number): T {\n if (index < 0 || index >= this.#items.length) {\n throw new RangeError(\n `StreamChannel index ${index} out of bounds (size=${this.#items.length})`\n );\n }\n return this.#items[index];\n }\n\n /** The number of items currently buffered in the channel. */\n get size(): number {\n return this.#items.length;\n }\n\n /** Whether the channel has been closed or failed. */\n get done(): boolean {\n return this.#done;\n }\n\n /** Mark the channel as complete after all buffered items are consumed. */\n close(): void {\n this.#done = true;\n this.#wake();\n }\n\n /** Mark the channel as failed after all buffered items are consumed. */\n fail(err: unknown): void {\n this.#error = err;\n this.#done = true;\n this.#wake();\n }\n\n /** @internal Called by the mux to wire auto-forwarding. */\n _wire(fn: (item: T) => void): void {\n this.#onPush = fn;\n }\n\n /** @internal Called by the mux on normal completion. */\n _close(): void {\n this.close();\n }\n\n /** @internal Called by the mux on failure. */\n _fail(err: unknown): void {\n this.fail(err);\n }\n\n [Symbol.asyncIterator](): AsyncIterator<T> {\n return this.iterate();\n }\n\n #wake(): void {\n const waiters = this.#waiters.splice(0);\n for (const w of waiters) w();\n }\n}\n\n/**\n * Type guard that tests whether a value is a {@link StreamChannel}.\n *\n * Uses a symbol brand rather than `instanceof` so channels built\n * against a different copy of this package (e.g. one bundled by the\n * `langchain` umbrella package) are still recognised.\n */\nexport function isStreamChannel(\n value: unknown\n): value is StreamChannel<unknown> {\n return StreamChannel.isInstance(value);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,uBAAsC,OAAO,IACxD,2BACD;;;;;;;;;;;AAiCD,IAAa,gBAAb,MAAa,cAA6C;;CAExD,CAAU,wBAAwB;;CAGlC;CAEA,SAAc,EAAE;CAChB,WAA8B,EAAE;CAChC,QAAQ;CACR;CACA;CAEA,YAAY,MAAe;AACzB,OAAK,cAAc;;;;;;CAOrB,OAAO,QAA6B;AAClC,SAAO,IAAI,eAAkB;;;;;;CAO/B,OAAO,OAAU,MAAgC;AAC/C,SAAO,IAAI,cAAiB,KAAK;;;;;;;;CASnC,OAAO,WAAW,OAAiD;AACjE,SACE,OAAO,UAAU,YACjB,UAAU,QACV,wBAAwB,SACvB,MAA8C,0BAC7C;;;;;;;CASN,KAAK,MAAe;AAClB,QAAA,MAAY,KAAK,KAAK;AACtB,QAAA,MAAY;AACZ,QAAA,SAAe,KAAK;;;;;;;CAQtB,QAAQ,UAAU,GAAqB;EACrC,IAAI,SAAS;AACb,SAAO,EACL,MAAM,YAAwC;AAE5C,UAAO,MAAM;AACX,QAAI,SAAS,MAAA,MAAY,OACvB,QAAO;KAAE,OAAO,MAAA,MAAY;KAAW,MAAM;KAAO;AAEtD,QAAI,MAAA,MAAY;AACd,SAAI,MAAA,MAAa,OAAM,MAAA;AACvB,YAAO;MAAE,OAAO,KAAA;MAA2B,MAAM;MAAM;;AAEzD,UAAM,IAAI,SAAe,YAAY,MAAA,QAAc,KAAK,QAAQ,CAAC;;KAGtE;;;;;;CAOH,gBAAgB,UAAU,GAAqB;AAC7C,SAAO,GACJ,OAAO,sBAAsB,KAAK,QAAQ,QAAQ,EACpD;;;;;;;CAQH,cACE,UAA8C,EAAE,EACpB;EAC5B,MAAM,UAAU,IAAI,aAAa;EACjC,MAAM,WAAW,KAAK,QAAQ,QAAQ,QAAQ;EAC9C,MAAM,QAAQ,QAAQ,SAAS,KAAK;EACpC,MAAM,YACJ,QAAQ,eAAe,SAAY,KAAK,UAAU,KAAK,IAAI;AAE7D,SAAO,IAAI,eAA2B;GACpC,MAAM,KAAK,YAAY;AACrB,QAAI;KACF,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,SAAI,KAAK,MAAM;AACb,iBAAW,OAAO;AAClB;;KAGF,MAAM,QAAkB,EAAE;AAC1B,SAAI,SAAS,KACX,OAAM,KAAK,UAAU,QAAQ;AAE/B,UAAK,MAAM,QAAQ,UAAU,KAAK,MAAM,CAAC,MAAM,aAAa,CAC1D,OAAM,KAAK,SAAS,OAAO;AAG7B,gBAAW,QAAQ,QAAQ,OAAO,GAAG,MAAM,KAAK,KAAK,CAAC,MAAM,CAAC;aACtD,OAAO;AACd,gBAAW,MAAM,MAAM;;;GAG3B,MAAM,SAAS;AACb,UAAM,SAAS,UAAU;;GAE5B,CAAC;;;;;;;CAQJ,IAAI,OAAkB;AACpB,MAAI,QAAQ,KAAK,SAAS,MAAA,MAAY,OACpC,OAAM,IAAI,WACR,uBAAuB,MAAM,uBAAuB,MAAA,MAAY,OAAO,GACxE;AAEH,SAAO,MAAA,MAAY;;;CAIrB,IAAI,OAAe;AACjB,SAAO,MAAA,MAAY;;;CAIrB,IAAI,OAAgB;AAClB,SAAO,MAAA;;;CAIT,QAAc;AACZ,QAAA,OAAa;AACb,QAAA,MAAY;;;CAId,KAAK,KAAoB;AACvB,QAAA,QAAc;AACd,QAAA,OAAa;AACb,QAAA,MAAY;;;CAId,MAAM,IAA6B;AACjC,QAAA,SAAe;;;CAIjB,SAAe;AACb,OAAK,OAAO;;;CAId,MAAM,KAAoB;AACxB,OAAK,KAAK,IAAI;;CAGhB,CAAC,OAAO,iBAAmC;AACzC,SAAO,KAAK,SAAS;;CAGvB,QAAc;EACZ,MAAM,UAAU,MAAA,QAAc,OAAO,EAAE;AACvC,OAAK,MAAM,KAAK,QAAS,IAAG;;;;;;;;;;AAWhC,SAAgB,gBACd,OACiC;AACjC,QAAO,cAAc,WAAW,MAAM"}
|
|
1
|
+
{"version":3,"file":"stream-channel.cjs","names":["#items","#wake","#onPush","#done","#error","#waiters"],"sources":["../../src/stream/stream-channel.ts"],"sourcesContent":["/**\n * StreamChannel — projection channel for local or remote streaming.\n *\n * A `StreamChannel<T>` is an append-only async stream with independent\n * cursors. Local channels stay in-process only. Remote channels declare a\n * protocol channel name; when registered with a {@link StreamMux} (via a\n * transformer's `init()` return value), every {@link push} is automatically\n * forwarded as a {@link ProtocolEvent} on `custom:<channelName>` — making the\n * data available both in-process (via `run.extensions`) and to remote clients\n * (via `session.subscribe(\"custom:<channelName>\")`).\n *\n * Lifecycle (`close` / `fail`) is managed by the mux automatically;\n * transformers do not need to call them.\n */\n\n/**\n * Branded symbol placed on every {@link StreamChannel} instance.\n *\n * Uses `Symbol.for` so the same symbol is shared across multiple\n * copies of this package that may coexist in a dependency graph\n * (e.g. when a user app imports `@langchain/langgraph` directly and a\n * wrapping library like `langchain` bundles its own copy). Using a\n * symbol brand instead of `instanceof` lets channels created against\n * one copy of the class be recognised by a mux from another.\n * @internal\n */\nexport const STREAM_CHANNEL_BRAND: unique symbol = Symbol.for(\n \"langgraph.stream_channel\"\n) as typeof STREAM_CHANNEL_BRAND;\n\nexport interface StreamChannelEventStreamOptions<T> {\n /**\n * SSE event name. Defaults to the channel's remote protocol name, if any.\n * Set this for local channels or when exposing the same channel under a\n * route-specific event name.\n */\n event?: string;\n /**\n * Cursor position to start streaming from. Useful for reconnects or\n * secondary subscribers that already consumed the first N buffered items and\n * only need replay from a known offset.\n */\n startAt?: number;\n /**\n * Serialize each item into the SSE `data:` field. Defaults to JSON. Use this\n * when a channel item needs a wire format other than its raw JSON shape, or\n * when the consumer expects line-oriented text payloads.\n */\n serialize?: (item: T) => string;\n}\n\n/**\n * A projection channel for {@link StreamTransformer}s.\n *\n * Implements `AsyncIterable<T>` so it can be iterated directly by\n * in-process consumers via `run.extensions.<key>`. Channels created with\n * {@link StreamChannel.remote} or `new StreamChannel(name)` are also\n * auto-forwarded to remote clients.\n *\n * @typeParam T - The type of items pushed into the channel.\n */\nexport class StreamChannel<T> implements AsyncIterable<T> {\n /** @internal Brand used by {@link StreamChannel.isInstance}. */\n readonly [STREAM_CHANNEL_BRAND] = true as const;\n\n /** Protocol channel name used for auto-forwarded events, if remote. */\n readonly channelName?: string;\n\n #items: T[] = [];\n #waiters: Array<() => void> = [];\n #done = false;\n #error: unknown;\n #onPush?: (item: T) => void;\n\n constructor(name?: string) {\n this.channelName = name;\n }\n\n /**\n * Create an in-process-only channel. Values remain available through\n * `run.extensions.<key>` but are not forwarded to remote clients.\n */\n static local<T>(): StreamChannel<T> {\n return new StreamChannel<T>();\n }\n\n /**\n * Create a channel whose pushes are forwarded to remote clients under\n * the given protocol channel name.\n */\n static remote<T>(name: string): StreamChannel<T> {\n return new StreamChannel<T>(name);\n }\n\n /**\n * Brand-based type guard that recognises any {@link StreamChannel}\n * instance, even ones originating from a different copy of this\n * package. Prefer this over `instanceof StreamChannel` when code\n * may observe channels that were constructed elsewhere.\n */\n static isInstance(value: unknown): value is StreamChannel<unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n STREAM_CHANNEL_BRAND in value &&\n (value as { [STREAM_CHANNEL_BRAND]: unknown })[STREAM_CHANNEL_BRAND] ===\n true\n );\n }\n\n /**\n * Append an item to the channel. If this is a remote channel wired to a\n * mux, the item is also injected into the main protocol event stream under\n * {@link channelName}.\n */\n push(item: T): void {\n this.#items.push(item);\n this.#wake();\n this.#onPush?.(item);\n }\n\n /**\n * Returns an async iterator starting at position {@link startAt}. Each call\n * returns an independent cursor so multiple consumers can iterate the same\n * channel concurrently.\n */\n iterate(startAt = 0): AsyncIterator<T> {\n let cursor = startAt;\n return {\n next: async (): Promise<IteratorResult<T>> => {\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (cursor < this.#items.length) {\n return { value: this.#items[cursor++], done: false };\n }\n if (this.#done) {\n if (this.#error) throw this.#error;\n return { value: undefined as unknown as T, done: true };\n }\n await new Promise<void>((resolve) => this.#waiters.push(resolve));\n }\n },\n };\n }\n\n /**\n * Creates an {@link AsyncIterable} backed by this channel, starting from\n * {@link startAt}.\n */\n toAsyncIterable(startAt = 0): AsyncIterable<T> {\n return {\n [Symbol.asyncIterator]: () => this.iterate(startAt),\n };\n }\n\n /**\n * Creates a web {@link ReadableStream} that emits channel items as\n * Server-Sent Events. Useful for returning a channel directly from\n * `new Response(channel.toEventStream())`.\n */\n toEventStream(\n options: StreamChannelEventStreamOptions<T> = {}\n ): ReadableStream<Uint8Array> {\n const encoder = new TextEncoder();\n const iterator = this.iterate(options.startAt);\n const event = options.event ?? this.channelName;\n const serialize =\n options.serialize ?? ((item: T) => JSON.stringify(item) ?? \"null\");\n\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n try {\n const next = await iterator.next();\n if (next.done) {\n controller.close();\n return;\n }\n\n const lines: string[] = [];\n if (event != null) {\n lines.push(`event: ${event}`);\n }\n for (const line of serialize(next.value).split(/\\r\\n|\\r|\\n/)) {\n lines.push(`data: ${line}`);\n }\n\n controller.enqueue(encoder.encode(`${lines.join(\"\\n\")}\\n\\n`));\n } catch (error) {\n controller.error(error);\n }\n },\n async cancel() {\n await iterator.return?.();\n },\n });\n }\n\n /**\n * Returns the item at the given zero-based index.\n *\n * @throws {RangeError} If the index is out of bounds.\n */\n get(index: number): T {\n if (index < 0 || index >= this.#items.length) {\n throw new RangeError(\n `StreamChannel index ${index} out of bounds (size=${this.#items.length})`\n );\n }\n return this.#items[index];\n }\n\n /** The number of items currently buffered in the channel. */\n get size(): number {\n return this.#items.length;\n }\n\n /** Whether the channel has been closed or failed. */\n get done(): boolean {\n return this.#done;\n }\n\n /** Mark the channel as complete after all buffered items are consumed. */\n close(): void {\n this.#done = true;\n this.#wake();\n }\n\n /** Mark the channel as failed after all buffered items are consumed. */\n fail(err: unknown): void {\n this.#error = err;\n this.#done = true;\n this.#wake();\n }\n\n /** @internal Called by the mux to wire auto-forwarding. */\n _wire(fn: (item: T) => void): void {\n this.#onPush = fn;\n }\n\n /** @internal Called by the mux on normal completion. */\n _close(): void {\n this.close();\n }\n\n /** @internal Called by the mux on failure. */\n _fail(err: unknown): void {\n this.fail(err);\n }\n\n [Symbol.asyncIterator](): AsyncIterator<T> {\n return this.iterate();\n }\n\n #wake(): void {\n const waiters = this.#waiters.splice(0);\n for (const w of waiters) w();\n }\n}\n\n/**\n * Type guard that tests whether a value is a {@link StreamChannel}.\n *\n * Uses a symbol brand rather than `instanceof` so channels built\n * against a different copy of this package (e.g. one bundled by the\n * `langchain` umbrella package) are still recognised.\n */\nexport function isStreamChannel(\n value: unknown\n): value is StreamChannel<unknown> {\n return StreamChannel.isInstance(value);\n}\n"],"mappings":"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"}
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* cursors. Local channels stay in-process only. Remote channels declare a
|
|
7
7
|
* protocol channel name; when registered with a {@link StreamMux} (via a
|
|
8
8
|
* transformer's `init()` return value), every {@link push} is automatically
|
|
9
|
-
* forwarded as a {@link ProtocolEvent} on
|
|
10
|
-
* available both in-process (via `run.extensions`) and to remote clients
|
|
11
|
-
* `session.subscribe("custom:<channelName>")`).
|
|
9
|
+
* forwarded as a {@link ProtocolEvent} on `custom:<channelName>` — making the
|
|
10
|
+
* data available both in-process (via `run.extensions`) and to remote clients
|
|
11
|
+
* (via `session.subscribe("custom:<channelName>")`).
|
|
12
12
|
*
|
|
13
13
|
* Lifecycle (`close` / `fail`) is managed by the mux automatically;
|
|
14
14
|
* transformers do not need to call them.
|
|
@@ -124,6 +124,14 @@ declare class StreamChannel<T> implements AsyncIterable<T> {
|
|
|
124
124
|
_fail(err: unknown): void;
|
|
125
125
|
[Symbol.asyncIterator](): AsyncIterator<T>;
|
|
126
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Type guard that tests whether a value is a {@link StreamChannel}.
|
|
129
|
+
*
|
|
130
|
+
* Uses a symbol brand rather than `instanceof` so channels built
|
|
131
|
+
* against a different copy of this package (e.g. one bundled by the
|
|
132
|
+
* `langchain` umbrella package) are still recognised.
|
|
133
|
+
*/
|
|
134
|
+
declare function isStreamChannel(value: unknown): value is StreamChannel<unknown>;
|
|
127
135
|
//#endregion
|
|
128
|
-
export { StreamChannel };
|
|
136
|
+
export { StreamChannel, isStreamChannel };
|
|
129
137
|
//# sourceMappingURL=stream-channel.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-channel.d.cts","names":[],"sources":["../../src/stream/stream-channel.ts"],"mappings":";;AA0BA;;;;;AAIA;;;;;;;;;;;;AA+BA;;;;;;;cAnCa,oBAAA;AAAA,UAII,+BAAA;EAsE6B;;;;;EAhE5C,KAAA;EA6H2C;;;;;EAvH3C,OAAA;EA+MwC;;;;;EAzMxC,SAAA,IAAa,IAAA,EAAM,CAAA;AAAA;;;;;;;;;;;cAaR,aAAA,eAA4B,aAAA,CAAc,CAAA;EAAA;EA6BvC;EAAA,UA3BJ,oBAAA;EA2BsB;EAAA,SAxBvB,WAAA;EAQT,WAAA,CAAY,IAAA;EA0BM;;;;EAAA,OAlBX,KAAA,GAAA,CAAA,GAAY,aAAA,CAAc,CAAA;EAiC5B;;;;EAAA,OAzBE,MAAA,GAAA,CAAU,IAAA,WAAe,aAAA,CAAc,CAAA;EA2D9C;;;;;;EAAA,OAjDO,UAAA,CAAW,KAAA,YAAiB,KAAA,IAAS,aAAA;EA6D1C;;;;;EA9CF,IAAA,CAAK,IAAA,EAAM,CAAA;EAiGP;;;;;EAtFJ,OAAA,CAAQ,OAAA,YAAc,aAAA,CAAc,CAAA;EA6GnB;;;;EAtFjB,eAAA,CAAgB,OAAA,YAAc,aAAA,CAAc,CAAA;EAgGtC;;;;;EArFN,aAAA,CACE,OAAA,GAAS,+BAAA,CAAgC,CAAA,IACxC,cAAA,CAAe,UAAA;EAuFuB
|
|
1
|
+
{"version":3,"file":"stream-channel.d.cts","names":[],"sources":["../../src/stream/stream-channel.ts"],"mappings":";;AA0BA;;;;;AAIA;;;;;;;;;;;;AA+BA;;;;;;;cAnCa,oBAAA;AAAA,UAII,+BAAA;EAsE6B;;;;;EAhE5C,KAAA;EA6H2C;;;;;EAvH3C,OAAA;EA+MwC;;;;;EAzMxC,SAAA,IAAa,IAAA,EAAM,CAAA;AAAA;;;;;;;;;;;cAaR,aAAA,eAA4B,aAAA,CAAc,CAAA;EAAA;EA6BvC;EAAA,UA3BJ,oBAAA;EA2BsB;EAAA,SAxBvB,WAAA;EAQT,WAAA,CAAY,IAAA;EA0BM;;;;EAAA,OAlBX,KAAA,GAAA,CAAA,GAAY,aAAA,CAAc,CAAA;EAiC5B;;;;EAAA,OAzBE,MAAA,GAAA,CAAU,IAAA,WAAe,aAAA,CAAc,CAAA;EA2D9C;;;;;;EAAA,OAjDO,UAAA,CAAW,KAAA,YAAiB,KAAA,IAAS,aAAA;EA6D1C;;;;;EA9CF,IAAA,CAAK,IAAA,EAAM,CAAA;EAiGP;;;;;EAtFJ,OAAA,CAAQ,OAAA,YAAc,aAAA,CAAc,CAAA;EA6GnB;;;;EAtFjB,eAAA,CAAgB,OAAA,YAAc,aAAA,CAAc,CAAA;EAgGtC;;;;;EArFN,aAAA,CACE,OAAA,GAAS,+BAAA,CAAgC,CAAA,IACxC,cAAA,CAAe,UAAA;EAuFuB;AAiB3C;;;;EAhEE,GAAA,CAAI,KAAA,WAAgB,CAAA;EAkEnB;EAAA,IAxDG,IAAA,CAAA;EAwDmB;EAAA,IAnDnB,IAAA,CAAA;;EAKJ,KAAA,CAAA;;EAMA,IAAA,CAAK,GAAA;;EAOL,KAAA,CAAM,EAAA,GAAK,IAAA,EAAM,CAAA;;EAKjB,MAAA,CAAA;;EAKA,KAAA,CAAM,GAAA;EAAA,CAIL,MAAA,CAAO,aAAA,KAAkB,aAAA,CAAc,CAAA;AAAA;;;;;;;;iBAiB1B,eAAA,CACd,KAAA,YACC,KAAA,IAAS,aAAA"}
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* cursors. Local channels stay in-process only. Remote channels declare a
|
|
7
7
|
* protocol channel name; when registered with a {@link StreamMux} (via a
|
|
8
8
|
* transformer's `init()` return value), every {@link push} is automatically
|
|
9
|
-
* forwarded as a {@link ProtocolEvent} on
|
|
10
|
-
* available both in-process (via `run.extensions`) and to remote clients
|
|
11
|
-
* `session.subscribe("custom:<channelName>")`).
|
|
9
|
+
* forwarded as a {@link ProtocolEvent} on `custom:<channelName>` — making the
|
|
10
|
+
* data available both in-process (via `run.extensions`) and to remote clients
|
|
11
|
+
* (via `session.subscribe("custom:<channelName>")`).
|
|
12
12
|
*
|
|
13
13
|
* Lifecycle (`close` / `fail`) is managed by the mux automatically;
|
|
14
14
|
* transformers do not need to call them.
|
|
@@ -124,6 +124,14 @@ declare class StreamChannel<T> implements AsyncIterable<T> {
|
|
|
124
124
|
_fail(err: unknown): void;
|
|
125
125
|
[Symbol.asyncIterator](): AsyncIterator<T>;
|
|
126
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Type guard that tests whether a value is a {@link StreamChannel}.
|
|
129
|
+
*
|
|
130
|
+
* Uses a symbol brand rather than `instanceof` so channels built
|
|
131
|
+
* against a different copy of this package (e.g. one bundled by the
|
|
132
|
+
* `langchain` umbrella package) are still recognised.
|
|
133
|
+
*/
|
|
134
|
+
declare function isStreamChannel(value: unknown): value is StreamChannel<unknown>;
|
|
127
135
|
//#endregion
|
|
128
|
-
export { StreamChannel };
|
|
136
|
+
export { StreamChannel, isStreamChannel };
|
|
129
137
|
//# sourceMappingURL=stream-channel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-channel.d.ts","names":[],"sources":["../../src/stream/stream-channel.ts"],"mappings":";;AA0BA;;;;;AAIA;;;;;;;;;;;;AA+BA;;;;;;;cAnCa,oBAAA;AAAA,UAII,+BAAA;EAsE6B;;;;;EAhE5C,KAAA;EA6H2C;;;;;EAvH3C,OAAA;EA+MwC;;;;;EAzMxC,SAAA,IAAa,IAAA,EAAM,CAAA;AAAA;;;;;;;;;;;cAaR,aAAA,eAA4B,aAAA,CAAc,CAAA;EAAA;EA6BvC;EAAA,UA3BJ,oBAAA;EA2BsB;EAAA,SAxBvB,WAAA;EAQT,WAAA,CAAY,IAAA;EA0BM;;;;EAAA,OAlBX,KAAA,GAAA,CAAA,GAAY,aAAA,CAAc,CAAA;EAiC5B;;;;EAAA,OAzBE,MAAA,GAAA,CAAU,IAAA,WAAe,aAAA,CAAc,CAAA;EA2D9C;;;;;;EAAA,OAjDO,UAAA,CAAW,KAAA,YAAiB,KAAA,IAAS,aAAA;EA6D1C;;;;;EA9CF,IAAA,CAAK,IAAA,EAAM,CAAA;EAiGP;;;;;EAtFJ,OAAA,CAAQ,OAAA,YAAc,aAAA,CAAc,CAAA;EA6GnB;;;;EAtFjB,eAAA,CAAgB,OAAA,YAAc,aAAA,CAAc,CAAA;EAgGtC;;;;;EArFN,aAAA,CACE,OAAA,GAAS,+BAAA,CAAgC,CAAA,IACxC,cAAA,CAAe,UAAA;EAuFuB
|
|
1
|
+
{"version":3,"file":"stream-channel.d.ts","names":[],"sources":["../../src/stream/stream-channel.ts"],"mappings":";;AA0BA;;;;;AAIA;;;;;;;;;;;;AA+BA;;;;;;;cAnCa,oBAAA;AAAA,UAII,+BAAA;EAsE6B;;;;;EAhE5C,KAAA;EA6H2C;;;;;EAvH3C,OAAA;EA+MwC;;;;;EAzMxC,SAAA,IAAa,IAAA,EAAM,CAAA;AAAA;;;;;;;;;;;cAaR,aAAA,eAA4B,aAAA,CAAc,CAAA;EAAA;EA6BvC;EAAA,UA3BJ,oBAAA;EA2BsB;EAAA,SAxBvB,WAAA;EAQT,WAAA,CAAY,IAAA;EA0BM;;;;EAAA,OAlBX,KAAA,GAAA,CAAA,GAAY,aAAA,CAAc,CAAA;EAiC5B;;;;EAAA,OAzBE,MAAA,GAAA,CAAU,IAAA,WAAe,aAAA,CAAc,CAAA;EA2D9C;;;;;;EAAA,OAjDO,UAAA,CAAW,KAAA,YAAiB,KAAA,IAAS,aAAA;EA6D1C;;;;;EA9CF,IAAA,CAAK,IAAA,EAAM,CAAA;EAiGP;;;;;EAtFJ,OAAA,CAAQ,OAAA,YAAc,aAAA,CAAc,CAAA;EA6GnB;;;;EAtFjB,eAAA,CAAgB,OAAA,YAAc,aAAA,CAAc,CAAA;EAgGtC;;;;;EArFN,aAAA,CACE,OAAA,GAAS,+BAAA,CAAgC,CAAA,IACxC,cAAA,CAAe,UAAA;EAuFuB;AAiB3C;;;;EAhEE,GAAA,CAAI,KAAA,WAAgB,CAAA;EAkEnB;EAAA,IAxDG,IAAA,CAAA;EAwDmB;EAAA,IAnDnB,IAAA,CAAA;;EAKJ,KAAA,CAAA;;EAMA,IAAA,CAAK,GAAA;;EAOL,KAAA,CAAM,EAAA,GAAK,IAAA,EAAM,CAAA;;EAKjB,MAAA,CAAA;;EAKA,KAAA,CAAM,GAAA;EAAA,CAIL,MAAA,CAAO,aAAA,KAAkB,aAAA,CAAc,CAAA;AAAA;;;;;;;;iBAiB1B,eAAA,CACd,KAAA,YACC,KAAA,IAAS,aAAA"}
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* cursors. Local channels stay in-process only. Remote channels declare a
|
|
7
7
|
* protocol channel name; when registered with a {@link StreamMux} (via a
|
|
8
8
|
* transformer's `init()` return value), every {@link push} is automatically
|
|
9
|
-
* forwarded as a {@link ProtocolEvent} on
|
|
10
|
-
* available both in-process (via `run.extensions`) and to remote clients
|
|
11
|
-
* `session.subscribe("custom:<channelName>")`).
|
|
9
|
+
* forwarded as a {@link ProtocolEvent} on `custom:<channelName>` — making the
|
|
10
|
+
* data available both in-process (via `run.extensions`) and to remote clients
|
|
11
|
+
* (via `session.subscribe("custom:<channelName>")`).
|
|
12
12
|
*
|
|
13
13
|
* Lifecycle (`close` / `fail`) is managed by the mux automatically;
|
|
14
14
|
* transformers do not need to call them.
|