UNPKG

@langchain/langgraph-sdk

Version:

Client library for interacting with the LangGraph API

271 lines (270 loc) • 7.89 kB
import { MultiCursorBuffer } from "../multi-cursor-buffer.js"; import { ToolCallAssembler, shouldIgnoreScopedTaskToolEvent, toClientAssembledToolCall } from "./tools.js"; import { StreamingMessageAssembler } from "../messages.js"; import { MediaAssembler } from "../media.js"; import { SubgraphDiscoveryHandle, isTerminalLifecycleAt } from "./subgraphs.js"; //#region src/client/stream/handles/subagents.ts /** * Discovered subagent within a streaming session. Mirrors the * in-process `SubagentRunStream` from DeepAgent. * * Each subagent is discovered when a `tool-started` event with * `tool_name === "task"` is observed. The `taskInput` and `output` * promises resolve from the task tool's lifecycle events. * * Use lazy getters (`sub.messages`, `sub.toolCalls`, etc.) for * namespace-scoped projections. */ var SubagentHandle = class { name; callId; taskInput; output; namespace; #session; #messagesIterable; #toolCallsIterable; #subgraphsIterable; #mediaDispatcherStarted = false; #audioBuffer; #imagesBuffer; #videoBuffer; #filesBuffer; constructor(name, callId, namespace, taskInput, output, session) { this.name = name; this.callId = callId; this.namespace = namespace; this.taskInput = taskInput; this.output = output; this.#session = session; } get messages() { if (this.#messagesIterable) return this.#messagesIterable; const buffer = new MultiCursorBuffer(); this.#messagesIterable = buffer; const assembler = new 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 toolCalls() { if (this.#toolCallsIterable) return this.#toolCallsIterable; const buffer = new MultiCursorBuffer(); this.#toolCallsIterable = buffer; const assembler = new ToolCallAssembler(); this.#startProjection(["tools"], (event) => { if (event.method !== "tools") return; const toolsEvent = event; if (shouldIgnoreScopedTaskToolEvent(this.namespace, toolsEvent)) return; const tc = assembler.consume(toolsEvent); if (tc) buffer.push(toClientAssembledToolCall(tc)); }, () => 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; } #ensureMediaDispatcher() { if (this.#mediaDispatcherStarted) return; this.#mediaDispatcherStarted = true; const audio = new MultiCursorBuffer(); const images = new MultiCursorBuffer(); const video = new MultiCursorBuffer(); const files = new MultiCursorBuffer(); this.#audioBuffer = audio; this.#imagesBuffer = images; this.#videoBuffer = video; this.#filesBuffer = files; const assembler = new 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(); }); } get subgraphs() { if (this.#subgraphsIterable) return this.#subgraphsIterable; const buffer = new MultiCursorBuffer(); this.#subgraphsIterable = buffer; (async () => { const discovery = new SubgraphDiscoveryHandle(await this.#session.subscribe({ channels: ["lifecycle"], namespaces: [this.namespace] }), this.#session, this.namespace); for await (const sub of discovery) buffer.push(sub); buffer.close(); })(); return buffer; } 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 SubagentHandle} instances as task * tool calls are discovered from the `tools` channel. * * Mirrors the in-process `createSubagentTransformer` from DeepAgent: * watches for `tool_name === "task"` with `tool-started`, extracts * `subagent_type` and `description` from the input, and resolves * `output` on `tool-finished`. */ var SubagentDiscoveryHandle = class { #source; #session; #queue = []; #waiters = []; #pending = /* @__PURE__ */ new Map(); #sourcePump; #closed = false; constructor(source, session) { this.#source = source; this.#session = session; } #processEvent(event) { if (event.method !== "tools") return void 0; const tools = event; const data = tools.params.data; const toolCallId = data.tool_call_id; if (data.tool_name === "task" && data.event === "tool-started") { const rawInput = data.input; const input = typeof rawInput === "string" ? JSON.parse(rawInput) : rawInput ?? {}; const name = input.subagent_type ?? "unknown"; const description = input.description ?? ""; let resolveTaskInput; let resolveOutput; let rejectOutput; const taskInput = new Promise((r) => { resolveTaskInput = r; }); const output = new Promise((res, rej) => { resolveOutput = res; rejectOutput = rej; }); resolveTaskInput(description); this.#pending.set(toolCallId, { resolveOutput, rejectOutput }); return new SubagentHandle(name, toolCallId, [...tools.params.namespace], taskInput, output, this.#session); } if (toolCallId) { const pending = this.#pending.get(toolCallId); if (pending) { if (data.event === "tool-finished") { pending.resolveOutput(data.output); this.#pending.delete(toolCallId); } else if (data.event === "tool-error") { const message = data.message ?? "unknown error"; pending.rejectOutput(new Error(message)); this.#pending.delete(toolCallId); } } } } #start() { if (this.#sourcePump) return; this.#sourcePump = (async () => { for await (const event of this.#source) { const handle = this.#processEvent(event); if (!handle) continue; const waiter = this.#waiters.shift(); if (waiter) waiter({ done: false, value: handle }); else this.#queue.push(handle); } this.#closed = true; for (const pending of this.#pending.values()) pending.resolveOutput(void 0); this.#pending.clear(); 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 export { SubagentDiscoveryHandle, SubagentHandle }; //# sourceMappingURL=subagents.js.map