UNPKG

@langchain/langgraph-sdk

Version:

Client library for interacting with the LangGraph API

374 lines (373 loc) • 11.7 kB
const require_multi_cursor_buffer = require("../multi-cursor-buffer.cjs"); const require_tools = require("./tools.cjs"); const require_messages = require("../messages.cjs"); const require_media = require("../media.cjs"); //#region src/client/stream/handles/subgraphs.ts const TERMINAL_LIFECYCLE_EVENTS = new Set([ "completed", "failed", "interrupted" ]); /** Whether `event` is a terminal lifecycle event for exactly `namespace`. */ function isTerminalLifecycleAt(event, namespace) { if (event.method !== "lifecycle") return false; const eventNamespace = event.params.namespace; if (eventNamespace.length !== namespace.length) return false; if (!namespace.every((segment, i) => eventNamespace[i] === segment)) return false; const data = event.params.data; return data?.event != null && TERMINAL_LIFECYCLE_EVENTS.has(data.event); } /** * Discovered subgraph within a streaming session. * * Mirrors the in-process `SubgraphRunStream` pattern: each subgraph * has `name`, `index`, `namespace`, and lazy getters for projections * scoped to this subgraph's namespace. * * ```ts * for await (const sub of session.subgraphs) { * for await (const msg of sub.messages) { ... } * const state = await sub.output; * } * ``` */ var SubgraphHandle = class { name; index; namespace; /** * Non-empty when upstream attached a `cause` to this subgraph's * `lifecycle.started` event. Population is product-specific and * performed by stream transformers on the runtime side (e.g. * deepagents' `SubagentTransformer` emits * `{ type: "toolCall", tool_call_id }`). Generic clients should * treat `cause.type` as an open enum — the protocol allows future * variants (`send`, `edge`, ...) to be forwarded verbatim without * a SDK bump. */ cause; graphName; /** * Raw `tool-started` event that triggered this subgraph, when * `cause.type === "toolCall"` and the matching event has been * observed on the `tools` channel. */ toolStartedEvent; #session; #messagesIterable; #valuesProjection; #toolCallsIterable; #subgraphsIterable; #subagentsIterable; #outputPromise; #mediaDispatcherStarted = false; #audioBuffer; #imagesBuffer; #videoBuffer; #filesBuffer; constructor(name, index, namespace, session, options) { this.name = name; this.index = index; this.namespace = namespace; this.cause = options?.cause; this.graphName = options?.graphName; this.toolStartedEvent = options?.toolStartedEvent; this.#session = session; } get messages() { if (this.#messagesIterable) return this.#messagesIterable; const buffer = new require_multi_cursor_buffer.MultiCursorBuffer(); this.#messagesIterable = buffer; const assembler = new require_messages.StreamingMessageAssembler(); this.#startProjection(["messages", "lifecycle"], (event) => { if (isTerminalLifecycleAt(event, this.namespace)) { assembler.abandonActive(this.namespace); return; } if (event.method !== "messages") return; const msg = assembler.consume(event); if (msg) buffer.push(msg); }, () => { assembler.abandonActive(); buffer.close(); }); return buffer; } get values() { if (this.#valuesProjection) return this.#valuesProjection; const buffer = new require_multi_cursor_buffer.MultiCursorBuffer(); let lastValue; let resolveOutput; const outputPromise = new Promise((resolve) => { resolveOutput = resolve; }); this.#outputPromise = outputPromise; const projection = Object.assign(buffer, { then: (onfulfilled, onrejected) => outputPromise.then(onfulfilled, onrejected) }); this.#valuesProjection = projection; this.#startProjection(["values"], (event) => { if (event.method !== "values") return; const data = event.params.data; lastValue = data; buffer.push(data); }, () => { resolveOutput(lastValue); buffer.close(); }); return projection; } get toolCalls() { if (this.#toolCallsIterable) return this.#toolCallsIterable; const buffer = new require_multi_cursor_buffer.MultiCursorBuffer(); this.#toolCallsIterable = buffer; const assembler = new require_tools.ToolCallAssembler(); this.#startProjection(["tools"], (event) => { if (event.method !== "tools") return; const tc = assembler.consume(event); if (tc) buffer.push(require_tools.toClientAssembledToolCall(tc)); }, () => buffer.close()); return buffer; } get subgraphs() { if (this.#subgraphsIterable) return this.#subgraphsIterable; const buffer = new require_multi_cursor_buffer.MultiCursorBuffer(); this.#subgraphsIterable = buffer; (async () => { const discovery = new SubgraphDiscoveryHandle(await this.#session.subscribe({ channels: ["lifecycle", "tools"], namespaces: [this.namespace] }), this.#session, this.namespace); for await (const sub of discovery) buffer.push(sub); buffer.close(); })(); return buffer; } get subagents() { if (this.#subagentsIterable) return this.#subagentsIterable; const buffer = new require_multi_cursor_buffer.MultiCursorBuffer(); this.#subagentsIterable = buffer; (async () => { const rawHandle = await this.#session.subscribe({ channels: ["tools", "lifecycle"], namespaces: [this.namespace] }); const { SubagentDiscoveryHandle: Discovery } = await Promise.resolve().then(() => require("./subagents.cjs")); const discovery = new Discovery(rawHandle, this.#session); for await (const sub of discovery) buffer.push(sub); buffer.close(); })(); return buffer; } get audio() { this.#ensureMediaDispatcher(); return this.#audioBuffer; } get images() { this.#ensureMediaDispatcher(); return this.#imagesBuffer; } get video() { this.#ensureMediaDispatcher(); return this.#videoBuffer; } get files() { this.#ensureMediaDispatcher(); return this.#filesBuffer; } get output() { this.values; return this.#outputPromise; } #ensureMediaDispatcher() { if (this.#mediaDispatcherStarted) return; this.#mediaDispatcherStarted = true; const audio = new require_multi_cursor_buffer.MultiCursorBuffer(); const images = new require_multi_cursor_buffer.MultiCursorBuffer(); const video = new require_multi_cursor_buffer.MultiCursorBuffer(); const files = new require_multi_cursor_buffer.MultiCursorBuffer(); this.#audioBuffer = audio; this.#imagesBuffer = images; this.#videoBuffer = video; this.#filesBuffer = files; const assembler = new require_media.MediaAssembler({ onAudio: (m) => audio.push(m), onImage: (m) => images.push(m), onVideo: (m) => video.push(m), onFile: (m) => files.push(m) }); this.#startProjection(["messages"], (event) => { if (event.method !== "messages") return; assembler.consume(event); }, () => { assembler.close(); audio.close(); images.close(); video.close(); files.close(); }); } subscribe(paramsOrChannels, options = {}) { if (typeof paramsOrChannels === "object" && !Array.isArray(paramsOrChannels) && "channels" in paramsOrChannels) return this.#session.subscribe({ ...paramsOrChannels, namespaces: paramsOrChannels.namespaces ?? [this.namespace] }); return this.#session.subscribe(paramsOrChannels, { ...options, namespaces: options.namespaces ?? [this.namespace] }); } async #startProjection(channels, onEvent, onDone) { try { const rawHandle = await this.#session.subscribe({ channels, namespaces: [this.namespace] }); for await (const event of rawHandle) onEvent(event); } finally { onDone(); } } }; /** * Async iterable that yields {@link SubgraphHandle} instances as new * subgraph namespaces are discovered from `lifecycle` events. * * Mirrors the in-process `run.subgraphs` pattern. A new subgraph is * discovered when a `lifecycle` event with `event: "started"` is * received at a namespace depth of exactly `parentDepth + 1`. */ var SubgraphDiscoveryHandle = class { #source; #session; #parentNamespace; #discovered = /* @__PURE__ */ new Set(); #pendingToolStarts = /* @__PURE__ */ new Map(); #pendingToolCallHandles = /* @__PURE__ */ new Map(); #queue = []; #waiters = []; #sourcePump; #closed = false; constructor(source, session, parentNamespace = []) { this.#source = source; this.#session = session; this.#parentNamespace = parentNamespace; } #emit(handle) { const waiter = this.#waiters.shift(); if (waiter) waiter({ done: false, value: handle }); else this.#queue.push(handle); } #processToolEvent(event) { if (event.method !== "tools") return false; const tools = event; const data = tools.params.data; if (data.event !== "tool-started") return true; const toolCallId = data.tool_call_id; if (!toolCallId) return true; const pendingHandle = this.#pendingToolCallHandles.get(toolCallId); if (pendingHandle) { pendingHandle.toolStartedEvent = tools; this.#pendingToolCallHandles.delete(toolCallId); return true; } this.#pendingToolStarts.set(toolCallId, tools); return true; } #processEvent(event) { if (this.#processToolEvent(event)) return void 0; if (event.method !== "lifecycle") return void 0; const lifecycle = event; if (lifecycle.params.data.event !== "started") return void 0; const ns = event.params.namespace; if (ns.length !== this.#parentNamespace.length + 1) return void 0; if (!this.#parentNamespace.every((seg, i) => ns[i] === seg)) return void 0; const nsKey = ns.join("/"); if (this.#discovered.has(nsKey)) return void 0; this.#discovered.add(nsKey); const lastSegment = ns[ns.length - 1] ?? ""; const colonIdx = lastSegment.lastIndexOf(":"); let name; let index; if (colonIdx >= 0) { name = lastSegment.slice(0, colonIdx); const suffix = lastSegment.slice(colonIdx + 1); index = /^\d+$/.test(suffix) ? Number(suffix) : 0; } else { name = lastSegment; index = 0; } const data = lifecycle.params.data; const cause = data.cause && typeof data.cause === "object" ? data.cause : void 0; let toolStartedEvent; if (cause?.type === "toolCall") { const toolCallId = cause.tool_call_id; if (toolCallId) { toolStartedEvent = this.#pendingToolStarts.get(toolCallId); this.#pendingToolStarts.delete(toolCallId); } } const handle = new SubgraphHandle(name, index, [...ns], this.#session, { cause, graphName: data.graph_name, toolStartedEvent }); if (cause?.type === "toolCall" && toolStartedEvent == null) { const toolCallId = cause.tool_call_id; if (toolCallId) this.#pendingToolCallHandles.set(toolCallId, handle); } return handle; } #start() { if (this.#sourcePump) return; this.#sourcePump = (async () => { for await (const event of this.#source) { const handle = this.#processEvent(event); if (!handle) continue; this.#emit(handle); } this.#pendingToolStarts.clear(); this.#pendingToolCallHandles.clear(); this.#closed = true; while (this.#waiters.length > 0) this.#waiters.shift()?.({ done: true, value: void 0 }); })(); } async close() { this.#closed = true; await this.#source.unsubscribe(); } [Symbol.asyncIterator]() { this.#start(); return { next: async () => { if (this.#queue.length > 0) return { done: false, value: this.#queue.shift() }; if (this.#closed) return { done: true, value: void 0 }; return await new Promise((resolve) => { this.#waiters.push(resolve); }); }, return: async () => { await this.close(); return { done: true, value: void 0 }; } }; } }; //#endregion exports.SubgraphDiscoveryHandle = SubgraphDiscoveryHandle; exports.SubgraphHandle = SubgraphHandle; exports.isTerminalLifecycleAt = isTerminalLifecycleAt; //# sourceMappingURL=subgraphs.cjs.map