UNPKG

@langchain/langgraph-sdk

Version:

Client library for interacting with the LangGraph API

197 lines (196 loc) • 6.43 kB
"use client"; import { BytesLineDecoder, SSEDecoder } from "../utils/sse.js"; import { IterableReadableStream } from "../utils/stream.js"; import "../stream/constants.js"; import { MessageTupleManager } from "../ui/messages.js"; import { flushPendingHeadlessToolInterrupts } from "../headless-tools.js"; import { userFacingInterruptsFromValuesArray } from "../ui/interrupts.js"; import { getToolCallsWithResults } from "../utils/tools.js"; import { StreamManager } from "../ui/manager.js"; import { useControllableThreadId } from "./thread.js"; import { useEffect, useRef, useState, useSyncExternalStore } from "react"; //#region src/react/stream.custom.tsx var FetchStreamTransport = class { constructor(options) { this.options = options; } async stream(payload) { const { signal, ...body } = payload; let requestInit = { method: "POST", headers: { "Content-Type": "application/json", ...this.options.defaultHeaders }, body: JSON.stringify(body), signal }; if (this.options.onRequest) requestInit = await this.options.onRequest(this.options.apiUrl, requestInit); const response = await (this.options.fetch ?? fetch)(this.options.apiUrl, requestInit); if (!response.ok) throw new Error(`Failed to stream: ${response.statusText}`); const stream = (response.body || new ReadableStream({ start: (ctrl) => ctrl.close() })).pipeThrough(BytesLineDecoder()).pipeThrough(SSEDecoder()); return IterableReadableStream.fromReadableStream(stream); } }; function createCustomTransportThreadState(values, threadId) { return { values, next: [], tasks: [], metadata: void 0, created_at: null, checkpoint: { thread_id: threadId, checkpoint_id: null, checkpoint_ns: "", checkpoint_map: null }, parent_checkpoint: null }; } function useStreamCustom(options) { const [messageManager] = useState(() => new MessageTupleManager()); const [stream] = useState(() => new StreamManager(messageManager, { throttle: options.throttle ?? false, subagentToolNames: options.subagentToolNames, filterSubagentMessages: options.filterSubagentMessages })); useSyncExternalStore(stream.subscribe, stream.getSnapshot, stream.getSnapshot); const [threadId, onThreadId] = useControllableThreadId(options); const threadIdRef = useRef(threadId); useEffect(() => { if (threadIdRef.current !== threadId) { threadIdRef.current = threadId; stream.clear(); } }, [threadId, stream]); const getMessages = (value) => { const messagesKey = options.messagesKey ?? "messages"; return Array.isArray(value[messagesKey]) ? value[messagesKey] : []; }; const setMessages = (current, messages) => { const messagesKey = options.messagesKey ?? "messages"; return { ...current, [messagesKey]: messages }; }; const historyValues = options.initialValues ?? {}; const historyMessages = getMessages(historyValues); const shouldReconstructSubagents = options.filterSubagentMessages && !stream.isLoading && historyMessages.length > 0; useEffect(() => { if (shouldReconstructSubagents) stream.reconstructSubagents(historyMessages, { skipIfPopulated: true }); }, [shouldReconstructSubagents, historyMessages.length]); const stop = () => stream.stop(historyValues, { onStop: options.onStop }); const submit = async (values, submitOptions) => { let usableThreadId = threadId; stream.setStreamValues(() => { if (submitOptions?.optimisticValues != null) return { ...historyValues, ...typeof submitOptions.optimisticValues === "function" ? submitOptions.optimisticValues(historyValues) : submitOptions.optimisticValues }; return { ...historyValues }; }); await stream.start(async (signal) => { if (!usableThreadId) { usableThreadId = crypto.randomUUID(); threadIdRef.current = usableThreadId; onThreadId(usableThreadId); } if (!usableThreadId) throw new Error("Failed to obtain valid thread ID."); return options.transport.stream({ input: values, context: submitOptions?.context, command: submitOptions?.command, signal, config: { ...submitOptions?.config, configurable: { thread_id: usableThreadId, ...submitOptions?.config?.configurable } } }); }, { getMessages, setMessages, initialValues: {}, callbacks: options, onSuccess: () => { if (!usableThreadId) return void 0; const finalValues = stream.values ?? historyValues; options.onFinish?.(createCustomTransportThreadState(finalValues, usableThreadId), void 0); }, onError(error) { options.onError?.(error, void 0); } }); }; const handledToolsRef = useRef(/* @__PURE__ */ new Set()); useEffect(() => { handledToolsRef.current.clear(); }, [threadId]); useEffect(() => { flushPendingHeadlessToolInterrupts(stream.values, options.tools, handledToolsRef.current, { onTool: options.onTool, defer: (run) => { Promise.resolve().then(run); }, resumeSubmit: (command) => submit(null, { command }) }); }, [ options.onTool, options.tools, stream.values, submit ]); return { get values() { return stream.values ?? {}; }, error: stream.error, isLoading: stream.isLoading, stop, submit, get interrupts() { if (stream.values != null && "__interrupt__" in stream.values && Array.isArray(stream.values.__interrupt__)) return userFacingInterruptsFromValuesArray(stream.values.__interrupt__); return []; }, get interrupt() { const all = this.interrupts; if (all.length === 0) return void 0; if (all.length === 1) return all[0]; return all; }, get messages() { if (!stream.values) return []; return getMessages(stream.values); }, get toolCalls() { if (!stream.values) return []; return getToolCallsWithResults(getMessages(stream.values)); }, getToolCalls(message) { if (!stream.values) return []; return getToolCallsWithResults(getMessages(stream.values)).filter((tc) => tc.aiMessage.id === message.id); }, get subagents() { return stream.getSubagents(); }, get activeSubagents() { return stream.getActiveSubagents(); }, getSubagent(toolCallId) { return stream.getSubagent(toolCallId); }, getSubagentsByType(type) { return stream.getSubagentsByType(type); }, getSubagentsByMessage(messageId) { return stream.getSubagentsByMessage(messageId); } }; } //#endregion export { FetchStreamTransport, useStreamCustom }; //# sourceMappingURL=stream.custom.js.map