@assistant-ui/react-langchain 0.0.28 → 0.0.29
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/README.md +1 -1
- package/dist/convertMessages.d.ts.map +1 -1
- package/dist/convertMessages.js +18 -4
- package/dist/convertMessages.js.map +1 -1
- package/dist/useStreamRuntime.d.ts.map +1 -1
- package/dist/useStreamRuntime.js +11 -7
- package/dist/useStreamRuntime.js.map +1 -1
- package/package.json +8 -8
- package/src/convertMessages.test.ts +118 -1
- package/src/convertMessages.ts +31 -6
- package/src/useStreamRuntime.test.tsx +74 -1
- package/src/useStreamRuntime.ts +18 -8
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ npm install @assistant-ui/react @assistant-ui/react-langchain @langchain/react
|
|
|
17
17
|
```tsx
|
|
18
18
|
import { useStreamRuntime } from "@assistant-ui/react-langchain";
|
|
19
19
|
import { AssistantRuntimeProvider } from "@assistant-ui/react";
|
|
20
|
-
import { Thread } from "@/components/assistant-ui/thread";
|
|
20
|
+
import { Thread } from "@/components/assistant-ui/elements/thread.aui";
|
|
21
21
|
|
|
22
22
|
export function App() {
|
|
23
23
|
const runtime = useStreamRuntime({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convertMessages.d.ts","names":[],"sources":["../src/convertMessages.ts"],"mappings":";;;;;KAiBK,oCACH,4BAA4B;EAC1B,qBAAqB,YAAY;EACjC,gBAAgB,eAAe;;
|
|
1
|
+
{"version":3,"file":"convertMessages.d.ts","names":[],"sources":["../src/convertMessages.ts"],"mappings":";;;;;KAiBK,oCACH,4BAA4B;EAC1B,qBAAqB,YAAY;EACjC,gBAAgB,eAAe;;cAetB,iBAAkB,SAAS;cAuC3B,8BACX,SAAS,sBACT,WAAU,sCACT,4BAA4B"}
|
package/dist/convertMessages.js
CHANGED
|
@@ -1,21 +1,35 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { convertLangChainContentBlock, getCustomMetadata, getMessageContent, uiMessageToDataPart, withAudioTranscript } from "./converter.js";
|
|
3
3
|
//#region src/convertMessages.ts
|
|
4
|
+
const warnedMalformedMessages = /* @__PURE__ */ new Set();
|
|
5
|
+
const warnOnceInDevelopment = (message) => {
|
|
6
|
+
if (typeof process === "undefined" || process?.env?.NODE_ENV !== "development") return;
|
|
7
|
+
if (warnedMalformedMessages.has(message)) return;
|
|
8
|
+
warnedMalformedMessages.add(message);
|
|
9
|
+
console.warn(message);
|
|
10
|
+
};
|
|
4
11
|
const getMessageType = (message) => {
|
|
5
12
|
if (typeof message._getType === "function") return message._getType();
|
|
6
13
|
if ("type" in message) return message.type;
|
|
7
|
-
|
|
14
|
+
warnOnceInDevelopment("Cannot determine message type; rendering the message as system text");
|
|
15
|
+
return "unknown";
|
|
16
|
+
};
|
|
17
|
+
const contentBlocks = (content) => {
|
|
18
|
+
if (content == null) return [];
|
|
19
|
+
if (Array.isArray(content)) return content.filter((block) => typeof block === "object" && block !== null);
|
|
20
|
+
warnOnceInDevelopment(`Ignoring message content that is neither a string nor an array: ${typeof content}`);
|
|
21
|
+
return [];
|
|
8
22
|
};
|
|
9
23
|
const contentToParts = (content) => {
|
|
10
24
|
if (typeof content === "string") return [{
|
|
11
25
|
type: "text",
|
|
12
26
|
text: content
|
|
13
27
|
}];
|
|
14
|
-
return content.map(convertLangChainContentBlock).filter((part) => part !== null && part !== void 0);
|
|
28
|
+
return contentBlocks(content).map(convertLangChainContentBlock).filter((part) => part !== null && part !== void 0);
|
|
15
29
|
};
|
|
16
30
|
const getStringContent = (content) => {
|
|
17
31
|
if (typeof content === "string") return content;
|
|
18
|
-
return content.filter((c) => c.type === "text").map((c) => c.text).join("");
|
|
32
|
+
return contentBlocks(content).filter((c) => c.type === "text").map((c) => c.text).join("");
|
|
19
33
|
};
|
|
20
34
|
const convertLangChainBaseMessage = (message, metadata = {}) => {
|
|
21
35
|
switch (getMessageType(message)) {
|
|
@@ -73,7 +87,7 @@ const convertLangChainBaseMessage = (message, metadata = {}) => {
|
|
|
73
87
|
id: message.id,
|
|
74
88
|
content: [{
|
|
75
89
|
type: "text",
|
|
76
|
-
text: typeof message.content === "string" ? message.content : JSON.stringify(message.content)
|
|
90
|
+
text: typeof message.content === "string" ? message.content : JSON.stringify(message.content) ?? ""
|
|
77
91
|
}]
|
|
78
92
|
};
|
|
79
93
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convertMessages.js","names":[
|
|
1
|
+
{"version":3,"file":"convertMessages.js","names":[],"sources":["../src/convertMessages.ts"],"sourcesContent":["\"use client\";\n\nimport type { MessageTiming } from \"@assistant-ui/core\";\nimport type { useExternalMessageConverter } from \"@assistant-ui/core/react\";\nimport type { ReadonlyJSONObject } from \"assistant-stream/utils\";\nimport {\n convertLangChainContentBlock,\n getCustomMetadata,\n uiMessageToDataPart,\n withAudioTranscript,\n} from \"./converter\";\nimport type {\n LangChainBaseMessage,\n LangChainContentBlock,\n UIMessage,\n} from \"./types\";\n\ntype LangChainMessageConverterMetadata =\n useExternalMessageConverter.Metadata & {\n uiMessagesByParent?: Map<string, UIMessage[]>;\n messageTiming?: Record<string, MessageTiming>;\n };\n\nconst warnedMalformedMessages = new Set<string>();\nconst warnOnceInDevelopment = (message: string) => {\n if (\n typeof process === \"undefined\" ||\n process?.env?.NODE_ENV !== \"development\"\n )\n return;\n if (warnedMalformedMessages.has(message)) return;\n warnedMalformedMessages.add(message);\n console.warn(message);\n};\n\nexport const getMessageType = (message: LangChainBaseMessage): string => {\n if (typeof message._getType === \"function\") return message._getType();\n if (\"type\" in message)\n return (message as Record<string, unknown>).type as string;\n warnOnceInDevelopment(\n \"Cannot determine message type; rendering the message as system text\",\n );\n return \"unknown\";\n};\n\nconst contentBlocks = (content: unknown): readonly LangChainContentBlock[] => {\n if (content == null) return [];\n if (Array.isArray(content))\n return content.filter(\n (block) => typeof block === \"object\" && block !== null,\n );\n warnOnceInDevelopment(\n `Ignoring message content that is neither a string nor an array: ${typeof content}`,\n );\n return [];\n};\n\nconst contentToParts = (content: unknown) => {\n if (typeof content === \"string\")\n return [{ type: \"text\" as const, text: content }];\n\n return contentBlocks(content)\n .map(convertLangChainContentBlock)\n .filter((part) => part !== null && part !== undefined);\n};\n\nconst getStringContent = (content: unknown): string => {\n if (typeof content === \"string\") return content;\n return contentBlocks(content)\n .filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n .map((c) => c.text)\n .join(\"\");\n};\n\nexport const convertLangChainBaseMessage = (\n message: LangChainBaseMessage,\n metadata: LangChainMessageConverterMetadata = {},\n): useExternalMessageConverter.Message => {\n const type = getMessageType(message);\n\n switch (type) {\n case \"system\":\n return {\n role: \"system\",\n id: message.id,\n content: [{ type: \"text\", text: getStringContent(message.content) }],\n metadata: {\n custom: getCustomMetadata(message.additional_kwargs),\n },\n };\n\n case \"human\":\n return {\n role: \"user\",\n id: message.id,\n content: contentToParts(message.content),\n metadata: {\n custom: getCustomMetadata(message.additional_kwargs),\n },\n };\n\n case \"ai\": {\n const toolCallParts =\n message.tool_calls?.map((tc) => ({\n type: \"tool-call\" as const,\n toolCallId: tc.id,\n toolName: tc.name,\n args: tc.args as ReadonlyJSONObject,\n argsText: JSON.stringify(tc.args),\n })) ?? [];\n\n const assistantStatus =\n typeof message.status === \"object\" ? message.status : undefined;\n\n const uiDataParts =\n (message.id\n ? metadata.uiMessagesByParent\n ?.get(message.id)\n ?.map(uiMessageToDataPart)\n : undefined) ?? [];\n\n const timing = metadata.messageTiming?.[message.id ?? \"\"];\n\n return {\n role: \"assistant\",\n id: message.id,\n content: [\n ...withAudioTranscript(\n contentToParts(message.content),\n message.additional_kwargs,\n ),\n ...toolCallParts,\n ...uiDataParts,\n ],\n metadata: {\n custom: getCustomMetadata(message.additional_kwargs),\n ...(timing && { timing }),\n },\n ...(assistantStatus && { status: assistantStatus }),\n };\n }\n\n case \"tool\":\n return {\n role: \"tool\",\n // `joinExternalMessages` only checks the name against the tool call\n // when it is non-null, so an empty name manufactures a mismatch.\n toolName: message.name || undefined,\n toolCallId: message.tool_call_id ?? \"\",\n result: message.content,\n artifact: message.artifact,\n isError: message.status === \"error\",\n };\n\n default:\n return {\n role: \"system\",\n id: message.id,\n content: [\n {\n type: \"text\",\n text:\n typeof message.content === \"string\"\n ? message.content\n : (JSON.stringify(message.content) ?? \"\"),\n },\n ],\n };\n }\n};\n\nexport { getMessageContent } from \"./converter\";\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStreamRuntime.d.ts","names":[],"sources":["../src/useStreamRuntime.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"useStreamRuntime.d.ts","names":[],"sources":["../src/useStreamRuntime.ts"],"mappings":";;;;cAoDa,2BACX,WAAW;;kBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;cAqjBA,mBAAoB,YAAY,yDAAuB"}
|
package/dist/useStreamRuntime.js
CHANGED
|
@@ -8,7 +8,7 @@ import { useLangChainStreamingTiming } from "./streamingTiming.js";
|
|
|
8
8
|
import { createCloudThreadListAdapterCreateFallback, createToolCallCancellationStub, scanPendingToolCalls } from "@assistant-ui/core/internal";
|
|
9
9
|
import { useAui, useAuiState } from "@assistant-ui/store";
|
|
10
10
|
import { useCloudThreadListAdapter, useExternalMessageConverter, useExternalStoreRuntime, useRemoteThreadListRuntime } from "@assistant-ui/core/react";
|
|
11
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
11
|
+
import { useCallback, useEffect, useInsertionEffect, useMemo, useRef, useState } from "react";
|
|
12
12
|
import { generateId, getExternalStoreMessages, pickExternalStoreSharedOptions } from "@assistant-ui/core";
|
|
13
13
|
import { STREAM_CONTROLLER, useChannel, useStream } from "@langchain/react";
|
|
14
14
|
//#region src/useStreamRuntime.ts
|
|
@@ -69,7 +69,9 @@ const useStreamThreadRuntime = (options) => {
|
|
|
69
69
|
isRunning: effectiveIsRunning
|
|
70
70
|
});
|
|
71
71
|
const streamRef = useRef(stream);
|
|
72
|
-
|
|
72
|
+
useInsertionEffect(() => {
|
|
73
|
+
streamRef.current = stream;
|
|
74
|
+
}, [stream]);
|
|
73
75
|
const activeRunConfigRef = useRef(void 0);
|
|
74
76
|
const runConfigByMessageIdRef = useRef(/* @__PURE__ */ new Map());
|
|
75
77
|
const activeThreadIdRef = useRef(externalId);
|
|
@@ -106,9 +108,13 @@ const useStreamThreadRuntime = (options) => {
|
|
|
106
108
|
}
|
|
107
109
|
}, [stream.messages]);
|
|
108
110
|
const visibleMessagesRef = useRef(visibleMessages);
|
|
109
|
-
|
|
111
|
+
useInsertionEffect(() => {
|
|
112
|
+
visibleMessagesRef.current = visibleMessages;
|
|
113
|
+
}, [visibleMessages]);
|
|
110
114
|
const threadMessagesRef = useRef(threadMessages);
|
|
111
|
-
|
|
115
|
+
useInsertionEffect(() => {
|
|
116
|
+
threadMessagesRef.current = threadMessages;
|
|
117
|
+
}, [threadMessages]);
|
|
112
118
|
const stagedMessagesRef = useRef(/* @__PURE__ */ new Map());
|
|
113
119
|
const stagedBaseMessagesRef = useRef(null);
|
|
114
120
|
useEffect(() => {
|
|
@@ -338,8 +344,6 @@ const useStreamThreadRuntime = (options) => {
|
|
|
338
344
|
*/
|
|
339
345
|
const useStreamRuntime = (rawOptions) => {
|
|
340
346
|
const { cloud, unstable_threadListAdapter, create, delete: deleteFn, onThreadIdChange, ...options } = rawOptions;
|
|
341
|
-
const optionsRef = useRef(options);
|
|
342
|
-
optionsRef.current = options;
|
|
343
347
|
const aui = useAui();
|
|
344
348
|
const cloudAdapter = useCloudThreadListAdapter({
|
|
345
349
|
cloud,
|
|
@@ -348,7 +352,7 @@ const useStreamRuntime = (rawOptions) => {
|
|
|
348
352
|
});
|
|
349
353
|
return useRemoteThreadListRuntime({
|
|
350
354
|
runtimeHook: function RuntimeHook() {
|
|
351
|
-
return useStreamThreadRuntime(
|
|
355
|
+
return useStreamThreadRuntime(options);
|
|
352
356
|
},
|
|
353
357
|
adapter: unstable_threadListAdapter ?? cloudAdapter,
|
|
354
358
|
allowNesting: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStreamRuntime.js","names":[],"sources":["../src/useStreamRuntime.ts"],"sourcesContent":["/// <reference types=\"@assistant-ui/core/store\" />\n\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState } 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 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 { Channel } from \"@langchain/react\";\nimport type {\n LangChainBaseMessage,\n LangChainToolCall,\n UIMessage,\n UseStreamRuntimeOptions,\n} from \"./types\";\nimport { groupUIMessagesByParent } from \"./converter\";\nexport { groupUIMessagesByParent } from \"./converter\";\nimport {\n convertLangChainBaseMessage,\n getMessageContent,\n getMessageType,\n} from \"./convertMessages\";\nimport { foldUIUpdates, mergeUIMessages } from \"./uiMessages\";\nimport { langChainExtras } from \"./runtimeExtras\";\nimport { resolveForkCheckpoint } from \"./resolveForkCheckpoint\";\nimport { useLangChainStreamingTiming } from \"./streamingTiming\";\n\nconst UI_CUSTOM_CHANNELS: readonly Channel[] = [\"custom\"];\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 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 uiStateValue = stream.values[uiStateKey];\n\n const customEvents = useChannel(stream, UI_CUSTOM_CHANNELS);\n const liveUiMessages = useMemo(\n () => foldUIUpdates(customEvents),\n [customEvents],\n );\n\n const mergedUiMessages = useMemo(\n () => mergeUIMessages(liveUiMessages, uiStateValue),\n [liveUiMessages, uiStateValue],\n );\n\n const visibleMessages =\n stagedMessages ?? (stream.messages as LangChainBaseMessage[]);\n\n const messageTiming = useLangChainStreamingTiming(\n visibleMessages,\n effectiveIsRunning,\n );\n\n const convertWithUI = useMemo<\n useExternalMessageConverter.Callback<LangChainBaseMessage>\n >(() => {\n const uiMessagesByParent =\n groupUIMessagesByParent<UIMessage>(mergedUiMessages);\n return (message, metadata) =>\n convertLangChainBaseMessage(message, {\n ...metadata,\n uiMessagesByParent,\n messageTiming,\n });\n }, [mergedUiMessages, messageTiming]);\n\n const threadMessages = useExternalMessageConverter({\n callback: convertWithUI,\n messages: visibleMessages,\n isRunning: effectiveIsRunning,\n });\n\n const streamRef = useRef(stream);\n streamRef.current = 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 visibleMessagesRef.current = visibleMessages;\n\n const threadMessagesRef = useRef(threadMessages);\n threadMessagesRef.current = threadMessages;\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 }\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 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 if (!parentId || !stagedMessagesRef.current.has(parentId)) return null;\n\n const staged: LangChainBaseMessage[] = [];\n for (const message of visibleMessagesRef.current) {\n if (message.id && stagedMessagesRef.current.has(message.id)) {\n staged.push(stagedMessagesRef.current.get(message.id)!.message);\n }\n if (message.id === parentId) break;\n }\n\n return {\n messages: staged,\n runConfig: stagedMessagesRef.current.get(parentId)!.runConfig,\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: threadMessages,\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 ).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 await streamRef.current.submit(\n {\n [messagesKey]: [\n ...cancellations,\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 } catch (error) {\n removeStagedMessage(stagedMessageId);\n throw error;\n }\n },\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 const promotedIds = new Set<string>();\n for (const message of stagedRun.messages) {\n if (!message.id) 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 stream.submit(\n {\n [messagesKey]: stagedRun.messages.map((message) => ({\n id: message.id,\n type: \"human\",\n content: message.content,\n })),\n },\n runConfigToSubmitOptions(runConfig),\n );\n return;\n }\n\n const threadId = externalId;\n if (!threadId || parentId == null) return;\n const s = streamRef.current;\n const checkpointId = await resolveForkCheckpoint(\n s.client,\n threadId,\n s.messages as readonly LangChainBaseMessage[],\n parentId,\n config.sourceId,\n s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),\n messagesKey,\n );\n if (!checkpointId) return;\n setActiveRunConfig(config.runConfig);\n await s.submit(null, {\n forkFrom: checkpointId,\n ...runConfigToSubmitOptions(config.runConfig),\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 checkpointId = await resolveForkCheckpoint(\n s.client,\n threadId,\n s.messages as readonly LangChainBaseMessage[],\n message.parentId,\n message.sourceId,\n s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),\n messagesKey,\n );\n if (!checkpointId) return;\n const content = getMessageContent(message);\n setActiveRunConfig(message.runConfig);\n await s.submit(\n { [messagesKey]: [{ type: \"human\", content }] },\n {\n forkFrom: checkpointId,\n ...runConfigToSubmitOptions(message.runConfig),\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 optionsRef = useRef(options);\n optionsRef.current = options;\n\n const aui = useAui();\n const cloudAdapter = useCloudThreadListAdapter({\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(optionsRef.current);\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\" />\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 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 { Channel } from \"@langchain/react\";\nimport type {\n LangChainBaseMessage,\n LangChainToolCall,\n UIMessage,\n UseStreamRuntimeOptions,\n} from \"./types\";\nimport { groupUIMessagesByParent } from \"./converter\";\nexport { groupUIMessagesByParent } from \"./converter\";\nimport {\n convertLangChainBaseMessage,\n getMessageContent,\n getMessageType,\n} from \"./convertMessages\";\nimport { foldUIUpdates, mergeUIMessages } from \"./uiMessages\";\nimport { langChainExtras } from \"./runtimeExtras\";\nimport { resolveForkCheckpoint } from \"./resolveForkCheckpoint\";\nimport { useLangChainStreamingTiming } from \"./streamingTiming\";\n\nconst UI_CUSTOM_CHANNELS: readonly Channel[] = [\"custom\"];\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 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 uiStateValue = stream.values[uiStateKey];\n\n const customEvents = useChannel(stream, UI_CUSTOM_CHANNELS);\n const liveUiMessages = useMemo(\n () => foldUIUpdates(customEvents),\n [customEvents],\n );\n\n const mergedUiMessages = useMemo(\n () => mergeUIMessages(liveUiMessages, uiStateValue),\n [liveUiMessages, uiStateValue],\n );\n\n const visibleMessages =\n stagedMessages ?? (stream.messages as LangChainBaseMessage[]);\n\n const messageTiming = useLangChainStreamingTiming(\n visibleMessages,\n effectiveIsRunning,\n );\n\n const convertWithUI = useMemo<\n useExternalMessageConverter.Callback<LangChainBaseMessage>\n >(() => {\n const uiMessagesByParent =\n groupUIMessagesByParent<UIMessage>(mergedUiMessages);\n return (message, metadata) =>\n convertLangChainBaseMessage(message, {\n ...metadata,\n uiMessagesByParent,\n messageTiming,\n });\n }, [mergedUiMessages, messageTiming]);\n\n const threadMessages = useExternalMessageConverter({\n callback: convertWithUI,\n messages: visibleMessages,\n isRunning: effectiveIsRunning,\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(threadMessages);\n useInsertionEffect(() => {\n threadMessagesRef.current = threadMessages;\n }, [threadMessages]);\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 }\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 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 if (!parentId || !stagedMessagesRef.current.has(parentId)) return null;\n\n const staged: LangChainBaseMessage[] = [];\n for (const message of visibleMessagesRef.current) {\n if (message.id && stagedMessagesRef.current.has(message.id)) {\n staged.push(stagedMessagesRef.current.get(message.id)!.message);\n }\n if (message.id === parentId) break;\n }\n\n return {\n messages: staged,\n runConfig: stagedMessagesRef.current.get(parentId)!.runConfig,\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: threadMessages,\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 ).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 await streamRef.current.submit(\n {\n [messagesKey]: [\n ...cancellations,\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 } catch (error) {\n removeStagedMessage(stagedMessageId);\n throw error;\n }\n },\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 const promotedIds = new Set<string>();\n for (const message of stagedRun.messages) {\n if (!message.id) 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 stream.submit(\n {\n [messagesKey]: stagedRun.messages.map((message) => ({\n id: message.id,\n type: \"human\",\n content: message.content,\n })),\n },\n runConfigToSubmitOptions(runConfig),\n );\n return;\n }\n\n const threadId = externalId;\n if (!threadId || parentId == null) return;\n const s = streamRef.current;\n const checkpointId = await resolveForkCheckpoint(\n s.client,\n threadId,\n s.messages as readonly LangChainBaseMessage[],\n parentId,\n config.sourceId,\n s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),\n messagesKey,\n );\n if (!checkpointId) return;\n setActiveRunConfig(config.runConfig);\n await s.submit(null, {\n forkFrom: checkpointId,\n ...runConfigToSubmitOptions(config.runConfig),\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 checkpointId = await resolveForkCheckpoint(\n s.client,\n threadId,\n s.messages as readonly LangChainBaseMessage[],\n message.parentId,\n message.sourceId,\n s[STREAM_CONTROLLER]?.messageMetadataStore?.getSnapshot?.(),\n messagesKey,\n );\n if (!checkpointId) return;\n const content = getMessageContent(message);\n setActiveRunConfig(message.runConfig);\n await s.submit(\n { [messagesKey]: [{ type: \"human\", content }] },\n {\n forkFrom: checkpointId,\n ...runConfigToSubmitOptions(message.runConfig),\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 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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"}
|
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.29",
|
|
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.17",
|
|
44
|
+
"@assistant-ui/store": "^0.3.12",
|
|
45
45
|
"assistant-cloud": "*",
|
|
46
|
-
"assistant-stream": "^0.3.
|
|
46
|
+
"assistant-stream": "^0.3.41"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"@langchain/react": "^1.0.2",
|
|
@@ -59,11 +59,11 @@
|
|
|
59
59
|
}
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
62
|
+
"@assistant-ui/x-buildutils": "0.0.25",
|
|
63
63
|
"@langchain/core": "^1.2.9",
|
|
64
|
-
"@langchain/langgraph-sdk": "^1.
|
|
65
|
-
"@langchain/react": "^1.0.
|
|
66
|
-
"@testing-library/react": "^16.3.
|
|
64
|
+
"@langchain/langgraph-sdk": "^1.10.0",
|
|
65
|
+
"@langchain/react": "^1.0.33",
|
|
66
|
+
"@testing-library/react": "^16.3.3",
|
|
67
67
|
"@types/react": "^19.2.18",
|
|
68
68
|
"jsdom": "^30.0.1",
|
|
69
69
|
"react": "^19.2.8",
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
2
|
import type { AppendMessage } from "@assistant-ui/core";
|
|
3
3
|
import { convertExternalMessages } from "@assistant-ui/core/react";
|
|
4
4
|
import {
|
|
5
5
|
convertLangChainBaseMessage,
|
|
6
6
|
getMessageContent,
|
|
7
|
+
getMessageType,
|
|
7
8
|
} from "./convertMessages";
|
|
8
9
|
import type { LangChainBaseMessage, UIMessage } from "./types";
|
|
9
10
|
|
|
@@ -1047,3 +1048,119 @@ describe("convertLangChainBaseMessage tool messages", () => {
|
|
|
1047
1048
|
).toThrow(/does not match existing tool call/);
|
|
1048
1049
|
});
|
|
1049
1050
|
});
|
|
1051
|
+
|
|
1052
|
+
describe("convertLangChainBaseMessage malformed messages", () => {
|
|
1053
|
+
it("reports an unknown type for a message without _getType or type", () => {
|
|
1054
|
+
const message = { id: "msg-3", content: "hello" } as LangChainBaseMessage;
|
|
1055
|
+
|
|
1056
|
+
expect(getMessageType(message)).toBe("unknown");
|
|
1057
|
+
expect(convertLangChainBaseMessage(message, {})).toEqual({
|
|
1058
|
+
role: "system",
|
|
1059
|
+
id: "msg-3",
|
|
1060
|
+
content: [{ type: "text", text: "hello" }],
|
|
1061
|
+
});
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
it("converts a human message with null content to empty content", () => {
|
|
1065
|
+
expect(
|
|
1066
|
+
contentOf(convertLangChainBaseMessage(humanMessage(null), {})),
|
|
1067
|
+
).toEqual([]);
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
it("keeps tool calls when an ai message carries object content", () => {
|
|
1071
|
+
const result = convertLangChainBaseMessage(
|
|
1072
|
+
{
|
|
1073
|
+
...aiMessage({ text: "not an array" }),
|
|
1074
|
+
tool_calls: [{ id: "call-1", name: "lookup", args: { q: "x" } }],
|
|
1075
|
+
},
|
|
1076
|
+
{},
|
|
1077
|
+
);
|
|
1078
|
+
|
|
1079
|
+
expect(contentOf(result)).toEqual([
|
|
1080
|
+
{
|
|
1081
|
+
type: "tool-call",
|
|
1082
|
+
toolCallId: "call-1",
|
|
1083
|
+
toolName: "lookup",
|
|
1084
|
+
args: { q: "x" },
|
|
1085
|
+
argsText: '{"q":"x"}',
|
|
1086
|
+
},
|
|
1087
|
+
]);
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
it("converts a system message with null content to empty text", () => {
|
|
1091
|
+
const result = convertLangChainBaseMessage(
|
|
1092
|
+
{ _getType: () => "system", id: "msg-4", content: null },
|
|
1093
|
+
{},
|
|
1094
|
+
);
|
|
1095
|
+
|
|
1096
|
+
expect(contentOf(result)).toEqual([{ type: "text", text: "" }]);
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
it("renders a message without a type or content as empty system text", () => {
|
|
1100
|
+
const result = convertLangChainBaseMessage(
|
|
1101
|
+
{ id: "msg-6" } as LangChainBaseMessage,
|
|
1102
|
+
{},
|
|
1103
|
+
);
|
|
1104
|
+
|
|
1105
|
+
expect(contentOf(result)).toEqual([{ type: "text", text: "" }]);
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1108
|
+
it("skips null entries inside a content array", () => {
|
|
1109
|
+
const result = convertLangChainBaseMessage(
|
|
1110
|
+
humanMessage([null, { type: "text", text: "kept" }, undefined]),
|
|
1111
|
+
{},
|
|
1112
|
+
);
|
|
1113
|
+
|
|
1114
|
+
expect(contentOf(result)).toEqual([{ type: "text", text: "kept" }]);
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
it("skips null entries when collecting system text", () => {
|
|
1118
|
+
const result = convertLangChainBaseMessage(
|
|
1119
|
+
{
|
|
1120
|
+
_getType: () => "system",
|
|
1121
|
+
id: "msg-7",
|
|
1122
|
+
content: [null, { type: "text", text: "kept" }],
|
|
1123
|
+
},
|
|
1124
|
+
{},
|
|
1125
|
+
);
|
|
1126
|
+
|
|
1127
|
+
expect(contentOf(result)).toEqual([{ type: "text", text: "kept" }]);
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
it("converts a system message with object content to empty text", () => {
|
|
1131
|
+
const result = convertLangChainBaseMessage(
|
|
1132
|
+
{ _getType: () => "system", id: "msg-5", content: { text: "x" } },
|
|
1133
|
+
{},
|
|
1134
|
+
);
|
|
1135
|
+
|
|
1136
|
+
expect(contentOf(result)).toEqual([{ type: "text", text: "" }]);
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
it("stays silent about non-array content outside development", () => {
|
|
1140
|
+
vi.stubEnv("NODE_ENV", "test");
|
|
1141
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
1142
|
+
try {
|
|
1143
|
+
convertLangChainBaseMessage(humanMessage(true), {});
|
|
1144
|
+
expect(warn).not.toHaveBeenCalled();
|
|
1145
|
+
} finally {
|
|
1146
|
+
warn.mockRestore();
|
|
1147
|
+
vi.unstubAllEnvs();
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
it("warns once in development about non-array content", () => {
|
|
1152
|
+
vi.stubEnv("NODE_ENV", "development");
|
|
1153
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
1154
|
+
try {
|
|
1155
|
+
convertLangChainBaseMessage(humanMessage(42), {});
|
|
1156
|
+
convertLangChainBaseMessage(humanMessage(42), {});
|
|
1157
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
1158
|
+
expect(warn).toHaveBeenCalledWith(
|
|
1159
|
+
"Ignoring message content that is neither a string nor an array: number",
|
|
1160
|
+
);
|
|
1161
|
+
} finally {
|
|
1162
|
+
warn.mockRestore();
|
|
1163
|
+
vi.unstubAllEnvs();
|
|
1164
|
+
}
|
|
1165
|
+
});
|
|
1166
|
+
});
|
package/src/convertMessages.ts
CHANGED
|
@@ -21,27 +21,52 @@ type LangChainMessageConverterMetadata =
|
|
|
21
21
|
messageTiming?: Record<string, MessageTiming>;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
const warnedMalformedMessages = new Set<string>();
|
|
25
|
+
const warnOnceInDevelopment = (message: string) => {
|
|
26
|
+
if (
|
|
27
|
+
typeof process === "undefined" ||
|
|
28
|
+
process?.env?.NODE_ENV !== "development"
|
|
29
|
+
)
|
|
30
|
+
return;
|
|
31
|
+
if (warnedMalformedMessages.has(message)) return;
|
|
32
|
+
warnedMalformedMessages.add(message);
|
|
33
|
+
console.warn(message);
|
|
34
|
+
};
|
|
35
|
+
|
|
24
36
|
export const getMessageType = (message: LangChainBaseMessage): string => {
|
|
25
37
|
if (typeof message._getType === "function") return message._getType();
|
|
26
38
|
if ("type" in message)
|
|
27
39
|
return (message as Record<string, unknown>).type as string;
|
|
28
|
-
|
|
40
|
+
warnOnceInDevelopment(
|
|
41
|
+
"Cannot determine message type; rendering the message as system text",
|
|
42
|
+
);
|
|
43
|
+
return "unknown";
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const contentBlocks = (content: unknown): readonly LangChainContentBlock[] => {
|
|
47
|
+
if (content == null) return [];
|
|
48
|
+
if (Array.isArray(content))
|
|
49
|
+
return content.filter(
|
|
50
|
+
(block) => typeof block === "object" && block !== null,
|
|
51
|
+
);
|
|
52
|
+
warnOnceInDevelopment(
|
|
53
|
+
`Ignoring message content that is neither a string nor an array: ${typeof content}`,
|
|
54
|
+
);
|
|
55
|
+
return [];
|
|
29
56
|
};
|
|
30
57
|
|
|
31
58
|
const contentToParts = (content: unknown) => {
|
|
32
59
|
if (typeof content === "string")
|
|
33
60
|
return [{ type: "text" as const, text: content }];
|
|
34
61
|
|
|
35
|
-
|
|
36
|
-
return parts
|
|
62
|
+
return contentBlocks(content)
|
|
37
63
|
.map(convertLangChainContentBlock)
|
|
38
64
|
.filter((part) => part !== null && part !== undefined);
|
|
39
65
|
};
|
|
40
66
|
|
|
41
67
|
const getStringContent = (content: unknown): string => {
|
|
42
68
|
if (typeof content === "string") return content;
|
|
43
|
-
|
|
44
|
-
return parts
|
|
69
|
+
return contentBlocks(content)
|
|
45
70
|
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
46
71
|
.map((c) => c.text)
|
|
47
72
|
.join("");
|
|
@@ -137,7 +162,7 @@ export const convertLangChainBaseMessage = (
|
|
|
137
162
|
text:
|
|
138
163
|
typeof message.content === "string"
|
|
139
164
|
? message.content
|
|
140
|
-
: JSON.stringify(message.content),
|
|
165
|
+
: (JSON.stringify(message.content) ?? ""),
|
|
141
166
|
},
|
|
142
167
|
],
|
|
143
168
|
};
|
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
} from "@assistant-ui/core";
|
|
11
11
|
import { useAui } from "@assistant-ui/store";
|
|
12
12
|
import type { LangChainBaseMessage } from "./types";
|
|
13
|
-
import type
|
|
13
|
+
import { startTransition, Suspense, type ReactNode } from "react";
|
|
14
14
|
import {
|
|
15
15
|
useLangChainRespond,
|
|
16
16
|
useLangChainRespondAll,
|
|
@@ -898,3 +898,76 @@ describe("useStreamRuntime staged messages", () => {
|
|
|
898
898
|
});
|
|
899
899
|
});
|
|
900
900
|
});
|
|
901
|
+
|
|
902
|
+
describe("useStreamRuntime committed refs", () => {
|
|
903
|
+
it("submits through the committed stream after an abandoned render", async () => {
|
|
904
|
+
const streamA = createMockStream();
|
|
905
|
+
const streamB = createMockStream();
|
|
906
|
+
const adapter = makeThreadListAdapter();
|
|
907
|
+
mockUseStream.mockImplementation((options: { apiUrl: string }) =>
|
|
908
|
+
options.apiUrl === "/api/b" ? streamB : streamA,
|
|
909
|
+
);
|
|
910
|
+
const host = renderHook(() =>
|
|
911
|
+
useStreamRuntime({
|
|
912
|
+
apiUrl: "/api/a",
|
|
913
|
+
unstable_threadListAdapter: adapter,
|
|
914
|
+
} as never),
|
|
915
|
+
);
|
|
916
|
+
|
|
917
|
+
const pending = new Promise<never>(() => {});
|
|
918
|
+
let blocked = false;
|
|
919
|
+
const interruptedRender = vi.fn();
|
|
920
|
+
const Blocker = () => {
|
|
921
|
+
if (blocked) {
|
|
922
|
+
interruptedRender();
|
|
923
|
+
throw pending;
|
|
924
|
+
}
|
|
925
|
+
return null;
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
const capture: { runtime: AssistantRuntime | null } = { runtime: null };
|
|
929
|
+
const Nested = ({ apiUrl }: { apiUrl: string }) => {
|
|
930
|
+
capture.runtime = useStreamRuntime({
|
|
931
|
+
apiUrl,
|
|
932
|
+
unstable_threadListAdapter: adapter,
|
|
933
|
+
} as never);
|
|
934
|
+
return null;
|
|
935
|
+
};
|
|
936
|
+
const Tree = ({ apiUrl }: { apiUrl: string }) => (
|
|
937
|
+
<AssistantRuntimeProvider runtime={host.result.current}>
|
|
938
|
+
<Suspense fallback={null}>
|
|
939
|
+
<Nested apiUrl={apiUrl} />
|
|
940
|
+
<Blocker />
|
|
941
|
+
</Suspense>
|
|
942
|
+
</AssistantRuntimeProvider>
|
|
943
|
+
);
|
|
944
|
+
|
|
945
|
+
const view = render(<Tree apiUrl="/api/a" />);
|
|
946
|
+
expect(capture.runtime).not.toBeNull();
|
|
947
|
+
|
|
948
|
+
act(() => {
|
|
949
|
+
blocked = true;
|
|
950
|
+
startTransition(() => view.rerender(<Tree apiUrl="/api/b" />));
|
|
951
|
+
});
|
|
952
|
+
expect(interruptedRender).toHaveBeenCalled();
|
|
953
|
+
|
|
954
|
+
await act(async () => {
|
|
955
|
+
await capture.runtime!.thread.append("hello");
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
expect(streamA.submit).toHaveBeenCalledOnce();
|
|
959
|
+
expect(streamB.submit).not.toHaveBeenCalled();
|
|
960
|
+
|
|
961
|
+
await act(async () => {
|
|
962
|
+
blocked = false;
|
|
963
|
+
view.rerender(<Tree apiUrl="/api/b" />);
|
|
964
|
+
});
|
|
965
|
+
await act(async () => {
|
|
966
|
+
await capture.runtime!.thread.append("second");
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
expect(streamB.submit).toHaveBeenCalledOnce();
|
|
970
|
+
view.unmount();
|
|
971
|
+
host.unmount();
|
|
972
|
+
});
|
|
973
|
+
});
|
package/src/useStreamRuntime.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
/// <reference types="@assistant-ui/core/store" />
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useInsertionEffect,
|
|
8
|
+
useMemo,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
} from "react";
|
|
5
12
|
import type { AppendMessage, ToolExecutionStatus } from "@assistant-ui/core";
|
|
6
13
|
import {
|
|
7
14
|
generateId,
|
|
@@ -192,7 +199,9 @@ const useStreamThreadRuntime = (
|
|
|
192
199
|
});
|
|
193
200
|
|
|
194
201
|
const streamRef = useRef(stream);
|
|
195
|
-
|
|
202
|
+
useInsertionEffect(() => {
|
|
203
|
+
streamRef.current = stream;
|
|
204
|
+
}, [stream]);
|
|
196
205
|
|
|
197
206
|
const activeRunConfigRef = useRef<
|
|
198
207
|
NormalizedRunConfigOptions["config"] | undefined
|
|
@@ -256,10 +265,14 @@ const useStreamThreadRuntime = (
|
|
|
256
265
|
}, [stream.messages]);
|
|
257
266
|
|
|
258
267
|
const visibleMessagesRef = useRef(visibleMessages);
|
|
259
|
-
|
|
268
|
+
useInsertionEffect(() => {
|
|
269
|
+
visibleMessagesRef.current = visibleMessages;
|
|
270
|
+
}, [visibleMessages]);
|
|
260
271
|
|
|
261
272
|
const threadMessagesRef = useRef(threadMessages);
|
|
262
|
-
|
|
273
|
+
useInsertionEffect(() => {
|
|
274
|
+
threadMessagesRef.current = threadMessages;
|
|
275
|
+
}, [threadMessages]);
|
|
263
276
|
|
|
264
277
|
const stagedMessagesRef = useRef(
|
|
265
278
|
new Map<
|
|
@@ -613,9 +626,6 @@ export const useStreamRuntime = (rawOptions: UseStreamRuntimeOptions) => {
|
|
|
613
626
|
...options
|
|
614
627
|
} = rawOptions;
|
|
615
628
|
|
|
616
|
-
const optionsRef = useRef(options);
|
|
617
|
-
optionsRef.current = options;
|
|
618
|
-
|
|
619
629
|
const aui = useAui();
|
|
620
630
|
const cloudAdapter = useCloudThreadListAdapter({
|
|
621
631
|
cloud,
|
|
@@ -629,7 +639,7 @@ export const useStreamRuntime = (rawOptions: UseStreamRuntimeOptions) => {
|
|
|
629
639
|
|
|
630
640
|
return useRemoteThreadListRuntime({
|
|
631
641
|
runtimeHook: function RuntimeHook() {
|
|
632
|
-
return useStreamThreadRuntime(
|
|
642
|
+
return useStreamThreadRuntime(options);
|
|
633
643
|
},
|
|
634
644
|
adapter,
|
|
635
645
|
allowNesting: true,
|