UNPKG

@langchain/langgraph-sdk

Version:

Client library for interacting with the LangGraph API

609 lines (608 loc) • 21.2 kB
"use client"; require("../_virtual/_rolldown/runtime.cjs"); require("../stream/constants.cjs"); const require_messages = require("../ui/messages.cjs"); const require_headless_tools = require("../headless-tools.cjs"); const require_index = require("../client/index.cjs"); require("../client.cjs"); const require_interrupts = require("../ui/interrupts.cjs"); const require_errors = require("../ui/errors.cjs"); const require_tools = require("../utils/tools.cjs"); const require_manager = require("../ui/manager.cjs"); const require_utils = require("../ui/utils.cjs"); const require_branching = require("../ui/branching.cjs"); const require_thread = require("./thread.cjs"); let react = require("react"); //#region src/react/stream.lgp.tsx function getFetchHistoryKey(client, threadId, limit) { return [ require_index.getClientConfigHash(client), threadId, limit ].join(":"); } function fetchHistory(client, threadId, options) { if (options?.limit === false) return client.threads.getState(threadId).then((state) => { if (state.checkpoint == null) return []; return [state]; }); const limit = typeof options?.limit === "number" ? options.limit : 10; return client.threads.getHistory(threadId, { limit }); } function useThreadHistory(client, threadId, limit, options) { const key = getFetchHistoryKey(client, threadId, limit); const [state, setState] = (0, react.useState)(() => ({ key: void 0, data: void 0, error: void 0, isLoading: threadId != null })); const clientRef = (0, react.useRef)(client); clientRef.current = client; const onErrorRef = (0, react.useRef)(options?.onError); onErrorRef.current = options?.onError; const fetcher = (0, react.useCallback)((threadId, limit) => { if (options.passthrough) return Promise.resolve([]); const client = clientRef.current; const key = getFetchHistoryKey(client, threadId, limit); if (threadId != null) { setState((state) => { if (state.key === key) return { ...state, isLoading: true }; return { key, data: void 0, error: void 0, isLoading: true }; }); return fetchHistory(client, threadId, { limit }).then((data) => { setState((state) => { if (state.key !== key) return state; return { key, data, error: void 0, isLoading: false }; }); return data; }, (error) => { setState((state) => { if (state.key !== key) return state; return { key, data: state.data, error, isLoading: false }; }); onErrorRef.current?.(error); return Promise.reject(error); }); } setState({ key, data: void 0, error: void 0, isLoading: false }); return Promise.resolve([]); }, [options.passthrough]); (0, react.useEffect)(() => { if (options.submittingRef.current != null && options.submittingRef.current === threadId) return; fetcher(threadId, limit); }, [fetcher, key]); return { data: state.data, error: state.error, isLoading: state.isLoading, mutate: (mutateId) => fetcher(mutateId ?? threadId, limit) }; } function useStreamLGP(options) { const reconnectOnMountRef = (0, react.useRef)(options.reconnectOnMount); const runMetadataStorage = (0, react.useMemo)(() => { if (typeof window === "undefined") return null; const storage = reconnectOnMountRef.current; if (storage === true) return window.sessionStorage; if (typeof storage === "function") return storage(); return null; }, []); const client = (0, react.useMemo)(() => options.client ?? new require_index.Client({ apiUrl: options.apiUrl, apiKey: options.apiKey, callerOptions: options.callerOptions, defaultHeaders: options.defaultHeaders }), [ options.client, options.apiKey, options.apiUrl, options.callerOptions, options.defaultHeaders ]); const [messageManager] = (0, react.useState)(() => new require_messages.MessageTupleManager()); const [stream] = (0, react.useState)(() => new require_manager.StreamManager(messageManager, { throttle: options.throttle ?? false, subagentToolNames: options.subagentToolNames, filterSubagentMessages: options.filterSubagentMessages })); (0, react.useSyncExternalStore)(stream.subscribe, stream.getSnapshot, stream.getSnapshot); const [threadId, onThreadId] = require_thread.useControllableThreadId(options); const trackStreamModeRef = (0, react.useRef)([]); const trackStreamMode = (0, react.useCallback)((...mode) => { const ref = trackStreamModeRef.current; for (const m of mode) if (!ref.includes(m)) ref.push(m); }, []); const hasUpdateListener = options.onUpdateEvent != null; const hasCustomListener = options.onCustomEvent != null; const hasLangChainListener = options.onLangChainEvent != null; const hasDebugListener = options.onDebugEvent != null; const hasCheckpointListener = options.onCheckpointEvent != null; const hasTaskListener = options.onTaskEvent != null; const hasToolListener = options.onToolEvent != null; const callbackStreamMode = (0, react.useMemo)(() => { const modes = []; if (hasUpdateListener) modes.push("updates"); if (hasCustomListener) modes.push("custom"); if (hasLangChainListener) modes.push("events"); if (hasDebugListener) modes.push("debug"); if (hasCheckpointListener) modes.push("checkpoints"); if (hasTaskListener) modes.push("tasks"); if (hasToolListener) modes.push("tools"); return modes; }, [ hasUpdateListener, hasCustomListener, hasLangChainListener, hasDebugListener, hasCheckpointListener, hasTaskListener, hasToolListener ]); const threadIdRef = (0, react.useRef)(threadId); const threadIdStreamingRef = (0, react.useRef)(null); (0, react.useEffect)(() => { if (threadIdRef.current !== threadId) { threadIdRef.current = threadId; stream.clear(); } }, [threadId, stream]); const switchThread = (0, react.useCallback)((newThreadId) => { if (newThreadId !== threadIdRef.current) { threadIdRef.current = newThreadId; stream.clear(); onThreadId(newThreadId); } }, [stream, onThreadId]); const historyLimit = typeof options.fetchStateHistory === "object" && options.fetchStateHistory != null ? options.fetchStateHistory.limit ?? false : options.fetchStateHistory ?? false; const builtInHistory = useThreadHistory(client, threadId, historyLimit, { passthrough: options.thread != null, submittingRef: threadIdStreamingRef, onError: options.onError }); const history = options.thread ?? builtInHistory; 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 [branch, setBranch] = (0, react.useState)(""); const branchContext = require_branching.getBranchContext(branch, history.data ?? void 0); const [toolProgressMap, setToolProgressMap] = (0, react.useState)(/* @__PURE__ */ new Map()); const handleToolEvent = (0, react.useCallback)((data) => { setToolProgressMap((prev) => { const next = new Map(prev); const key = data.toolCallId ?? data.name; const existing = next.get(key); switch (data.event) { case "on_tool_start": next.set(key, { toolCallId: data.toolCallId, name: data.name, state: "starting", input: data.input }); break; case "on_tool_event": if (existing) next.set(key, { ...existing, state: "running", data: data.data }); break; case "on_tool_end": if (existing) next.set(key, { ...existing, state: "completed", result: data.output }); break; case "on_tool_error": if (existing) next.set(key, { ...existing, state: "error", error: data.error }); break; default: throw new Error(`Unexpected tool event: ${data.event}`); } return next; }); }, []); const historyValues = branchContext.threadHead?.values ?? options.initialValues ?? {}; const historyMessages = getMessages(historyValues); const shouldReconstructSubagents = options.filterSubagentMessages && !stream.isLoading && !history.isLoading && historyMessages.length > 0; (0, react.useEffect)(() => { if (shouldReconstructSubagents) { stream.reconstructSubagents(historyMessages, { skipIfPopulated: true }); if (historyLimit !== false && threadId) { const controller = new AbortController(); stream.fetchSubagentHistory(client.threads, threadId, { messagesKey: options.messagesKey ?? "messages", historyLimit: typeof historyLimit === "number" ? historyLimit : void 0, signal: controller.signal }); return () => controller.abort(); } } }, [shouldReconstructSubagents, historyMessages.length]); const historyError = (() => { const error = branchContext.threadHead?.tasks?.at(-1)?.error; if (error == null) return void 0; try { const parsed = JSON.parse(error); if (require_errors.StreamError.isStructuredError(parsed)) return new require_errors.StreamError(parsed); return parsed; } catch {} return error; })(); const messageMetadata = (() => { const alreadyShown = /* @__PURE__ */ new Set(); return getMessages(historyValues).map((message, idx) => { const messageId = message.id ?? idx; const firstSeenState = require_utils.findLast(history.data ?? [], (state) => state.values != null && getMessages(state.values).map((m, idx) => m.id ?? idx).includes(messageId)); const checkpointId = firstSeenState?.checkpoint?.checkpoint_id; let branch = checkpointId != null ? branchContext.branchByCheckpoint[checkpointId] : void 0; if (!branch?.branch?.length) branch = void 0; const optionsShown = branch?.branchOptions?.flat(2).join(","); if (optionsShown) { if (alreadyShown.has(optionsShown)) branch = void 0; alreadyShown.add(optionsShown); } return { messageId: messageId.toString(), firstSeenState, branch: branch?.branch, branchOptions: branch?.branchOptions }; }); })(); const stop = async () => { const wasLoading = stream.isLoading; const shouldRefetch = historyLimit === true || typeof historyLimit === "number" || require_utils.onFinishRequiresThreadState(options.onFinish); await stream.stop(historyValues, { onStop: (args) => { if (runMetadataStorage && threadId) { const runId = runMetadataStorage.getItem(`lg:stream:${threadId}`); if (runId) client.runs.cancel(threadId, runId); runMetadataStorage.removeItem(`lg:stream:${threadId}`); } options.onStop?.(args); } }); if (wasLoading && threadId) if (shouldRefetch) { const newHistory = await history.mutate(threadId); stream.setStreamValues(newHistory?.at(0)?.values ?? null); } else stream.setStreamValues(historyValues); }; const submit = async (values, submitOptions) => { setToolProgressMap(/* @__PURE__ */ new Map()); const checkpointId = submitOptions?.checkpoint?.checkpoint_id; setBranch(checkpointId != null ? branchContext.branchByCheckpoint[checkpointId]?.branch ?? "" : ""); const includeImplicitBranch = historyLimit === true || typeof historyLimit === "number"; const shouldRefetch = includeImplicitBranch || require_utils.onFinishRequiresThreadState(options.onFinish); let callbackMeta; let rejoinKey; let usableThreadId = threadId; const shouldAbortPrevious = (submitOptions?.multitaskStrategy === "interrupt" || submitOptions?.multitaskStrategy === "rollback") && stream.isLoading; await stream.start(async (signal) => { stream.setStreamValues((values) => { const prev = { ...historyValues, ...values }; if (submitOptions?.optimisticValues != null) return { ...prev, ...typeof submitOptions.optimisticValues === "function" ? submitOptions.optimisticValues(prev) : submitOptions.optimisticValues }; return { ...prev }; }); if (!usableThreadId) { usableThreadId = (await client.threads.create({ threadId: submitOptions?.threadId, metadata: submitOptions?.metadata, signal })).thread_id; threadIdRef.current = usableThreadId; threadIdStreamingRef.current = usableThreadId; onThreadId(usableThreadId); } if (!usableThreadId) throw new Error("Failed to obtain valid thread ID."); threadIdStreamingRef.current = usableThreadId; const streamMode = require_utils.unique([ ...submitOptions?.streamMode ?? [], ...trackStreamModeRef.current, ...callbackStreamMode ]); let checkpoint = submitOptions?.checkpoint ?? (includeImplicitBranch ? branchContext.threadHead?.checkpoint : void 0) ?? void 0; if (submitOptions?.checkpoint === null) checkpoint = void 0; if (checkpoint != null) delete checkpoint.thread_id; const streamResumable = submitOptions?.streamResumable ?? !!runMetadataStorage; return client.runs.stream(usableThreadId, options.assistantId, { input: values, config: submitOptions?.config, context: submitOptions?.context, command: submitOptions?.command, interruptBefore: submitOptions?.interruptBefore, interruptAfter: submitOptions?.interruptAfter, metadata: submitOptions?.metadata, multitaskStrategy: submitOptions?.multitaskStrategy, onCompletion: submitOptions?.onCompletion, onDisconnect: submitOptions?.onDisconnect ?? (streamResumable ? "continue" : "cancel"), signal, checkpoint, streamMode, streamSubgraphs: submitOptions?.streamSubgraphs, streamResumable, durability: submitOptions?.durability, onRunCreated(params) { callbackMeta = { run_id: params.run_id, thread_id: params.thread_id ?? usableThreadId }; if (runMetadataStorage) { rejoinKey = `lg:stream:${usableThreadId}`; runMetadataStorage.setItem(rejoinKey, callbackMeta.run_id); } options.onCreated?.(callbackMeta); } }); }, { getMessages, setMessages, initialValues: historyValues, callbacks: { ...options, onToolEvent: (data, opts) => { handleToolEvent(data); options.onToolEvent?.(data, opts); } }, async onSuccess() { if (rejoinKey) runMetadataStorage?.removeItem(rejoinKey); if (shouldRefetch) { const lastHead = (await history.mutate(usableThreadId))?.at(0); if (lastHead) { options.onFinish?.(lastHead, callbackMeta); return null; } } else if (options.onFinish != null && !require_utils.onFinishRequiresThreadState(options.onFinish)) options.onFinish(void 0, callbackMeta); }, onError(error) { options.onError?.(error, callbackMeta); }, onFinish() { threadIdStreamingRef.current = null; } }, { abortPrevious: shouldAbortPrevious }); }; const joinStream = async (runId, lastEventId, joinOptions) => { setToolProgressMap(/* @__PURE__ */ new Map()); lastEventId ??= "-1"; if (!threadId) return; const callbackMeta = { thread_id: threadId, run_id: runId }; const shouldRefetchJoin = historyLimit === true || typeof historyLimit === "number" || require_utils.onFinishRequiresThreadState(options.onFinish); await stream.start(async (signal) => { threadIdStreamingRef.current = threadId; const stream = client.runs.joinStream(threadId, runId, { signal, lastEventId, streamMode: joinOptions?.streamMode }); return joinOptions?.filter != null ? require_utils.filterStream(stream, joinOptions.filter) : stream; }, { getMessages, setMessages, initialValues: historyValues, callbacks: { ...options, onToolEvent: (data, opts) => { handleToolEvent(data); options.onToolEvent?.(data, opts); } }, async onSuccess() { runMetadataStorage?.removeItem(`lg:stream:${threadId}`); if (!shouldRefetchJoin) { if (options.onFinish != null && !require_utils.onFinishRequiresThreadState(options.onFinish)) options.onFinish(void 0, callbackMeta); return; } const lastHead = (await history.mutate(threadId))?.at(0); if (lastHead) options.onFinish?.(lastHead, callbackMeta); }, onError(error) { options.onError?.(error, callbackMeta); }, onFinish() { threadIdStreamingRef.current = null; } }); }; const reconnectKey = (0, react.useMemo)(() => { if (!runMetadataStorage || stream.isLoading) return void 0; if (typeof window === "undefined") return void 0; const runId = runMetadataStorage?.getItem(`lg:stream:${threadId}`); if (!runId) return void 0; return { runId, threadId }; }, [ runMetadataStorage, stream.isLoading, threadId ]); const shouldReconnect = !!runMetadataStorage; const reconnectRef = (0, react.useRef)({ threadId, shouldReconnect }); const joinStreamRef = (0, react.useRef)(joinStream); joinStreamRef.current = joinStream; (0, react.useEffect)(() => { if (reconnectRef.current.threadId !== threadId) reconnectRef.current = { threadId, shouldReconnect }; }, [threadId, shouldReconnect]); (0, react.useEffect)(() => { if (reconnectKey && reconnectRef.current.shouldReconnect) { reconnectRef.current.shouldReconnect = false; joinStreamRef.current?.(reconnectKey.runId); } }, [reconnectKey]); const error = stream.error ?? historyError ?? history.error; const values = stream.values ?? historyValues; const handledToolsRef = (0, react.useRef)(/* @__PURE__ */ new Set()); (0, react.useEffect)(() => { handledToolsRef.current.clear(); }, [threadId]); (0, react.useEffect)(() => { require_headless_tools.flushPendingHeadlessToolInterrupts(values, options.tools, handledToolsRef.current, { onTool: options.onTool, defer: (run) => { Promise.resolve().then(run); }, resumeSubmit: (command) => submit(null, { multitaskStrategy: "interrupt", command }) }); }, [ options.onTool, options.tools, submit, values ]); const streamHandle = { get values() { trackStreamMode("values"); return values; }, client, assistantId: options.assistantId, error, isLoading: stream.isLoading, stop, submit, switchThread, joinStream, branch, setBranch, get history() { if (historyLimit === false) throw new Error("`fetchStateHistory` must be set to `true` to use `history`"); return branchContext.flatHistory; }, isThreadLoading: history.isLoading && history.data == null, get experimental_branchTree() { if (historyLimit === false) throw new Error("`fetchStateHistory` must be set to `true` to use `experimental_branchTree`"); return branchContext.branchTree; }, get interrupts() { if (values != null && "__interrupt__" in values && Array.isArray(values.__interrupt__)) return require_interrupts.userFacingInterruptsFromValuesArray(values.__interrupt__); if (stream.isLoading) return []; const taskInterrupts = require_interrupts.userFacingInterruptsFromThreadTasks((branchContext.threadHead?.tasks ?? []).flatMap((t) => t.interrupts ?? [])); if (taskInterrupts != null) return taskInterrupts; if (!(branchContext.threadHead?.next ?? []).length || error != null) return []; return [{ when: "breakpoint" }]; }, get interrupt() { const all = this.interrupts; if (all.length === 0) return void 0; if (all.length === 1) return all[0]; return all; }, get messages() { trackStreamMode("messages-tuple", "values"); return getMessages(values); }, get toolCalls() { trackStreamMode("messages-tuple", "values"); return require_tools.getToolCallsWithResults(getMessages(values)); }, get toolProgress() { trackStreamMode("tools"); return Array.from(toolProgressMap.values()); }, getToolCalls(message) { trackStreamMode("messages-tuple", "values"); return require_tools.getToolCallsWithResults(getMessages(values)).filter((tc) => tc.aiMessage.id === message.id); }, getMessagesMetadata(message, index) { trackStreamMode("values"); const streamMetadata = messageManager.get(message.id)?.metadata; const historyMetadata = messageMetadata?.find((m) => m.messageId === (message.id ?? index)); if (streamMetadata != null || historyMetadata != null) return { ...historyMetadata, streamMetadata }; }, get subagents() { trackStreamMode("updates", "messages-tuple"); return stream.getSubagents(); }, get activeSubagents() { trackStreamMode("updates", "messages-tuple"); return stream.getActiveSubagents(); }, getSubagent(toolCallId) { trackStreamMode("updates", "messages-tuple"); return stream.getSubagent(toolCallId); }, getSubagentsByType(type) { trackStreamMode("updates", "messages-tuple"); return stream.getSubagentsByType(type); }, getSubagentsByMessage(messageId) { trackStreamMode("updates", "messages-tuple"); return stream.getSubagentsByMessage(messageId); } }; for (const key of [ "history", "experimental_branchTree", "toolProgress", "subagents", "activeSubagents" ]) { const descriptor = Object.getOwnPropertyDescriptor(streamHandle, key); if (descriptor?.get) Object.defineProperty(streamHandle, key, { ...descriptor, enumerable: false }); } return streamHandle; } //#endregion exports.useStreamLGP = useStreamLGP; //# sourceMappingURL=stream.lgp.cjs.map