@assistant-ui/react-langchain 0.0.35 → 0.0.36
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/LangChainThreadController.d.ts +9 -0
- package/dist/LangChainThreadController.d.ts.map +1 -0
- package/dist/LangChainThreadController.js +21 -0
- package/dist/LangChainThreadController.js.map +1 -0
- package/dist/convertMessages.d.ts.map +1 -1
- package/dist/convertMessages.js +6 -11
- package/dist/convertMessages.js.map +1 -1
- package/dist/converter.d.ts +24 -1
- package/dist/converter.d.ts.map +1 -1
- package/dist/converter.js +84 -18
- package/dist/converter.js.map +1 -1
- package/dist/langChainThreadState.d.ts +54 -0
- package/dist/langChainThreadState.d.ts.map +1 -0
- package/dist/langChainThreadState.js +135 -0
- package/dist/langChainThreadState.js.map +1 -0
- package/dist/sdkIdentity.js +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/useStreamRuntime.d.ts +3 -2
- package/dist/useStreamRuntime.d.ts.map +1 -1
- package/dist/useStreamRuntime.js +152 -139
- package/dist/useStreamRuntime.js.map +1 -1
- package/dist/useSubagentTranscripts.js +1 -1
- package/package.json +8 -8
- package/src/LangChainThreadController.ts +24 -0
- package/src/convertMessages.test.ts +23 -0
- package/src/convertMessages.ts +9 -16
- package/src/converter.test.ts +322 -0
- package/src/converter.ts +138 -27
- package/src/getPendingToolCalls.test.ts +35 -0
- package/src/langChainThreadState.test.ts +203 -0
- package/src/langChainThreadState.ts +239 -0
- package/src/types.ts +9 -0
- package/src/useStreamRuntime.cloud-options.test.tsx +61 -0
- package/src/useStreamRuntime.test.tsx +227 -1
- package/src/useStreamRuntime.ts +210 -239
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStreamRuntime.js","names":[],"sources":["../src/useStreamRuntime.ts"],"sourcesContent":["/// <reference types=\"@assistant-ui/core/store\" preserve=\"true\" />\n\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useInsertionEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { AppendMessage, ToolExecutionStatus } from \"@assistant-ui/core\";\nimport {\n generateId,\n getExternalStoreMessages,\n pickExternalStoreSharedOptions,\n} from \"@assistant-ui/core\";\nimport type { ThreadMessage } from \"@assistant-ui/core\";\nimport {\n createCloudThreadListAdapterCreateFallback,\n createToolCallCancellationStub,\n getThreadMessageText,\n scanPendingToolCalls,\n} from \"@assistant-ui/core/internal\";\nimport {\n useCloudThreadListAdapter,\n useExternalStoreRuntime,\n useExternalMessageConverter,\n useRemoteThreadListRuntime,\n} from \"@assistant-ui/core/react\";\nimport { useAui, useAuiState } from \"@assistant-ui/store\";\nimport { STREAM_CONTROLLER, useChannel, useStream } from \"@langchain/react\";\nimport type {\n LangChainBaseMessage,\n LangChainToolCall,\n UIMessage,\n UseStreamRuntimeOptions,\n} from \"./types\";\nimport { getMessageModality, groupUIMessagesByParent } from \"./converter\";\nexport { groupUIMessagesByParent } from \"./converter\";\nimport {\n createLangChainMetadataKey,\n convertLangChainBaseMessage,\n getMessageContent,\n getMessageType,\n type LangChainMessageConverterMetadata,\n} from \"./convertMessages\";\nimport {\n attachSubagentTranscripts,\n createAttachMemo,\n} from \"./attachSubagentTranscripts\";\nimport { useSubagentTranscripts } from \"./useSubagentTranscripts\";\nimport {\n createUIFoldMemo,\n createUISnapshotMemo,\n foldUIUpdates,\n mergeUIMessages,\n reconcileUISnapshot,\n UI_CUSTOM_CHANNELS,\n} from \"./uiMessages\";\nimport { langChainExtras } from \"./runtimeExtras\";\nimport { resolveForkCheckpoint } from \"./resolveForkCheckpoint\";\nimport { useLangChainStreamingTiming } from \"./streamingTiming\";\nimport { LANGCHAIN_SDK } from \"./sdkIdentity\";\n\nexport const runConfigToSubmitOptions = (\n runConfig: AppendMessage[\"runConfig\"],\n) =>\n runConfig?.custom\n ? { config: { configurable: runConfig.custom } }\n : undefined;\n\ntype NormalizedRunConfigOptions = NonNullable<\n ReturnType<typeof runConfigToSubmitOptions>\n>;\n\nconst getPendingToolCalls = (\n messages: readonly LangChainBaseMessage[],\n): LangChainToolCall[] =>\n scanPendingToolCalls(\n messages,\n (message) => {\n const type = getMessageType(message);\n if (type === \"ai\") return { toolCalls: message.tool_calls ?? [] };\n if (type === \"tool\" && message.tool_call_id) {\n return { toolCallId: message.tool_call_id };\n }\n return undefined;\n },\n (toolCall) => toolCall.id,\n );\n\nconst toStagedHumanMessage = (\n msg: AppendMessage,\n id = generateId(),\n): LangChainBaseMessage & { id: string } => ({\n id,\n _getType: () => \"human\",\n content: getMessageContent(msg),\n});\n\nconst toStagedMessageInput = (message: LangChainBaseMessage) => ({\n id: message.id,\n type: getMessageType(message) === \"ai\" ? (\"ai\" as const) : (\"human\" as const),\n content: message.content,\n ...(message.additional_kwargs && {\n additional_kwargs: message.additional_kwargs,\n }),\n});\n\nconst humanContentText = (content: LangChainBaseMessage[\"content\"]) => {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n return content\n .filter(\n (part): part is { type: \"text\"; text: string } =>\n typeof part === \"object\" &&\n part !== null &&\n part.type === \"text\" &&\n typeof part.text === \"string\",\n )\n .map((part) => part.text)\n .join(\"\");\n};\n\nconst hasSameMessageContent = (\n a: LangChainBaseMessage,\n b: LangChainBaseMessage,\n) => humanContentText(a.content) === humanContentText(b.content);\n\nconst truncateLangChainBaseMessages = (\n threadMessages: readonly ThreadMessage[],\n parentId: string | null,\n): LangChainBaseMessage[] => {\n if (parentId === null) return [];\n const parentIndex = threadMessages.findIndex((m) => m.id === parentId);\n if (parentIndex === -1) return [];\n const truncated: LangChainBaseMessage[] = [];\n for (let i = 0; i <= parentIndex && i < threadMessages.length; i++) {\n truncated.push(\n ...getExternalStoreMessages<LangChainBaseMessage>(threadMessages[i]!),\n );\n }\n return truncated;\n};\n\ntype DistributiveOmit<T, K extends keyof any> = T extends unknown\n ? Omit<T, K>\n : never;\n\nconst useStreamThreadRuntime = (\n options: DistributiveOmit<\n UseStreamRuntimeOptions,\n \"cloud\" | \"unstable_threadListAdapter\" | \"create\" | \"delete\"\n >,\n) => {\n const { adapters, autoCancelPendingToolCalls, unstable_allowCancellation } =\n options;\n const aui = useAui();\n const messagesKey = options.messagesKey ?? \"messages\";\n const uiStateKey = options.uiStateKey ?? \"ui\";\n\n const externalId = useAuiState((s) => s.threadListItem.externalId) as\n | string\n | null;\n // Object.assign preserves the discriminated transport union; object spread\n // collapses its arms and no longer satisfies UseStreamOptions.\n const stream = useStream(\n Object.assign({}, options, { threadId: externalId }),\n );\n const [stagedMessages, setStagedMessages] = useState<\n LangChainBaseMessage[] | null\n >(null);\n\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n const hasExecutingTools = Object.values(toolStatuses).some(\n (s) => s?.type === \"executing\",\n );\n const effectiveIsRunning = stream.isLoading || hasExecutingTools;\n\n const [uiSnapshotMemo] = useState(createUISnapshotMemo);\n const uiStateValue = reconcileUISnapshot(\n stream.values[uiStateKey],\n uiSnapshotMemo,\n );\n\n const customEvents = useChannel(stream, UI_CUSTOM_CHANNELS);\n const [uiFoldMemo] = useState(createUIFoldMemo);\n const liveUiMessages = useMemo(\n () => foldUIUpdates(customEvents, uiFoldMemo),\n [customEvents, uiFoldMemo],\n );\n\n const mergedUiMessages = useMemo(\n () => mergeUIMessages(liveUiMessages, uiStateValue),\n [liveUiMessages, uiStateValue],\n );\n\n const uiMessagesByParent = useMemo(\n () => groupUIMessagesByParent<UIMessage>(mergedUiMessages),\n [mergedUiMessages],\n );\n\n const visibleMessages =\n stagedMessages ?? (stream.messages as LangChainBaseMessage[]);\n\n const messageTiming = useLangChainStreamingTiming(\n visibleMessages,\n effectiveIsRunning,\n );\n\n const subagentTranscripts = useSubagentTranscripts(\n stream,\n uiMessagesByParent,\n );\n\n const [getConverterMetadataKey] = useState(createLangChainMetadataKey);\n const converterMetadata = useMemo<LangChainMessageConverterMetadata>(\n () => ({\n uiMessagesByParent: uiMessagesByParent,\n messageTiming,\n }),\n [uiMessagesByParent, messageTiming],\n );\n\n const threadMessages = useExternalMessageConverter({\n callback: convertLangChainBaseMessage,\n messages: visibleMessages,\n isRunning: effectiveIsRunning,\n metadata: converterMetadata,\n getMetadataKey: getConverterMetadataKey,\n });\n const [memo] = useState(createAttachMemo);\n const messagesWithTranscripts = useMemo(\n () => attachSubagentTranscripts(threadMessages, subagentTranscripts, memo),\n [threadMessages, subagentTranscripts, memo],\n );\n\n const streamRef = useRef(stream);\n useInsertionEffect(() => {\n streamRef.current = stream;\n }, [stream]);\n\n const activeRunConfigRef = useRef<\n NormalizedRunConfigOptions[\"config\"] | undefined\n >(undefined);\n const runConfigByMessageIdRef = useRef(\n new Map<string, NormalizedRunConfigOptions[\"config\"] | undefined>(),\n );\n const activeThreadIdRef = useRef(externalId);\n const setActiveRunConfig = useCallback(\n (runConfig: AppendMessage[\"runConfig\"]) => {\n activeRunConfigRef.current = runConfigToSubmitOptions(runConfig)?.config;\n },\n [],\n );\n const withActiveRunConfig = useCallback(\n (submitOptions?: Record<string, unknown>) => {\n if (submitOptions && \"config\" in submitOptions) return submitOptions;\n if (activeRunConfigRef.current === undefined) return submitOptions;\n return { ...submitOptions, config: activeRunConfigRef.current };\n },\n [],\n );\n\n useEffect(() => {\n if (\n activeThreadIdRef.current !== null &&\n activeThreadIdRef.current !== externalId\n ) {\n activeRunConfigRef.current = undefined;\n runConfigByMessageIdRef.current.clear();\n }\n activeThreadIdRef.current = externalId;\n }, [externalId]);\n\n useEffect(() => {\n const messages = stream.messages as readonly LangChainBaseMessage[];\n const owned = runConfigByMessageIdRef.current;\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages.at(i);\n if (\n !message?.id ||\n getMessageType(message) !== \"ai\" ||\n !message.tool_calls?.length\n ) {\n continue;\n }\n if (owned.has(message.id)) return;\n break;\n }\n for (const message of messages) {\n if (\n !message.id ||\n getMessageType(message) !== \"ai\" ||\n !message.tool_calls?.length ||\n owned.has(message.id)\n ) {\n continue;\n }\n owned.set(message.id, activeRunConfigRef.current);\n }\n }, [stream.messages]);\n\n const visibleMessagesRef = useRef(visibleMessages);\n useInsertionEffect(() => {\n visibleMessagesRef.current = visibleMessages;\n }, [visibleMessages]);\n\n const threadMessagesRef = useRef(messagesWithTranscripts);\n useInsertionEffect(() => {\n threadMessagesRef.current = messagesWithTranscripts;\n }, [messagesWithTranscripts]);\n\n const stagedMessagesRef = useRef(\n new Map<\n string,\n {\n message: LangChainBaseMessage & { id: string };\n runConfig: AppendMessage[\"runConfig\"];\n reconcileOnEcho: boolean;\n baseMessageCount: number;\n transcriptStatus?: \"unsent\" | \"sent\";\n }\n >(),\n );\n const stagedBaseMessagesRef = useRef<LangChainBaseMessage[] | null>(null);\n useEffect(() => {\n if (stagedMessagesRef.current.size === 0) return;\n\n // Staged edits must keep their truncated base while stream updates arrive before promotion.\n const baseMessages =\n stagedBaseMessagesRef.current ??\n (stream.messages as LangChainBaseMessage[]);\n const remainingStagedMessages: LangChainBaseMessage[] = [];\n const matchedBaseMessageIndexes = new Set<number>();\n const visibleStagedIds = new Set(\n visibleMessagesRef.current.flatMap((m) => (m.id ? [m.id] : [])),\n );\n for (const [id, staged] of stagedMessagesRef.current) {\n if (!visibleStagedIds.has(id)) continue;\n const echoed = baseMessages.some((message, index) => {\n if (matchedBaseMessageIndexes.has(index)) return false;\n if (message.id === id) {\n matchedBaseMessageIndexes.add(index);\n return true;\n }\n if (\n !staged.reconcileOnEcho ||\n index < staged.baseMessageCount ||\n getMessageType(message) !== \"human\" ||\n !hasSameMessageContent(message, staged.message)\n ) {\n return false;\n }\n matchedBaseMessageIndexes.add(index);\n return true;\n });\n if (echoed) stagedMessagesRef.current.delete(id);\n else remainingStagedMessages.push(staged.message);\n }\n\n if (remainingStagedMessages.length === 0) {\n stagedBaseMessagesRef.current = null;\n visibleMessagesRef.current = baseMessages;\n // Reconciling against the upstream stream mutates the staged refs above,\n // which cannot happen during render.\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setStagedMessages(null);\n return;\n }\n\n const nextMessages = [...baseMessages, ...remainingStagedMessages];\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n }, [stream.messages]);\n\n const getStagedRun = (parentId: string | null) => {\n const parent = parentId\n ? stagedMessagesRef.current.get(parentId)\n : undefined;\n if (!parent || parent.transcriptStatus === \"sent\") return null;\n\n const staged: LangChainBaseMessage[] = [];\n for (const message of visibleMessagesRef.current) {\n const entry = message.id\n ? stagedMessagesRef.current.get(message.id)\n : undefined;\n if (entry && entry.transcriptStatus !== \"sent\") {\n staged.push(entry.message);\n }\n if (message.id === parentId) break;\n }\n\n return {\n messages: staged,\n runConfig: parent.runConfig,\n };\n };\n\n const appendVoiceTranscript = (message: ThreadMessage) => {\n const transcript = {\n id: message.id,\n _getType: () => (message.role === \"assistant\" ? \"ai\" : \"human\"),\n content: getThreadMessageText(message),\n ...(message.metadata.modality && {\n additional_kwargs: { modality: message.metadata.modality },\n }),\n };\n stagedMessagesRef.current.set(transcript.id, {\n message: transcript,\n runConfig: undefined,\n reconcileOnEcho: false,\n baseMessageCount: streamRef.current.messages.length,\n transcriptStatus: \"unsent\",\n });\n const nextMessages = [...visibleMessagesRef.current, transcript];\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n };\n\n const getUnsentTranscripts = () =>\n visibleMessagesRef.current.filter(\n (message) =>\n message.id !== undefined &&\n stagedMessagesRef.current.get(message.id)?.transcriptStatus ===\n \"unsent\",\n );\n\n const setTranscriptStatus = (\n messages: readonly LangChainBaseMessage[],\n status: \"unsent\" | \"sent\",\n ) => {\n for (const message of messages) {\n const staged = message.id\n ? stagedMessagesRef.current.get(message.id)\n : undefined;\n if (staged?.transcriptStatus) staged.transcriptStatus = status;\n }\n };\n\n // Reserving before the submit keeps an overlapping submit from carrying the\n // same transcript; a failed submit hands it back to the next run.\n const submitCarryingTranscripts = async (\n transcripts: readonly LangChainBaseMessage[],\n submit: () => Promise<void>,\n ) => {\n setTranscriptStatus(transcripts, \"sent\");\n try {\n await submit();\n } catch (error) {\n setTranscriptStatus(transcripts, \"unsent\");\n throw error;\n }\n };\n\n const dropTranscripts = (messages: readonly LangChainBaseMessage[]) => {\n for (const message of messages) {\n if (\n message.id &&\n stagedMessagesRef.current.get(message.id)?.transcriptStatus\n )\n removeStagedMessage(message.id);\n }\n };\n\n const isTranscriptMessage = (message: LangChainBaseMessage) => {\n const staged = message.id\n ? stagedMessagesRef.current.get(message.id)\n : undefined;\n if (staged?.transcriptStatus === \"unsent\") return true;\n const type = getMessageType(message);\n return (\n (type === \"human\" || type === \"ai\") &&\n getMessageModality(message.additional_kwargs) !== undefined\n );\n };\n\n // A transcript reaches the graph in the same input as the message after it, so\n // no checkpoint ends at one; a fork starts before the trailing transcripts.\n const planForkTranscripts = (parentId: string | null) => {\n const visible = visibleMessagesRef.current;\n const parentIndex =\n parentId == null ? -1 : visible.findIndex((m) => m.id === parentId);\n if (parentId != null && parentIndex === -1)\n return { forkParentId: parentId, transcripts: [], truncated: [] };\n\n let start = parentIndex + 1;\n while (start > 0 && isTranscriptMessage(visible[start - 1]!)) start--;\n return {\n forkParentId: start > 0 ? (visible[start - 1]!.id ?? null) : null,\n transcripts: visible.slice(start, parentIndex + 1),\n truncated: visible.slice(parentIndex + 1),\n };\n };\n\n const stageUserMessage = (msg: AppendMessage, reconcileOnEcho = false) => {\n const stagedMessage = toStagedHumanMessage(msg);\n stagedMessagesRef.current.set(stagedMessage.id, {\n message: stagedMessage,\n runConfig: msg.runConfig,\n reconcileOnEcho,\n baseMessageCount: streamRef.current.messages.length,\n });\n const nextMessages = [...visibleMessagesRef.current, stagedMessage];\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n return stagedMessage;\n };\n\n const removeStagedMessage = (id: string) => {\n if (!stagedMessagesRef.current.delete(id)) return;\n const nextMessages = visibleMessagesRef.current.filter(\n (message) => message.id !== id,\n );\n visibleMessagesRef.current = nextMessages;\n if (stagedMessagesRef.current.size === 0) {\n stagedBaseMessagesRef.current = null;\n setStagedMessages(null);\n } else {\n setStagedMessages(nextMessages);\n }\n };\n\n const extras = useMemo(\n () =>\n langChainExtras.provide({\n interrupt: stream.interrupt,\n interrupts: stream.interrupts,\n toolCalls: stream.toolCalls,\n subagents: stream.subagents,\n subgraphs: stream.subgraphs,\n stream,\n error: stream.error,\n submit: (values, submitOptions) => {\n const isResume = values == null || submitOptions?.command != null;\n return stream.submit(\n values,\n isResume ? withActiveRunConfig(submitOptions) : submitOptions,\n );\n },\n respond: (response, respondOptions) =>\n stream.respond(response, withActiveRunConfig(respondOptions)),\n respondAll: (responsesById, respondOptions) =>\n stream.respondAll(responsesById, withActiveRunConfig(respondOptions)),\n values: stream.values,\n messagesKey,\n }),\n [stream, messagesKey, withActiveRunConfig],\n );\n\n const runtime = useExternalStoreRuntime({\n ...pickExternalStoreSharedOptions(options),\n isRunning: stream.isLoading,\n isLoading: stream.isThreadLoading,\n messages: messagesWithTranscripts,\n adapters,\n extras,\n unstable_enableToolInvocations: true,\n setToolStatuses,\n onNew: async (msg) => {\n if (!(msg.startRun ?? msg.role === \"user\")) {\n stageUserMessage(msg);\n return;\n }\n\n const stagedMessage = stageUserMessage(msg, true);\n const stagedMessageId = stagedMessage.id;\n setActiveRunConfig(msg.runConfig);\n const content = getMessageContent(msg);\n const cancellations =\n autoCancelPendingToolCalls !== false\n ? getPendingToolCalls(\n streamRef.current.messages as readonly LangChainBaseMessage[],\n )\n // LangChain rejects a tool message without a tool_call_id.\n .filter((toolCall) => toolCall.id)\n .map(createToolCallCancellationStub)\n : [];\n // A null threadId is not a no-op for the SDK: it rebinds the controller\n // away from its self-created thread and forces a fresh one, so the\n // submit waits for initialization to produce an identity; core no\n // longer holds appends on that barrier.\n try {\n const { externalId } = await aui.threadListItem.initialize();\n const transcripts = getUnsentTranscripts();\n await submitCarryingTranscripts(transcripts, () =>\n streamRef.current.submit(\n {\n [messagesKey]: [\n ...cancellations,\n ...transcripts.map(toStagedMessageInput),\n {\n id: stagedMessageId,\n type: \"human\",\n content,\n },\n ],\n },\n {\n ...runConfigToSubmitOptions(msg.runConfig),\n ...(externalId != null ? { threadId: externalId } : {}),\n },\n ),\n );\n } catch (error) {\n removeStagedMessage(stagedMessageId);\n throw error;\n }\n },\n onVoiceTranscript: appendVoiceTranscript,\n onAddToolResult: async ({\n messageId,\n toolCallId,\n toolName,\n result,\n isError,\n artifact,\n }) => {\n const runConfig = runConfigByMessageIdRef.current.has(messageId)\n ? runConfigByMessageIdRef.current.get(messageId)\n : activeRunConfigRef.current;\n await stream.submit(\n {\n [messagesKey]: [\n {\n type: \"tool\",\n name: toolName,\n tool_call_id: toolCallId,\n content: JSON.stringify(result),\n ...(artifact !== undefined && { artifact }),\n status: isError ? \"error\" : \"success\",\n },\n ],\n },\n runConfig === undefined ? undefined : { config: runConfig },\n );\n },\n onReload: async (parentId, config) => {\n const stagedRun = getStagedRun(parentId);\n if (stagedRun) {\n if (\n config.sourceId &&\n stagedMessagesRef.current.get(config.sourceId)?.transcriptStatus\n )\n removeStagedMessage(config.sourceId);\n const promotedIds = new Set<string>();\n for (const message of stagedRun.messages) {\n if (\n !message.id ||\n stagedMessagesRef.current.get(message.id)?.transcriptStatus\n )\n continue;\n promotedIds.add(message.id);\n stagedMessagesRef.current.delete(message.id);\n }\n stagedBaseMessagesRef.current = null;\n if (stagedMessagesRef.current.size > 0) {\n const nextMessages = visibleMessagesRef.current.filter(\n (message) => !message.id || !promotedIds.has(message.id),\n );\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n } else {\n setStagedMessages(null);\n }\n const runConfig = config.runConfig ?? stagedRun.runConfig;\n setActiveRunConfig(runConfig);\n await submitCarryingTranscripts(stagedRun.messages, () =>\n stream.submit(\n {\n [messagesKey]: stagedRun.messages.map(toStagedMessageInput),\n },\n runConfigToSubmitOptions(runConfig),\n ),\n );\n return;\n }\n\n const threadId = externalId;\n if (!threadId || parentId == null) return;\n const s = streamRef.current;\n const fork = planForkTranscripts(parentId);\n const checkpointId = await resolveForkCheckpoint(\n s.client,\n threadId,\n s.messages as readonly LangChainBaseMessage[],\n fork.forkParentId,\n config.sourceId,\n s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),\n messagesKey,\n );\n if (!checkpointId) return;\n dropTranscripts(fork.truncated);\n setActiveRunConfig(config.runConfig);\n await submitCarryingTranscripts(fork.transcripts, () =>\n s.submit(\n fork.transcripts.length > 0\n ? { [messagesKey]: fork.transcripts.map(toStagedMessageInput) }\n : null,\n {\n forkFrom: checkpointId,\n ...runConfigToSubmitOptions(config.runConfig),\n },\n ),\n );\n },\n onEdit: async (message) => {\n if (!(message.startRun ?? message.role === \"user\")) {\n const truncated = truncateLangChainBaseMessages(\n threadMessagesRef.current,\n message.parentId,\n );\n const stagedMessage = toStagedHumanMessage(message);\n stagedMessagesRef.current.set(stagedMessage.id, {\n message: stagedMessage,\n runConfig: message.runConfig,\n reconcileOnEcho: false,\n baseMessageCount: 0,\n });\n stagedBaseMessagesRef.current = truncated;\n const nextMessages = [...truncated, stagedMessage];\n visibleMessagesRef.current = nextMessages;\n setStagedMessages(nextMessages);\n return;\n }\n\n const threadId = externalId;\n if (!threadId) return;\n const s = streamRef.current;\n const fork = planForkTranscripts(message.parentId);\n const checkpointId = await resolveForkCheckpoint(\n s.client,\n threadId,\n s.messages as readonly LangChainBaseMessage[],\n fork.forkParentId,\n message.sourceId,\n s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),\n messagesKey,\n );\n if (!checkpointId) return;\n dropTranscripts(fork.truncated);\n const content = getMessageContent(message);\n setActiveRunConfig(message.runConfig);\n await submitCarryingTranscripts(fork.transcripts, () =>\n s.submit(\n {\n [messagesKey]: [\n ...fork.transcripts.map(toStagedMessageInput),\n { type: \"human\", content },\n ],\n },\n {\n forkFrom: checkpointId,\n ...runConfigToSubmitOptions(message.runConfig),\n },\n ),\n );\n },\n onCancel:\n unstable_allowCancellation !== false\n ? async () => {\n activeRunConfigRef.current = undefined;\n await stream.stop();\n }\n : undefined,\n });\n\n return runtime;\n};\n\n/**\n * Creates an assistant-ui runtime backed by LangChain's `useStream` hook.\n * Accepts the same options as `useStream` from `@langchain/react`, plus\n * `cloud` and `adapters`.\n *\n * @example\n * ```tsx\n * import { useStreamRuntime } from \"@assistant-ui/react-langchain\";\n * import { AssistantRuntimeProvider, Thread } from \"@assistant-ui/react\";\n *\n * function App() {\n * const runtime = useStreamRuntime({\n * assistantId: \"agent\",\n * apiUrl: \"http://localhost:2024\",\n * });\n *\n * return (\n * <AssistantRuntimeProvider runtime={runtime}>\n * <Thread />\n * </AssistantRuntimeProvider>\n * );\n * }\n * ```\n */\nexport const useStreamRuntime = (rawOptions: UseStreamRuntimeOptions) => {\n const {\n cloud,\n unstable_threadListAdapter,\n create,\n delete: deleteFn,\n onThreadIdChange,\n ...options\n } = rawOptions;\n\n const aui = useAui();\n const cloudAdapter = useCloudThreadListAdapter({\n sdk: LANGCHAIN_SDK,\n cloud,\n create: createCloudThreadListAdapterCreateFallback(\n create,\n aui.threadListItem,\n ),\n delete: deleteFn,\n });\n const adapter = unstable_threadListAdapter ?? cloudAdapter;\n\n return useRemoteThreadListRuntime({\n runtimeHook: function RuntimeHook() {\n return useStreamThreadRuntime(options);\n },\n adapter,\n allowNesting: true,\n onThreadIdChange,\n });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useStreamRuntime.js","names":[],"sources":["../src/useStreamRuntime.ts"],"sourcesContent":["/// <reference types=\"@assistant-ui/core/store\" preserve=\"true\" />\n\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useInsertionEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport type { AppendMessage, ToolExecutionStatus } from \"@assistant-ui/core\";\nimport {\n generateId,\n getExternalStoreMessages,\n pickExternalStoreSharedOptions,\n} from \"@assistant-ui/core\";\nimport type { ThreadMessage } from \"@assistant-ui/core\";\nimport {\n createCloudThreadListAdapterCreateFallback,\n createToolCallCancellationStub,\n getThreadMessageText,\n scanPendingToolCalls,\n} from \"@assistant-ui/core/internal\";\nimport {\n useCloudThreadListAdapter,\n useExternalStoreRuntime,\n useExternalMessageConverter,\n useRemoteThreadListRuntime,\n} from \"@assistant-ui/core/react\";\nimport { useAui, useAuiState } from \"@assistant-ui/store\";\nimport { STREAM_CONTROLLER, useChannel, useStream } from \"@langchain/react\";\nimport type {\n LangChainBaseMessage,\n LangChainToolCall,\n UIMessage,\n UseStreamRuntimeOptions,\n} from \"./types\";\nimport { getMessageModality, groupUIMessagesByParent } from \"./converter\";\nexport { groupUIMessagesByParent } from \"./converter\";\nimport {\n createLangChainMetadataKey,\n convertLangChainBaseMessage,\n getMessageContent,\n getMessageType,\n type LangChainMessageConverterMetadata,\n} from \"./convertMessages\";\nimport {\n attachSubagentTranscripts,\n createAttachMemo,\n} from \"./attachSubagentTranscripts\";\nimport { useSubagentTranscripts } from \"./useSubagentTranscripts\";\nimport {\n createUIFoldMemo,\n createUISnapshotMemo,\n foldUIUpdates,\n mergeUIMessages,\n reconcileUISnapshot,\n UI_CUSTOM_CHANNELS,\n} from \"./uiMessages\";\nimport { langChainExtras } from \"./runtimeExtras\";\nimport { resolveForkCheckpoint } from \"./resolveForkCheckpoint\";\nimport { useLangChainStreamingTiming } from \"./streamingTiming\";\nimport { LANGCHAIN_SDK } from \"./sdkIdentity\";\nimport { LangChainThreadController } from \"./LangChainThreadController\";\nimport type { LangChainThreadAction } from \"./langChainThreadState\";\n\nexport const runConfigToSubmitOptions = (\n runConfig: AppendMessage[\"runConfig\"],\n) =>\n runConfig?.custom\n ? { config: { configurable: runConfig.custom } }\n : undefined;\n\ntype NormalizedRunConfigOptions = NonNullable<\n ReturnType<typeof runConfigToSubmitOptions>\n>;\n\nexport const getPendingToolCalls = (\n messages: readonly LangChainBaseMessage[],\n): LangChainToolCall[] =>\n scanPendingToolCalls(\n messages,\n (message) => {\n const type = getMessageType(message);\n if (type === \"ai\") {\n return {\n toolCalls: (message.tool_calls ?? []).filter(\n (toolCall) => typeof toolCall === \"object\" && toolCall !== null,\n ),\n };\n }\n if (type === \"tool\" && message.tool_call_id) {\n return { toolCallId: message.tool_call_id };\n }\n return undefined;\n },\n (toolCall) => toolCall.id,\n );\n\nconst toStagedHumanMessage = (\n msg: AppendMessage,\n id = generateId(),\n): LangChainBaseMessage & { id: string } => ({\n id,\n _getType: () => \"human\",\n content: getMessageContent(msg),\n});\n\nconst toStagedMessageInput = (message: LangChainBaseMessage) => ({\n id: message.id,\n type: getMessageType(message) === \"ai\" ? (\"ai\" as const) : (\"human\" as const),\n content: message.content,\n ...(message.additional_kwargs && {\n additional_kwargs: message.additional_kwargs,\n }),\n});\n\nconst truncateLangChainBaseMessages = (\n threadMessages: readonly ThreadMessage[],\n parentId: string | null,\n): LangChainBaseMessage[] => {\n if (parentId === null) return [];\n const parentIndex = threadMessages.findIndex((m) => m.id === parentId);\n if (parentIndex === -1) return [];\n const truncated: LangChainBaseMessage[] = [];\n for (let i = 0; i <= parentIndex && i < threadMessages.length; i++) {\n truncated.push(\n ...getExternalStoreMessages<LangChainBaseMessage>(threadMessages[i]!),\n );\n }\n return truncated;\n};\n\ntype DistributiveOmit<T, K extends keyof any> = T extends unknown\n ? Omit<T, K>\n : never;\n\nconst useStreamThreadRuntime = (\n options: DistributiveOmit<\n UseStreamRuntimeOptions,\n \"cloud\" | \"scopeId\" | \"unstable_threadListAdapter\" | \"create\" | \"delete\"\n >,\n) => {\n const { adapters, autoCancelPendingToolCalls, unstable_allowCancellation } =\n options;\n const aui = useAui();\n const messagesKey = options.messagesKey ?? \"messages\";\n const uiStateKey = options.uiStateKey ?? \"ui\";\n\n const externalId = useAuiState((s) => s.threadListItem.externalId) as\n | string\n | null;\n // Object.assign preserves the discriminated transport union; object spread\n // collapses its arms and no longer satisfies UseStreamOptions.\n const stream = useStream(\n Object.assign({}, options, { threadId: externalId }),\n );\n const [threadController] = useState(() => new LangChainThreadController());\n const getVisibleStagedMessages = () =>\n threadController.getState().visibleStagedMessages;\n const visibleStagedMessages = useSyncExternalStore(\n threadController.subscribe,\n getVisibleStagedMessages,\n getVisibleStagedMessages,\n );\n const forkPending = useSyncExternalStore(\n threadController.subscribe,\n () => threadController.getState().forkPending,\n () => threadController.getState().forkPending,\n );\n\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n const hasExecutingTools = Object.values(toolStatuses).some(\n (s) => s?.type === \"executing\",\n );\n const effectiveIsRunning = stream.isLoading || hasExecutingTools;\n\n const [uiSnapshotMemo] = useState(createUISnapshotMemo);\n const uiStateValue = reconcileUISnapshot(\n stream.values[uiStateKey],\n uiSnapshotMemo,\n );\n\n const customEvents = useChannel(stream, UI_CUSTOM_CHANNELS);\n const [uiFoldMemo] = useState(createUIFoldMemo);\n const liveUiMessages = useMemo(\n () => foldUIUpdates(customEvents, uiFoldMemo),\n [customEvents, uiFoldMemo],\n );\n\n const mergedUiMessages = useMemo(\n () => mergeUIMessages(liveUiMessages, uiStateValue),\n [liveUiMessages, uiStateValue],\n );\n\n const uiMessagesByParent = useMemo(\n () => groupUIMessagesByParent<UIMessage>(mergedUiMessages),\n [mergedUiMessages],\n );\n\n const visibleMessages =\n visibleStagedMessages ?? (stream.messages as LangChainBaseMessage[]);\n\n const messageTiming = useLangChainStreamingTiming(\n visibleMessages,\n effectiveIsRunning,\n );\n\n const subagentTranscripts = useSubagentTranscripts(\n stream,\n uiMessagesByParent,\n );\n\n const [getConverterMetadataKey] = useState(createLangChainMetadataKey);\n const converterMetadata = useMemo<LangChainMessageConverterMetadata>(\n () => ({\n uiMessagesByParent: uiMessagesByParent,\n messageTiming,\n }),\n [uiMessagesByParent, messageTiming],\n );\n\n const threadMessages = useExternalMessageConverter({\n callback: convertLangChainBaseMessage,\n messages: visibleMessages,\n isRunning: effectiveIsRunning,\n metadata: converterMetadata,\n getMetadataKey: getConverterMetadataKey,\n });\n const [memo] = useState(createAttachMemo);\n const messagesWithTranscripts = useMemo(\n () => attachSubagentTranscripts(threadMessages, subagentTranscripts, memo),\n [threadMessages, subagentTranscripts, memo],\n );\n\n const streamRef = useRef(stream);\n useInsertionEffect(() => {\n streamRef.current = stream;\n }, [stream]);\n\n const activeRunConfigRef = useRef<\n NormalizedRunConfigOptions[\"config\"] | undefined\n >(undefined);\n const runConfigByMessageIdRef = useRef(\n new Map<string, NormalizedRunConfigOptions[\"config\"] | undefined>(),\n );\n const activeThreadIdRef = useRef(externalId);\n const setActiveRunConfig = useCallback(\n (runConfig: AppendMessage[\"runConfig\"]) => {\n activeRunConfigRef.current = runConfigToSubmitOptions(runConfig)?.config;\n },\n [],\n );\n const withActiveRunConfig = useCallback(\n (submitOptions?: Record<string, unknown>) => {\n if (submitOptions && \"config\" in submitOptions) return submitOptions;\n if (activeRunConfigRef.current === undefined) return submitOptions;\n return { ...submitOptions, config: activeRunConfigRef.current };\n },\n [],\n );\n\n useEffect(() => {\n if (\n activeThreadIdRef.current !== null &&\n activeThreadIdRef.current !== externalId\n ) {\n activeRunConfigRef.current = undefined;\n runConfigByMessageIdRef.current.clear();\n }\n activeThreadIdRef.current = externalId;\n }, [externalId]);\n\n useEffect(() => {\n const messages = stream.messages as readonly LangChainBaseMessage[];\n const owned = runConfigByMessageIdRef.current;\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages.at(i);\n if (\n !message?.id ||\n getMessageType(message) !== \"ai\" ||\n !message.tool_calls?.length\n ) {\n continue;\n }\n if (owned.has(message.id)) return;\n break;\n }\n for (const message of messages) {\n if (\n !message.id ||\n getMessageType(message) !== \"ai\" ||\n !message.tool_calls?.length ||\n owned.has(message.id)\n ) {\n continue;\n }\n owned.set(message.id, activeRunConfigRef.current);\n }\n }, [stream.messages]);\n\n const visibleMessagesRef = useRef(visibleMessages);\n useInsertionEffect(() => {\n visibleMessagesRef.current = visibleMessages;\n }, [visibleMessages]);\n\n const dispatchStaging = useCallback(\n (action: LangChainThreadAction, fallback = visibleMessagesRef.current) => {\n threadController.dispatch(action);\n visibleMessagesRef.current =\n threadController.getState().visibleStagedMessages ?? fallback;\n },\n [threadController],\n );\n\n const threadMessagesRef = useRef(messagesWithTranscripts);\n useInsertionEffect(() => {\n threadMessagesRef.current = messagesWithTranscripts;\n }, [messagesWithTranscripts]);\n\n useEffect(() => {\n const state = threadController.getState();\n if (state.stagedEntries.size === 0) return;\n const messages = stream.messages as LangChainBaseMessage[];\n dispatchStaging(\n {\n type: \"reconcile\",\n messages,\n visibleMessages: visibleMessagesRef.current,\n },\n state.stagedBaseMessages ?? messages,\n );\n }, [stream.messages, threadController, dispatchStaging]);\n\n const getStagedRun = (parentId: string | null) => {\n const stagedEntries = threadController.getState().stagedEntries;\n const parent = parentId ? stagedEntries.get(parentId) : undefined;\n if (!parent || parent.transcriptStatus === \"sent\") return null;\n\n const staged: LangChainBaseMessage[] = [];\n for (const message of visibleMessagesRef.current) {\n const entry = message.id ? stagedEntries.get(message.id) : undefined;\n if (entry && entry.transcriptStatus !== \"sent\") {\n staged.push(entry.message);\n }\n if (message.id === parentId) break;\n }\n\n return {\n messages: staged,\n runConfig: parent.runConfig,\n };\n };\n\n const appendVoiceTranscript = (message: ThreadMessage) => {\n const transcript = {\n id: message.id,\n _getType: () => (message.role === \"assistant\" ? \"ai\" : \"human\"),\n content: getThreadMessageText(message),\n ...(message.metadata.modality && {\n additional_kwargs: { modality: message.metadata.modality },\n }),\n };\n dispatchStaging({\n type: \"stage\",\n entry: {\n message: transcript,\n runConfig: undefined,\n reconcileOnEcho: false,\n baseMessageCount: streamRef.current.messages.length,\n transcriptStatus: \"unsent\",\n },\n visibleMessages: visibleMessagesRef.current,\n });\n };\n\n const getUnsentTranscripts = () => {\n const stagedEntries = threadController.getState().stagedEntries;\n return visibleMessagesRef.current.filter(\n (message) =>\n message.id !== undefined &&\n stagedEntries.get(message.id)?.transcriptStatus === \"unsent\",\n );\n };\n\n const setTranscriptStatus = (\n messages: readonly LangChainBaseMessage[],\n status: \"unsent\" | \"sent\",\n ) => {\n dispatchStaging({ type: \"markTranscript\", messages, status });\n };\n\n // Reserving before the submit keeps an overlapping submit from carrying the\n // same transcript; a failed submit hands it back to the next run.\n const submitCarryingTranscripts = async (\n transcripts: readonly LangChainBaseMessage[],\n submit: () => Promise<void>,\n ) => {\n setTranscriptStatus(transcripts, \"sent\");\n try {\n await submit();\n } catch (error) {\n setTranscriptStatus(transcripts, \"unsent\");\n throw error;\n }\n };\n\n const dropTranscripts = (messages: readonly LangChainBaseMessage[]) => {\n for (const message of messages) {\n if (\n message.id &&\n threadController.getState().stagedEntries.get(message.id)\n ?.transcriptStatus\n )\n removeStagedMessage(message.id);\n }\n };\n\n const isTranscriptMessage = (message: LangChainBaseMessage) => {\n const staged = message.id\n ? threadController.getState().stagedEntries.get(message.id)\n : undefined;\n if (staged?.transcriptStatus === \"unsent\") return true;\n const type = getMessageType(message);\n return (\n (type === \"human\" || type === \"ai\") &&\n getMessageModality(message.additional_kwargs) !== undefined\n );\n };\n\n // A transcript reaches the graph in the same input as the message after it, so\n // no checkpoint ends at one; a fork starts before the trailing transcripts.\n const planForkTranscripts = (parentId: string | null) => {\n const visible = visibleMessagesRef.current;\n const parentIndex =\n parentId == null ? -1 : visible.findIndex((m) => m.id === parentId);\n if (parentId != null && parentIndex === -1)\n return { forkParentId: parentId, transcripts: [], truncated: [] };\n\n let start = parentIndex + 1;\n while (start > 0 && isTranscriptMessage(visible[start - 1]!)) start--;\n return {\n forkParentId: start > 0 ? (visible[start - 1]!.id ?? null) : null,\n transcripts: visible.slice(start, parentIndex + 1),\n truncated: visible.slice(parentIndex + 1),\n };\n };\n\n const stageUserMessage = (msg: AppendMessage, reconcileOnEcho = false) => {\n const stagedMessage = toStagedHumanMessage(msg);\n dispatchStaging({\n type: \"stage\",\n entry: {\n message: stagedMessage,\n runConfig: msg.runConfig,\n reconcileOnEcho,\n baseMessageCount: streamRef.current.messages.length,\n },\n visibleMessages: visibleMessagesRef.current,\n });\n return stagedMessage;\n };\n\n const removeStagedMessage = (id: string) => {\n if (!threadController.getState().stagedEntries.has(id)) return;\n const visible = visibleMessagesRef.current;\n const nextMessages = visible.filter((message) => message.id !== id);\n dispatchStaging(\n { type: \"remove\", id, visibleMessages: visible },\n nextMessages,\n );\n };\n\n const supersedeFork = () => {\n threadController.dispatch({ type: \"supersedeFork\" });\n return threadController.getState().forkGeneration;\n };\n\n const extras = useMemo(\n () =>\n langChainExtras.provide({\n interrupt: stream.interrupt,\n interrupts: stream.interrupts,\n toolCalls: stream.toolCalls,\n subagents: stream.subagents,\n subgraphs: stream.subgraphs,\n stream,\n error: stream.error,\n submit: (values, submitOptions) => {\n const isResume = values == null || submitOptions?.command != null;\n return stream.submit(\n values,\n isResume ? withActiveRunConfig(submitOptions) : submitOptions,\n );\n },\n respond: (response, respondOptions) =>\n stream.respond(response, withActiveRunConfig(respondOptions)),\n respondAll: (responsesById, respondOptions) =>\n stream.respondAll(responsesById, withActiveRunConfig(respondOptions)),\n values: stream.values,\n messagesKey,\n }),\n [stream, messagesKey, withActiveRunConfig],\n );\n\n const runtime = useExternalStoreRuntime({\n ...pickExternalStoreSharedOptions(options),\n isRunning: stream.isLoading || forkPending,\n isLoading: stream.isThreadLoading,\n messages: messagesWithTranscripts,\n adapters,\n extras,\n unstable_enableToolInvocations: true,\n setToolStatuses,\n onNew: async (msg) => {\n if (!(msg.startRun ?? msg.role === \"user\")) {\n stageUserMessage(msg);\n return;\n }\n\n const stagedMessage = stageUserMessage(msg, true);\n const stagedMessageId = stagedMessage.id;\n setActiveRunConfig(msg.runConfig);\n const content = getMessageContent(msg);\n const cancellations =\n autoCancelPendingToolCalls !== false\n ? getPendingToolCalls(\n streamRef.current.messages as readonly LangChainBaseMessage[],\n )\n // LangChain rejects a tool message without a tool_call_id.\n .filter((toolCall) => toolCall.id)\n .map(createToolCallCancellationStub)\n : [];\n // A null threadId is not a no-op for the SDK: it rebinds the controller\n // away from its self-created thread and forces a fresh one, so the\n // submit waits for initialization to produce an identity; core no\n // longer holds appends on that barrier.\n try {\n const { externalId } = await aui.threadListItem.initialize();\n const transcripts = getUnsentTranscripts();\n await submitCarryingTranscripts(transcripts, () =>\n streamRef.current.submit(\n {\n [messagesKey]: [\n ...cancellations,\n ...transcripts.map(toStagedMessageInput),\n {\n id: stagedMessageId,\n type: \"human\",\n content,\n },\n ],\n },\n {\n ...runConfigToSubmitOptions(msg.runConfig),\n ...(externalId != null ? { threadId: externalId } : {}),\n },\n ),\n );\n } catch (error) {\n removeStagedMessage(stagedMessageId);\n throw error;\n }\n },\n onVoiceTranscript: appendVoiceTranscript,\n onAddToolResult: async ({\n messageId,\n toolCallId,\n toolName,\n result,\n isError,\n artifact,\n }) => {\n const runConfig = runConfigByMessageIdRef.current.has(messageId)\n ? runConfigByMessageIdRef.current.get(messageId)\n : activeRunConfigRef.current;\n await stream.submit(\n {\n [messagesKey]: [\n {\n type: \"tool\",\n name: toolName,\n tool_call_id: toolCallId,\n content: JSON.stringify(result),\n ...(artifact !== undefined && { artifact }),\n status: isError ? \"error\" : \"success\",\n },\n ],\n },\n runConfig === undefined ? undefined : { config: runConfig },\n );\n },\n onReload: async (parentId, config) => {\n const forkGeneration = supersedeFork();\n try {\n await streamRef.current.stop();\n if (threadController.getState().forkGeneration !== forkGeneration)\n return;\n const stagedRun = getStagedRun(parentId);\n if (stagedRun) {\n if (\n config.sourceId &&\n threadController.getState().stagedEntries.get(config.sourceId)\n ?.transcriptStatus\n )\n removeStagedMessage(config.sourceId);\n dispatchStaging({\n type: \"promote\",\n messages: stagedRun.messages,\n visibleMessages: visibleMessagesRef.current,\n });\n const runConfig = config.runConfig ?? stagedRun.runConfig;\n setActiveRunConfig(runConfig);\n await submitCarryingTranscripts(stagedRun.messages, () =>\n stream.submit(\n {\n [messagesKey]: stagedRun.messages.map(toStagedMessageInput),\n },\n runConfigToSubmitOptions(runConfig),\n ),\n );\n return;\n }\n\n const threadId = externalId;\n if (!threadId || parentId == null) return;\n const s = streamRef.current;\n const fork = planForkTranscripts(parentId);\n const checkpointId = await resolveForkCheckpoint(\n s.client,\n threadId,\n s.messages as readonly LangChainBaseMessage[],\n fork.forkParentId,\n config.sourceId,\n s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),\n messagesKey,\n );\n if (threadController.getState().forkGeneration !== forkGeneration)\n return;\n if (!checkpointId) return;\n dropTranscripts(fork.truncated);\n setActiveRunConfig(config.runConfig);\n await submitCarryingTranscripts(fork.transcripts, () =>\n s.submit(\n fork.transcripts.length > 0\n ? { [messagesKey]: fork.transcripts.map(toStagedMessageInput) }\n : null,\n {\n forkFrom: checkpointId,\n ...runConfigToSubmitOptions(config.runConfig),\n },\n ),\n );\n } finally {\n threadController.dispatch({\n type: \"finishFork\",\n generation: forkGeneration,\n });\n }\n },\n onEdit: async (message) => {\n const forkGeneration = supersedeFork();\n try {\n await streamRef.current.stop();\n if (threadController.getState().forkGeneration !== forkGeneration)\n return;\n if (!(message.startRun ?? message.role === \"user\")) {\n const truncated = truncateLangChainBaseMessages(\n threadMessagesRef.current,\n message.parentId,\n );\n const stagedMessage = toStagedHumanMessage(message);\n dispatchStaging({\n type: \"stageEdit\",\n entry: {\n message: stagedMessage,\n runConfig: message.runConfig,\n reconcileOnEcho: false,\n baseMessageCount: 0,\n },\n baseMessages: truncated,\n });\n return;\n }\n\n const threadId = externalId;\n if (!threadId) return;\n const s = streamRef.current;\n const fork = planForkTranscripts(message.parentId);\n const checkpointId = await resolveForkCheckpoint(\n s.client,\n threadId,\n s.messages as readonly LangChainBaseMessage[],\n fork.forkParentId,\n message.sourceId,\n s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),\n messagesKey,\n );\n if (threadController.getState().forkGeneration !== forkGeneration)\n return;\n if (!checkpointId) return;\n dropTranscripts(fork.truncated);\n const content = getMessageContent(message);\n setActiveRunConfig(message.runConfig);\n await submitCarryingTranscripts(fork.transcripts, () =>\n s.submit(\n {\n [messagesKey]: [\n ...fork.transcripts.map(toStagedMessageInput),\n { type: \"human\", content },\n ],\n },\n {\n forkFrom: checkpointId,\n ...runConfigToSubmitOptions(message.runConfig),\n },\n ),\n );\n } finally {\n threadController.dispatch({\n type: \"finishFork\",\n generation: forkGeneration,\n });\n }\n },\n onCancel:\n unstable_allowCancellation !== false\n ? async () => {\n threadController.dispatch({ type: \"cancelFork\" });\n activeRunConfigRef.current = undefined;\n await stream.stop();\n }\n : undefined,\n });\n\n return runtime;\n};\n\n/**\n * Creates an assistant-ui runtime backed by LangChain's `useStream` hook.\n * Accepts the same options as `useStream` from `@langchain/react`, plus\n * `cloud`, `scopeId`, and `adapters`.\n *\n * @example\n * ```tsx\n * import { useStreamRuntime } from \"@assistant-ui/react-langchain\";\n * import { AssistantRuntimeProvider, Thread } from \"@assistant-ui/react\";\n *\n * function App() {\n * const runtime = useStreamRuntime({\n * assistantId: \"agent\",\n * apiUrl: \"http://localhost:2024\",\n * });\n *\n * return (\n * <AssistantRuntimeProvider runtime={runtime}>\n * <Thread />\n * </AssistantRuntimeProvider>\n * );\n * }\n * ```\n */\nexport const useStreamRuntime = (rawOptions: UseStreamRuntimeOptions) => {\n const {\n cloud,\n scopeId,\n unstable_threadListAdapter,\n create,\n delete: deleteFn,\n onThreadIdChange,\n ...options\n } = rawOptions;\n\n const aui = useAui();\n const cloudAdapter = useCloudThreadListAdapter({\n sdk: LANGCHAIN_SDK,\n cloud,\n scopeId,\n create: createCloudThreadListAdapterCreateFallback(\n create,\n aui.threadListItem,\n ),\n delete: deleteFn,\n });\n const adapter = unstable_threadListAdapter ?? cloudAdapter;\n\n return useRemoteThreadListRuntime({\n runtimeHook: function RuntimeHook() {\n return useStreamThreadRuntime(options);\n },\n adapter,\n allowNesting: true,\n onThreadIdChange,\n });\n};\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { attachSubagentTranscripts, createAttachMemo } from "./attachSubagentTranscripts.js";
|
|
3
2
|
import { groupUIMessagesByParent } from "./converter.js";
|
|
4
3
|
import { convertLangChainBaseMessage } from "./convertMessages.js";
|
|
4
|
+
import { attachSubagentTranscripts, createAttachMemo } from "./attachSubagentTranscripts.js";
|
|
5
5
|
import { langChainStreamingTimingAccessors } from "./streamingTiming.js";
|
|
6
6
|
import { subagentMessagesProjection } from "./subagentMessagesProjection.js";
|
|
7
7
|
import { UI_CUSTOM_CHANNELS, createUIFoldMemo, foldUIUpdates, mergeUIMessages } from "./uiMessages.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react-langchain",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.36",
|
|
4
4
|
"description": "LangChain useStream adapter for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"langchain",
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
],
|
|
41
41
|
"sideEffects": false,
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@assistant-ui/core": "^0.3.
|
|
44
|
-
"@assistant-ui/store": "^0.3.
|
|
43
|
+
"@assistant-ui/core": "^0.3.25",
|
|
44
|
+
"@assistant-ui/store": "^0.3.18",
|
|
45
45
|
"assistant-cloud": "*",
|
|
46
|
-
"assistant-stream": "^0.3.
|
|
46
|
+
"assistant-stream": "^0.3.49"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"@langchain/core": "^1.0.0",
|
|
@@ -69,12 +69,12 @@
|
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@assistant-ui/x-buildutils": "0.0.30",
|
|
71
71
|
"@assistant-ui/x-react18": "0.0.0",
|
|
72
|
-
"@langchain/core": "^1.2.
|
|
73
|
-
"@langchain/langgraph-sdk": "^1.12.
|
|
74
|
-
"@langchain/react": "^1.2.
|
|
72
|
+
"@langchain/core": "^1.2.17",
|
|
73
|
+
"@langchain/langgraph-sdk": "^1.12.2",
|
|
74
|
+
"@langchain/react": "^1.2.2",
|
|
75
75
|
"@testing-library/react": "^16.3.3",
|
|
76
76
|
"@types/react": "^19.3.0",
|
|
77
|
-
"jsdom": "^30.
|
|
77
|
+
"jsdom": "^30.1.2",
|
|
78
78
|
"react": "^19.3.0",
|
|
79
79
|
"vitest": "^5.0.3"
|
|
80
80
|
},
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createLangChainThreadState,
|
|
3
|
+
reduceLangChainThreadState,
|
|
4
|
+
type LangChainThreadAction,
|
|
5
|
+
} from "./langChainThreadState";
|
|
6
|
+
|
|
7
|
+
export class LangChainThreadController {
|
|
8
|
+
private state = createLangChainThreadState();
|
|
9
|
+
private listeners = new Set<() => void>();
|
|
10
|
+
|
|
11
|
+
public getState = () => this.state;
|
|
12
|
+
|
|
13
|
+
public subscribe = (listener: () => void) => {
|
|
14
|
+
this.listeners.add(listener);
|
|
15
|
+
return () => this.listeners.delete(listener);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
public dispatch = (action: LangChainThreadAction) => {
|
|
19
|
+
const nextState = reduceLangChainThreadState(this.state, action);
|
|
20
|
+
if (nextState === this.state) return;
|
|
21
|
+
this.state = nextState;
|
|
22
|
+
for (const listener of this.listeners) listener();
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -208,6 +208,29 @@ describe("getMessageContent file blocks", () => {
|
|
|
208
208
|
]);
|
|
209
209
|
});
|
|
210
210
|
|
|
211
|
+
it("uses the file media type for a media-less base64 data URL", () => {
|
|
212
|
+
const content = getMessageContent(
|
|
213
|
+
appendMessage({
|
|
214
|
+
type: "file",
|
|
215
|
+
data: "data:;base64,ZmFrZQ==",
|
|
216
|
+
mimeType: "application/pdf",
|
|
217
|
+
filename: "a.pdf",
|
|
218
|
+
}),
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
expect(content).toEqual([
|
|
222
|
+
{ type: "text", text: " " },
|
|
223
|
+
{
|
|
224
|
+
type: "file",
|
|
225
|
+
data: "ZmFrZQ==",
|
|
226
|
+
mime_type: "application/pdf",
|
|
227
|
+
filename: "a.pdf",
|
|
228
|
+
metadata: { filename: "a.pdf" },
|
|
229
|
+
source_type: "base64",
|
|
230
|
+
},
|
|
231
|
+
]);
|
|
232
|
+
});
|
|
233
|
+
|
|
211
234
|
it("keeps non-http schemes on the base64 path", () => {
|
|
212
235
|
const content = getMessageContent(
|
|
213
236
|
appendMessage({
|
package/src/convertMessages.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
convertLangChainContentBlock,
|
|
12
12
|
getCustomMetadata,
|
|
13
13
|
getMessageModality,
|
|
14
|
+
normalizeToolCallArgs,
|
|
14
15
|
uiMessageToDataPart,
|
|
15
16
|
withAudioTranscript,
|
|
16
17
|
} from "./converter";
|
|
@@ -89,21 +90,6 @@ const contentBlocks = (content: unknown): readonly LangChainContentBlock[] => {
|
|
|
89
90
|
return [];
|
|
90
91
|
};
|
|
91
92
|
|
|
92
|
-
const normalizeToolCallArgs = (args: unknown): ReadonlyJSONObject => {
|
|
93
|
-
if (typeof args !== "object" || args === null || Array.isArray(args)) {
|
|
94
|
-
return {};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
const prototype = Object.getPrototypeOf(args);
|
|
99
|
-
return prototype === Object.prototype || prototype === null
|
|
100
|
-
? (args as ReadonlyJSONObject)
|
|
101
|
-
: {};
|
|
102
|
-
} catch {
|
|
103
|
-
return {};
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
|
|
107
93
|
const toolCallArgs = (
|
|
108
94
|
value: unknown,
|
|
109
95
|
): { args: ReadonlyJSONObject; argsText: string } => {
|
|
@@ -123,7 +109,14 @@ const contentToParts = (content: unknown) => {
|
|
|
123
109
|
return [{ type: "text" as const, text: content }];
|
|
124
110
|
|
|
125
111
|
return contentBlocks(content)
|
|
126
|
-
.map(
|
|
112
|
+
.map((block) => {
|
|
113
|
+
const part = convertLangChainContentBlock(block);
|
|
114
|
+
if (part === undefined)
|
|
115
|
+
warnOnceInDevelopment(
|
|
116
|
+
`Dropped an unrepresentable message part of type: ${block.type}`,
|
|
117
|
+
);
|
|
118
|
+
return part;
|
|
119
|
+
})
|
|
127
120
|
.filter((part) => part !== null && part !== undefined);
|
|
128
121
|
};
|
|
129
122
|
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { convertLangChainContentBlock } from "./converter";
|
|
3
|
+
|
|
4
|
+
describe("convertLangChainContentBlock standard content blocks", () => {
|
|
5
|
+
it("converts a base64 image block to an image part", () => {
|
|
6
|
+
expect(
|
|
7
|
+
convertLangChainContentBlock({
|
|
8
|
+
type: "image",
|
|
9
|
+
mimeType: "image/png",
|
|
10
|
+
data: "ZmFrZQ==",
|
|
11
|
+
}),
|
|
12
|
+
).toEqual({ type: "image", image: "data:image/png;base64,ZmFrZQ==" });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("converts a url image block to an image part", () => {
|
|
16
|
+
expect(
|
|
17
|
+
convertLangChainContentBlock({
|
|
18
|
+
type: "image",
|
|
19
|
+
mimeType: "image/png",
|
|
20
|
+
url: "https://cdn.example/a.png",
|
|
21
|
+
}),
|
|
22
|
+
).toEqual({ type: "image", image: "https://cdn.example/a.png" });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("keeps a base64 image block that already carries a data url", () => {
|
|
26
|
+
expect(
|
|
27
|
+
convertLangChainContentBlock({
|
|
28
|
+
type: "image",
|
|
29
|
+
mimeType: "image/png",
|
|
30
|
+
data: "data:image/png;base64,ZmFrZQ==",
|
|
31
|
+
}),
|
|
32
|
+
).toEqual({ type: "image", image: "data:image/png;base64,ZmFrZQ==" });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("keeps a percent-encoded data url out of a base64 envelope", () => {
|
|
36
|
+
const image = "data:image/svg+xml,%3Csvg%3E%3C/svg%3E";
|
|
37
|
+
expect(
|
|
38
|
+
convertLangChainContentBlock({
|
|
39
|
+
type: "image",
|
|
40
|
+
mimeType: "image/svg+xml",
|
|
41
|
+
data: image,
|
|
42
|
+
}),
|
|
43
|
+
).toEqual({ type: "image", image });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("keeps an uppercase data url scheme out of a base64 envelope", () => {
|
|
47
|
+
const image = "DATA:image/png;base64,ZmFrZQ==";
|
|
48
|
+
expect(
|
|
49
|
+
convertLangChainContentBlock({
|
|
50
|
+
type: "image",
|
|
51
|
+
mimeType: "image/png",
|
|
52
|
+
data: image,
|
|
53
|
+
}),
|
|
54
|
+
).toEqual({ type: "image", image });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("reads the camelCase mime type of a base64 file block", () => {
|
|
58
|
+
expect(
|
|
59
|
+
convertLangChainContentBlock({
|
|
60
|
+
type: "file",
|
|
61
|
+
mimeType: "application/pdf",
|
|
62
|
+
data: "JVBERi0=",
|
|
63
|
+
}),
|
|
64
|
+
).toEqual({
|
|
65
|
+
type: "file",
|
|
66
|
+
filename: "file",
|
|
67
|
+
data: "JVBERi0=",
|
|
68
|
+
mimeType: "application/pdf",
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("resolves the url of a file block that carries no source_type", () => {
|
|
73
|
+
expect(
|
|
74
|
+
convertLangChainContentBlock({
|
|
75
|
+
type: "file",
|
|
76
|
+
mimeType: "application/pdf",
|
|
77
|
+
url: "https://cdn.example/a.pdf",
|
|
78
|
+
metadata: { filename: "a.pdf" },
|
|
79
|
+
}),
|
|
80
|
+
).toEqual({
|
|
81
|
+
type: "file",
|
|
82
|
+
filename: "a.pdf",
|
|
83
|
+
data: "https://cdn.example/a.pdf",
|
|
84
|
+
mimeType: "application/pdf",
|
|
85
|
+
sourceType: "url",
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("resolves the fileId of a file block as an id source", () => {
|
|
90
|
+
expect(
|
|
91
|
+
convertLangChainContentBlock({
|
|
92
|
+
type: "file",
|
|
93
|
+
mimeType: "application/pdf",
|
|
94
|
+
fileId: "file-abc123",
|
|
95
|
+
}),
|
|
96
|
+
).toEqual({
|
|
97
|
+
type: "file",
|
|
98
|
+
filename: "file",
|
|
99
|
+
data: "file-abc123",
|
|
100
|
+
mimeType: "application/pdf",
|
|
101
|
+
sourceType: "id",
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("reads the Python base64 field of an image block", () => {
|
|
106
|
+
expect(
|
|
107
|
+
convertLangChainContentBlock({
|
|
108
|
+
type: "image",
|
|
109
|
+
mime_type: "image/png",
|
|
110
|
+
base64: "ZmFrZQ==",
|
|
111
|
+
}),
|
|
112
|
+
).toEqual({ type: "image", image: "data:image/png;base64,ZmFrZQ==" });
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("resolves the Python file_id field as an id source", () => {
|
|
116
|
+
expect(
|
|
117
|
+
convertLangChainContentBlock({
|
|
118
|
+
type: "file",
|
|
119
|
+
mime_type: "application/pdf",
|
|
120
|
+
file_id: "file-python-123",
|
|
121
|
+
}),
|
|
122
|
+
).toEqual({
|
|
123
|
+
type: "file",
|
|
124
|
+
filename: "file",
|
|
125
|
+
data: "file-python-123",
|
|
126
|
+
mimeType: "application/pdf",
|
|
127
|
+
sourceType: "id",
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("converts a video block to a file part", () => {
|
|
132
|
+
expect(
|
|
133
|
+
convertLangChainContentBlock({
|
|
134
|
+
type: "video",
|
|
135
|
+
mimeType: "video/mp4",
|
|
136
|
+
data: "ZmFrZQ==",
|
|
137
|
+
}),
|
|
138
|
+
).toEqual({
|
|
139
|
+
type: "file",
|
|
140
|
+
filename: "video.mp4",
|
|
141
|
+
data: "ZmFrZQ==",
|
|
142
|
+
mimeType: "video/mp4",
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("converts an audio block that names its mime type in camelCase", () => {
|
|
147
|
+
expect(
|
|
148
|
+
convertLangChainContentBlock({
|
|
149
|
+
type: "audio",
|
|
150
|
+
mimeType: "audio/wav",
|
|
151
|
+
data: "ZmFrZQ==",
|
|
152
|
+
}),
|
|
153
|
+
).toEqual({
|
|
154
|
+
type: "file",
|
|
155
|
+
filename: "audio.wav",
|
|
156
|
+
data: "ZmFrZQ==",
|
|
157
|
+
mimeType: "audio/wav",
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("converts a text-plain block to a file part", () => {
|
|
162
|
+
expect(
|
|
163
|
+
convertLangChainContentBlock({
|
|
164
|
+
type: "text-plain",
|
|
165
|
+
data: "ZmFrZQ==",
|
|
166
|
+
metadata: { filename: "notes.txt" },
|
|
167
|
+
}),
|
|
168
|
+
).toEqual({
|
|
169
|
+
type: "file",
|
|
170
|
+
filename: "notes.txt",
|
|
171
|
+
data: "ZmFrZQ==",
|
|
172
|
+
mimeType: "text/plain",
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("falls back to a file part for an image referenced by id", () => {
|
|
177
|
+
expect(
|
|
178
|
+
convertLangChainContentBlock({
|
|
179
|
+
type: "image",
|
|
180
|
+
mimeType: "image/png",
|
|
181
|
+
fileId: "file-abc123",
|
|
182
|
+
}),
|
|
183
|
+
).toEqual({
|
|
184
|
+
type: "file",
|
|
185
|
+
filename: "file",
|
|
186
|
+
data: "file-abc123",
|
|
187
|
+
mimeType: "image/png",
|
|
188
|
+
sourceType: "id",
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("falls back to a file part for a base64 image with no mime type", () => {
|
|
193
|
+
expect(
|
|
194
|
+
convertLangChainContentBlock({ type: "image", data: "ZmFrZQ==" }),
|
|
195
|
+
).toEqual({
|
|
196
|
+
type: "file",
|
|
197
|
+
filename: "file",
|
|
198
|
+
data: "ZmFrZQ==",
|
|
199
|
+
mimeType: "application/octet-stream",
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("reports a block whose payload is not a string as unknown", () => {
|
|
204
|
+
expect(
|
|
205
|
+
convertLangChainContentBlock({
|
|
206
|
+
type: "image",
|
|
207
|
+
mimeType: "image/png",
|
|
208
|
+
data: new Uint8Array([1, 2, 3]),
|
|
209
|
+
}),
|
|
210
|
+
).toBeUndefined();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("converts a text-plain block carrying inline text to a base64 file part", () => {
|
|
214
|
+
expect(
|
|
215
|
+
convertLangChainContentBlock({
|
|
216
|
+
type: "text-plain",
|
|
217
|
+
text: "hello",
|
|
218
|
+
}),
|
|
219
|
+
).toEqual({
|
|
220
|
+
type: "file",
|
|
221
|
+
filename: "file",
|
|
222
|
+
data: "aGVsbG8=",
|
|
223
|
+
mimeType: "text/plain",
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("round-trips non-ASCII inline text through base64", () => {
|
|
228
|
+
const text = "héllo ✓ https://example.com";
|
|
229
|
+
const part = convertLangChainContentBlock({ type: "text-plain", text });
|
|
230
|
+
expect(part).toMatchObject({ type: "file", mimeType: "text/plain" });
|
|
231
|
+
const data = (part as { data: string }).data;
|
|
232
|
+
const bytes = Uint8Array.from(atob(data), (char) => char.charCodeAt(0));
|
|
233
|
+
expect(new TextDecoder().decode(bytes)).toBe(text);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe("convertLangChainContentBlock legacy data content blocks", () => {
|
|
238
|
+
it("converts a base64 file block that names its mime type in snake_case", () => {
|
|
239
|
+
expect(
|
|
240
|
+
convertLangChainContentBlock({
|
|
241
|
+
type: "file",
|
|
242
|
+
data: "JVBERi0=",
|
|
243
|
+
mime_type: "application/pdf",
|
|
244
|
+
source_type: "base64",
|
|
245
|
+
metadata: { filename: "a.pdf" },
|
|
246
|
+
}),
|
|
247
|
+
).toEqual({
|
|
248
|
+
type: "file",
|
|
249
|
+
filename: "a.pdf",
|
|
250
|
+
data: "JVBERi0=",
|
|
251
|
+
mimeType: "application/pdf",
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("converts a url source file block", () => {
|
|
256
|
+
expect(
|
|
257
|
+
convertLangChainContentBlock({
|
|
258
|
+
type: "file",
|
|
259
|
+
url: "https://cdn.example/a.pdf",
|
|
260
|
+
mime_type: "application/pdf",
|
|
261
|
+
source_type: "url",
|
|
262
|
+
}),
|
|
263
|
+
).toEqual({
|
|
264
|
+
type: "file",
|
|
265
|
+
filename: "file",
|
|
266
|
+
data: "https://cdn.example/a.pdf",
|
|
267
|
+
mimeType: "application/pdf",
|
|
268
|
+
sourceType: "url",
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("converts an id source file block", () => {
|
|
273
|
+
expect(
|
|
274
|
+
convertLangChainContentBlock({
|
|
275
|
+
type: "file",
|
|
276
|
+
id: "file-abc123",
|
|
277
|
+
source_type: "id",
|
|
278
|
+
}),
|
|
279
|
+
).toEqual({
|
|
280
|
+
type: "file",
|
|
281
|
+
filename: "file",
|
|
282
|
+
data: "file-abc123",
|
|
283
|
+
mimeType: "application/octet-stream",
|
|
284
|
+
sourceType: "id",
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("converts a base64 audio block", () => {
|
|
289
|
+
expect(
|
|
290
|
+
convertLangChainContentBlock({
|
|
291
|
+
type: "audio",
|
|
292
|
+
data: "ZmFrZQ==",
|
|
293
|
+
mime_type: "audio/mp3",
|
|
294
|
+
source_type: "base64",
|
|
295
|
+
}),
|
|
296
|
+
).toEqual({
|
|
297
|
+
type: "file",
|
|
298
|
+
filename: "audio.mp3",
|
|
299
|
+
data: "ZmFrZQ==",
|
|
300
|
+
mimeType: "audio/mp3",
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("converts a base64 image data block", () => {
|
|
305
|
+
expect(
|
|
306
|
+
convertLangChainContentBlock({
|
|
307
|
+
type: "image",
|
|
308
|
+
data: "ZmFrZQ==",
|
|
309
|
+
mime_type: "image/png",
|
|
310
|
+
source_type: "base64",
|
|
311
|
+
}),
|
|
312
|
+
).toEqual({ type: "image", image: "data:image/png;base64,ZmFrZQ==" });
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("leaves an unknown block type unconverted", () => {
|
|
316
|
+
expect(
|
|
317
|
+
convertLangChainContentBlock({
|
|
318
|
+
type: "non_standard",
|
|
319
|
+
} as never),
|
|
320
|
+
).toBeUndefined();
|
|
321
|
+
});
|
|
322
|
+
});
|