@assistant-ui/ai-sdk 0.0.11 → 0.0.12
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/converters/convertMessage.d.ts +8 -6
- package/dist/converters/convertMessage.d.ts.map +1 -1
- package/dist/converters/convertMessage.js +48 -22
- package/dist/converters/convertMessage.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.native.d.ts +1 -1
- package/dist/index.native.d.ts.map +1 -1
- package/dist/index.native.js +1 -1
- package/dist/model-context/injectInteractableContext.d.ts +3 -0
- package/dist/model-context/injectInteractableContext.d.ts.map +1 -1
- package/dist/model-context/injectInteractableContext.js +3 -0
- package/dist/model-context/injectInteractableContext.js.map +1 -1
- package/dist/model-context/injectQuoteContext.d.ts +1 -0
- package/dist/model-context/injectQuoteContext.d.ts.map +1 -1
- package/dist/model-context/injectQuoteContext.js +1 -0
- package/dist/model-context/injectQuoteContext.js.map +1 -1
- package/dist/runtime/AISDKChat.js +1 -1
- package/dist/runtime/AISDKThreads.d.ts +5 -0
- package/dist/runtime/AISDKThreads.d.ts.map +1 -1
- package/dist/runtime/AISDKThreads.js +37 -4
- package/dist/runtime/AISDKThreads.js.map +1 -1
- package/dist/runtime/DynamicChatTransport.d.ts +49 -0
- package/dist/runtime/DynamicChatTransport.d.ts.map +1 -0
- package/dist/runtime/DynamicChatTransport.js +147 -0
- package/dist/runtime/DynamicChatTransport.js.map +1 -0
- package/dist/runtime/getResumableAdapter.d.ts +5 -0
- package/dist/runtime/getResumableAdapter.d.ts.map +1 -0
- package/dist/runtime/getResumableAdapter.js +12 -0
- package/dist/runtime/getResumableAdapter.js.map +1 -0
- package/dist/runtime/sdkIdentity.js +1 -1
- package/dist/runtime/toolHistoryCodec.d.ts +20 -0
- package/dist/runtime/toolHistoryCodec.d.ts.map +1 -0
- package/dist/runtime/toolHistoryCodec.js +107 -0
- package/dist/runtime/toolHistoryCodec.js.map +1 -0
- package/dist/runtime/useAISDKRuntime.d.ts +4 -1
- package/dist/runtime/useAISDKRuntime.d.ts.map +1 -1
- package/dist/runtime/useAISDKRuntime.js +154 -155
- package/dist/runtime/useAISDKRuntime.js.map +1 -1
- package/dist/runtime/useChatRuntime.d.ts +14 -2
- package/dist/runtime/useChatRuntime.d.ts.map +1 -1
- package/dist/runtime/useChatRuntime.js +12 -3
- package/dist/runtime/useChatRuntime.js.map +1 -1
- package/dist/runtime/useChatThread.d.ts +3 -2
- package/dist/runtime/useChatThread.d.ts.map +1 -1
- package/dist/runtime/useChatThread.js +56 -36
- package/dist/runtime/useChatThread.js.map +1 -1
- package/dist/runtime/useDynamicChatTransport.d.ts +4 -0
- package/dist/runtime/useDynamicChatTransport.d.ts.map +1 -0
- package/dist/runtime/useDynamicChatTransport.js +64 -0
- package/dist/runtime/useDynamicChatTransport.js.map +1 -0
- package/dist/runtime/useExternalHistory.d.ts.map +1 -1
- package/dist/runtime/useExternalHistory.js +11 -109
- package/dist/runtime/useExternalHistory.js.map +1 -1
- package/dist/runtime/useResourceCleanup.js +1 -1
- package/dist/runtime/useStreamingTiming.js +2 -2
- package/dist/runtime/useStreamingTiming.js.map +1 -1
- package/dist/tools/generativeTools.d.ts +2 -1
- package/dist/tools/generativeTools.d.ts.map +1 -1
- package/dist/tools/generativeTools.js +5 -5
- package/dist/tools/generativeTools.js.map +1 -1
- package/dist/usage.js +1 -1
- package/dist/utils/sliceMessagesUntil.d.ts.map +1 -1
- package/dist/utils/sliceMessagesUntil.js +1 -2
- package/dist/utils/sliceMessagesUntil.js.map +1 -1
- package/package.json +11 -10
- package/src/converters/convertMessage.test.ts +120 -2
- package/src/converters/convertMessage.tool-args-status.test.tsx +185 -0
- package/src/converters/convertMessage.ts +60 -19
- package/src/converters/toCreateMessage.test.ts +27 -0
- package/src/index.native.ts +1 -1
- package/src/index.ts +1 -1
- package/src/model-context/injectInteractableContext.ts +3 -0
- package/src/model-context/injectQuoteContext.ts +1 -0
- package/src/runtime/AISDKChat.integration.test.tsx +57 -2
- package/src/runtime/AISDKThreads.cloud.test.ts +3 -0
- package/src/runtime/AISDKThreads.test.ts +181 -0
- package/src/runtime/AISDKThreads.ts +31 -4
- package/src/runtime/DynamicChatTransport.test.ts +203 -0
- package/src/runtime/DynamicChatTransport.ts +273 -0
- package/src/runtime/__tests__/controlled-transport.ts +3 -0
- package/src/runtime/getResumableAdapter.ts +16 -0
- package/src/runtime/toolHistoryCodec.test.ts +161 -0
- package/src/runtime/toolHistoryCodec.ts +207 -0
- package/src/runtime/useAISDKRuntime.approval.test.tsx +12 -0
- package/src/runtime/useAISDKRuntime.reload.test.tsx +219 -0
- package/src/runtime/useAISDKRuntime.test.ts +538 -5
- package/src/runtime/useAISDKRuntime.ts +161 -51
- package/src/runtime/useChatRuntime.integration.test.tsx +319 -4
- package/src/runtime/useChatRuntime.local-storage.test.tsx +123 -0
- package/src/runtime/useChatRuntime.test.ts +107 -1
- package/src/runtime/useChatRuntime.ts +26 -4
- package/src/runtime/useChatThread.binding.test.tsx +143 -0
- package/src/runtime/useChatThread.ts +98 -81
- package/src/runtime/useDynamicChatTransport.ts +26 -0
- package/src/runtime/useExternalHistory.test.ts +111 -0
- package/src/runtime/useExternalHistory.ts +14 -210
- package/src/runtime/useStreamingTiming.ts +2 -2
- package/src/tools/generativeTools.test.ts +1 -1
- package/src/tools/generativeTools.ts +5 -6
- package/src/utils/sliceMessagesUntil.test.ts +2 -6
- package/src/utils/sliceMessagesUntil.ts +1 -5
- package/dist/converters/modelContentEnvelope.d.ts +0 -14
- package/dist/converters/modelContentEnvelope.d.ts.map +0 -1
- package/dist/converters/modelContentEnvelope.js +0 -22
- package/dist/converters/modelContentEnvelope.js.map +0 -1
- package/dist/converters/toolOutputConversion.d.ts +0 -26
- package/dist/converters/toolOutputConversion.d.ts.map +0 -1
- package/dist/converters/toolOutputConversion.js +0 -31
- package/dist/converters/toolOutputConversion.js.map +0 -1
- package/dist/tools/frontendTools.d.ts +0 -30
- package/dist/tools/frontendTools.d.ts.map +0 -1
- package/dist/tools/frontendTools.js +0 -33
- package/dist/tools/frontendTools.js.map +0 -1
- package/src/converters/modelContentEnvelope.ts +0 -41
- package/src/converters/toolOutputConversion.ts +0 -26
- package/src/tools/frontendTools.test.ts +0 -205
- package/src/tools/frontendTools.ts +0 -83
|
@@ -2,37 +2,14 @@
|
|
|
2
2
|
import { useAISDKRuntime } from "./useAISDKRuntime.js";
|
|
3
3
|
import { AssistantChatTransport } from "../transport/AssistantChatTransport.js";
|
|
4
4
|
import { useResourceCleanup } from "./useResourceCleanup.js";
|
|
5
|
+
import { getResumableAdapter } from "./getResumableAdapter.js";
|
|
6
|
+
import { DynamicChatTransport, getResumedStreamIds } from "./DynamicChatTransport.js";
|
|
7
|
+
import { useDynamicChatTransport } from "./useDynamicChatTransport.js";
|
|
5
8
|
import { pickExternalStoreSharedOptions } from "@assistant-ui/core";
|
|
6
|
-
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "@assistant-ui/tap/react-shim";
|
|
9
|
+
import { useCallback, useEffect, useEffectEvent, useInsertionEffect, useMemo, useRef, useState, useSyncExternalStore } from "@assistant-ui/tap/react-shim";
|
|
7
10
|
import { Chat, useChat } from "@ai-sdk/react";
|
|
8
11
|
//#region src/runtime/useChatThread.ts
|
|
9
|
-
const useDynamicChatTransport = (transport) => {
|
|
10
|
-
const transportRef = useRef(transport);
|
|
11
|
-
useEffect(() => {
|
|
12
|
-
transportRef.current = transport;
|
|
13
|
-
});
|
|
14
|
-
return useMemo(() => new Proxy(transportRef.current, { get(_, prop) {
|
|
15
|
-
const res = transportRef.current[prop];
|
|
16
|
-
return typeof res === "function" ? res.bind(transportRef.current) : res;
|
|
17
|
-
} }), []);
|
|
18
|
-
};
|
|
19
|
-
const getResumableAdapter = (transport) => {
|
|
20
|
-
if (transport instanceof AssistantChatTransport) return transport.getResumableAdapter();
|
|
21
|
-
const candidate = transport.getResumableAdapter;
|
|
22
|
-
if (typeof candidate !== "function") return void 0;
|
|
23
|
-
return candidate.call(transport);
|
|
24
|
-
};
|
|
25
12
|
const getNoPendingStreamId = () => null;
|
|
26
|
-
const resumedStreamIdsByStorage = /* @__PURE__ */ new WeakMap();
|
|
27
|
-
const getResumedStreamIds = (storage) => {
|
|
28
|
-
if (!storage) return /* @__PURE__ */ new Set();
|
|
29
|
-
let resumedStreamIds = resumedStreamIdsByStorage.get(storage);
|
|
30
|
-
if (!resumedStreamIds) {
|
|
31
|
-
resumedStreamIds = /* @__PURE__ */ new Set();
|
|
32
|
-
resumedStreamIdsByStorage.set(storage, resumedStreamIds);
|
|
33
|
-
}
|
|
34
|
-
return resumedStreamIds;
|
|
35
|
-
};
|
|
36
13
|
/**
|
|
37
14
|
* Splits the combined options into the assistant-ui side and the `ChatInit`
|
|
38
15
|
* remainder the AI SDK consumes, so external `Chat` construction forwards the
|
|
@@ -86,8 +63,27 @@ const createChat = (init, callbacksRef) => {
|
|
|
86
63
|
const useChatThread = (options, env) => {
|
|
87
64
|
const { adapters, transport: transportOptions, throttle, toCreateMessage, onResume, onResumeToolCall, onRespondToToolApproval, onResumeError, joinStrategy, messageRepository, unstable_onBranchChange, chatInit: chatOptions } = splitChatThreadOptions(options);
|
|
88
65
|
const { id, isMainThread, getThreadListItem, stopOnClientDestroy = true, hostDestroySignal, chat: externalChat, messageRepositoryInstance } = env;
|
|
89
|
-
const
|
|
90
|
-
const
|
|
66
|
+
const defaultTransport = useMemo(() => new AssistantChatTransport(), []);
|
|
67
|
+
const configuredTransport = transportOptions ?? defaultTransport;
|
|
68
|
+
const transport = useDynamicChatTransport(configuredTransport, externalChat === void 0);
|
|
69
|
+
const transportContextOwner = useMemo(() => ({}), []);
|
|
70
|
+
const getThreadListItemRef = useRef(getThreadListItem);
|
|
71
|
+
useInsertionEffect(() => {
|
|
72
|
+
getThreadListItemRef.current = getThreadListItem;
|
|
73
|
+
}, [getThreadListItem]);
|
|
74
|
+
const getCurrentThreadListItem = useCallback(() => getThreadListItemRef.current(), []);
|
|
75
|
+
const resumableStorage = useMemo(() => transport instanceof DynamicChatTransport ? transport.getCurrentResumableStorage() : getResumableAdapter(transport)?.storage, [transport]);
|
|
76
|
+
const transportBindingRef = useRef(null);
|
|
77
|
+
let initialTransportBinding = null;
|
|
78
|
+
const chatTransport = useMemo(() => transport instanceof DynamicChatTransport ? transport.createThreadProxy(transportContextOwner, () => {
|
|
79
|
+
const binding = transportBindingRef.current ?? initialTransportBinding;
|
|
80
|
+
if (!binding) throw new Error("Chat transport used before runtime setup");
|
|
81
|
+
return binding;
|
|
82
|
+
}) : transport, [
|
|
83
|
+
initialTransportBinding,
|
|
84
|
+
transport,
|
|
85
|
+
transportContextOwner
|
|
86
|
+
]);
|
|
91
87
|
const latestChatOptionsRef = useRef(chatOptions);
|
|
92
88
|
useEffect(() => {
|
|
93
89
|
latestChatOptionsRef.current = chatOptions;
|
|
@@ -95,7 +91,7 @@ const useChatThread = (options, env) => {
|
|
|
95
91
|
const [ownedChat] = useState(() => externalChat ?? createChat({
|
|
96
92
|
...chatOptions,
|
|
97
93
|
id,
|
|
98
|
-
transport
|
|
94
|
+
transport: chatTransport
|
|
99
95
|
}, latestChatOptionsRef));
|
|
100
96
|
const chat = useChat({
|
|
101
97
|
chat: externalChat ?? ownedChat,
|
|
@@ -123,14 +119,36 @@ const useChatThread = (options, env) => {
|
|
|
123
119
|
unstable_hostApprovalOwner: externalChat ?? ownedChat,
|
|
124
120
|
...unstable_onBranchChange && { unstable_onBranchChange }
|
|
125
121
|
});
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
122
|
+
initialTransportBinding = {
|
|
123
|
+
runtime,
|
|
124
|
+
getThreadListItem: getCurrentThreadListItem
|
|
125
|
+
};
|
|
126
|
+
useInsertionEffect(() => {
|
|
127
|
+
transportBindingRef.current = {
|
|
128
|
+
runtime,
|
|
129
|
+
getThreadListItem: getCurrentThreadListItem
|
|
130
|
+
};
|
|
131
|
+
}, [runtime, getCurrentThreadListItem]);
|
|
132
|
+
const registerTransportContext = useEffectEvent((dynamicTransport, chatId) => {
|
|
133
|
+
dynamicTransport.setThreadContext(chatId, transportContextOwner, runtime, getCurrentThreadListItem);
|
|
134
|
+
return () => dynamicTransport.unregisterThread(chatId, transportContextOwner);
|
|
135
|
+
});
|
|
136
|
+
useInsertionEffect(() => {
|
|
137
|
+
if (!(transport instanceof DynamicChatTransport)) return void 0;
|
|
138
|
+
return registerTransportContext(transport, id);
|
|
139
|
+
}, [
|
|
140
|
+
id,
|
|
141
|
+
runtime,
|
|
142
|
+
transport,
|
|
143
|
+
transportContextOwner
|
|
144
|
+
]);
|
|
145
|
+
if (!(transport instanceof DynamicChatTransport) && configuredTransport instanceof AssistantChatTransport) {
|
|
146
|
+
configuredTransport.setRuntime(runtime);
|
|
147
|
+
configuredTransport.__internal_setGetThreadListItem(getCurrentThreadListItem);
|
|
129
148
|
}
|
|
130
149
|
const subscribeToRuntime = useCallback((callback) => runtime.thread.subscribe(callback), [runtime]);
|
|
131
150
|
const getHistoryLoadingSnapshot = useCallback(() => runtime.thread.getState().isLoading, [runtime]);
|
|
132
151
|
const isLoadingHistory = useSyncExternalStore(subscribeToRuntime, getHistoryLoadingSnapshot, getHistoryLoadingSnapshot);
|
|
133
|
-
const resumableStorage = useMemo(() => getResumableAdapter(sourceTransport)?.storage, [sourceTransport]);
|
|
134
152
|
const subscribeToResumableStorage = useCallback((callback_0) => isMainThread ? resumableStorage?.subscribe?.(callback_0, id) ?? (() => {}) : () => {}, [
|
|
135
153
|
id,
|
|
136
154
|
isMainThread,
|
|
@@ -143,12 +161,13 @@ const useChatThread = (options, env) => {
|
|
|
143
161
|
]);
|
|
144
162
|
const pendingStreamId = useSyncExternalStore(subscribeToResumableStorage, getPendingStreamId, getNoPendingStreamId);
|
|
145
163
|
const isChatRunning = chat.status === "submitted" || chat.status === "streaming";
|
|
146
|
-
const
|
|
164
|
+
const staticResumedStreamIds = useMemo(() => getResumedStreamIds(resumableStorage), [resumableStorage]);
|
|
147
165
|
const onResumeErrorRef = useRef(onResumeError);
|
|
148
166
|
useEffect(() => {
|
|
149
167
|
onResumeErrorRef.current = onResumeError;
|
|
150
168
|
});
|
|
151
169
|
useEffect(() => {
|
|
170
|
+
const resumedStreamIds = transport instanceof DynamicChatTransport ? transport.getResumedStreamIds() : staticResumedStreamIds;
|
|
152
171
|
if (!pendingStreamId || resumedStreamIds.has(pendingStreamId)) return;
|
|
153
172
|
if (isChatRunning) {
|
|
154
173
|
resumedStreamIds.add(pendingStreamId);
|
|
@@ -181,7 +200,8 @@ const useChatThread = (options, env) => {
|
|
|
181
200
|
pendingStreamId,
|
|
182
201
|
ownedChat,
|
|
183
202
|
resumableStorage,
|
|
184
|
-
|
|
203
|
+
staticResumedStreamIds,
|
|
204
|
+
transport
|
|
185
205
|
]);
|
|
186
206
|
return runtime;
|
|
187
207
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChatThread.js","names":["Chat","useChat","UIMessage","MessageRepository","pickExternalStoreSharedOptions","AssistantRuntime","ExternalStoreSharedOptions","useAISDKRuntime","AISDKRuntimeAdapter","CustomToCreateMessageFunction","ChatInit","ChatTransport","AssistantChatTransport","InitializableThreadListItem","AssistantChatResumableOptions","ResumableClientStorage","useCallback","useEffect","useMemo","useRef","useState","useSyncExternalStore","useResourceCleanup","ChatThreadOptions","UI_MESSAGE","throttle","adapters","toCreateMessage","onResume","onResumeToolCall","onRespondToToolApproval","onResumeError","error","joinStrategy","messageRepository","unstable_onBranchChange","ChatThreadEnvironment","id","isMainThread","getThreadListItem","stopOnClientDestroy","hostDestroySignal","AbortSignal","chat","messageRepositoryInstance","useDynamicChatTransport","transport","transportRef","current","dynamicTransport","Proxy","get","_","prop","res","bind","getResumableAdapter","candidate","undefined","call","getNoPendingStreamId","resumedStreamIdsByStorage","WeakMap","Set","getResumedStreamIds","storage","resumedStreamIds","set","splitChatThreadOptions","options","isDisabled","_isDisabled","isSendDisabled","_isSendDisabled","unstable_capabilities","_unstable_capabilities","suggestions","_suggestions","chatInit","ChatCallbacks","Pick","requestsByChat","createChat","init","callbacksRef","sendMessages","Symbol","reconnectToStream","onToolCall","arg","onData","onFinish","onError","sendAutomaticallyWhen","useChatThread","env","transportOptions","chatOptions","externalChat","sourceTransport","__internal_clone","latestChatOptionsRef","ownedChat","stop","catch","runtime","threadList","threadId","unstable_messageRepositoryInstance","unstable_hostApprovalOwner","setRuntime","__internal_setGetThreadListItem","subscribeToRuntime","callback","thread","subscribe","getHistoryLoadingSnapshot","getState","isLoading","isLoadingHistory","resumableStorage","subscribeToResumableStorage","getPendingStreamId","getStreamId","pendingStreamId","isChatRunning","status","onResumeErrorRef","has","add","activeChat","clearError","pending","resumeStream","request","then","err","console","warn","callbackError","clear"],"sources":["../../src/runtime/useChatThread.ts"],"sourcesContent":["\"use client\";\n\nimport { Chat, useChat, type UIMessage } from \"@ai-sdk/react\";\nimport type { MessageRepository } from \"@assistant-ui/core/internal\";\nimport {\n pickExternalStoreSharedOptions,\n type AssistantRuntime,\n type ExternalStoreSharedOptions,\n} from \"@assistant-ui/core\";\nimport {\n useAISDKRuntime,\n type AISDKRuntimeAdapter,\n type CustomToCreateMessageFunction,\n} from \"./useAISDKRuntime\";\nimport type { ChatInit, ChatTransport } from \"ai\";\nimport {\n AssistantChatTransport,\n type InitializableThreadListItem,\n} from \"../transport/AssistantChatTransport\";\nimport type {\n AssistantChatResumableOptions,\n ResumableClientStorage,\n} from \"../transport/resumable\";\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport { useResourceCleanup } from \"./useResourceCleanup\";\n\nexport type ChatThreadOptions<UI_MESSAGE extends UIMessage = UIMessage> =\n ChatInit<UI_MESSAGE> &\n ExternalStoreSharedOptions & {\n throttle?: number | undefined;\n adapters?: AISDKRuntimeAdapter[\"adapters\"] | undefined;\n toCreateMessage?: CustomToCreateMessageFunction;\n onResume?: AISDKRuntimeAdapter[\"onResume\"];\n onResumeToolCall?: AISDKRuntimeAdapter[\"onResumeToolCall\"];\n onRespondToToolApproval?: AISDKRuntimeAdapter[\"onRespondToToolApproval\"];\n /**\n * Called when an automatic resumable stream reconnect fails. Use this to\n * surface a toast, report telemetry, or mark the thread as needing a\n * retry. The failed stream id is cleared after the callback unless a\n * newer id has replaced it.\n */\n onResumeError?: ((error: unknown) => void) | undefined;\n joinStrategy?: AISDKRuntimeAdapter[\"joinStrategy\"];\n messageRepository?: AISDKRuntimeAdapter<UI_MESSAGE>[\"messageRepository\"];\n unstable_onBranchChange?: AISDKRuntimeAdapter[\"unstable_onBranchChange\"];\n };\n\nexport type ChatThreadEnvironment<UI_MESSAGE extends UIMessage = UIMessage> = {\n id: string;\n isMainThread: boolean;\n getThreadListItem: () => InitializableThreadListItem | undefined;\n stopOnClientDestroy?: boolean;\n /**\n * Aborts when the React component hosting the runtime is deleted. A nested\n * runtime resolves the destroy signal of the provider above it, which\n * outlives the nested component, so this stops the chat on its own unmount.\n */\n hostDestroySignal?: AbortSignal | undefined;\n /**\n * An externally owned chat instance. State lives on the instance, so it\n * survives the hosting resource unmounting; construction options are read\n * from the instance.\n */\n chat?: Chat<UI_MESSAGE> | undefined;\n /**\n * An externally owned per-thread message repository. Hosts that route\n * multiple threads through one mounting pass a distinct instance per\n * thread so histories and branches stay isolated.\n */\n messageRepositoryInstance?: MessageRepository | undefined;\n};\n\nconst useDynamicChatTransport = <UI_MESSAGE extends UIMessage = UIMessage>(\n transport: ChatTransport<UI_MESSAGE>,\n): ChatTransport<UI_MESSAGE> => {\n const transportRef = useRef<ChatTransport<UI_MESSAGE>>(transport);\n useEffect(() => {\n transportRef.current = transport;\n });\n const dynamicTransport = useMemo(\n () =>\n new Proxy(transportRef.current, {\n get(_, prop) {\n const res =\n transportRef.current[prop as keyof ChatTransport<UI_MESSAGE>];\n return typeof res === \"function\"\n ? res.bind(transportRef.current)\n : res;\n },\n }),\n [],\n );\n return dynamicTransport;\n};\n\nconst getResumableAdapter = <UI_MESSAGE extends UIMessage>(\n transport: ChatTransport<UI_MESSAGE>,\n): AssistantChatResumableOptions | undefined => {\n if (transport instanceof AssistantChatTransport) {\n return transport.getResumableAdapter();\n }\n const candidate = (transport as { getResumableAdapter?: () => unknown })\n .getResumableAdapter;\n if (typeof candidate !== \"function\") return undefined;\n return candidate.call(transport) as AssistantChatResumableOptions | undefined;\n};\n\nconst getNoPendingStreamId = () => null;\n\nconst resumedStreamIdsByStorage = new WeakMap<\n ResumableClientStorage,\n Set<string>\n>();\n\nconst getResumedStreamIds = (storage: ResumableClientStorage | undefined) => {\n if (!storage) return new Set<string>();\n let resumedStreamIds = resumedStreamIdsByStorage.get(storage);\n if (!resumedStreamIds) {\n resumedStreamIds = new Set();\n resumedStreamIdsByStorage.set(storage, resumedStreamIds);\n }\n return resumedStreamIds;\n};\n\n/**\n * Splits the combined options into the assistant-ui side and the `ChatInit`\n * remainder the AI SDK consumes, so external `Chat` construction forwards the\n * same fields `useChat` would.\n */\nexport const splitChatThreadOptions = <UI_MESSAGE extends UIMessage>(\n options: ChatThreadOptions<UI_MESSAGE> | undefined,\n) => {\n const {\n adapters,\n transport,\n throttle,\n toCreateMessage,\n isDisabled: _isDisabled,\n isSendDisabled: _isSendDisabled,\n unstable_capabilities: _unstable_capabilities,\n suggestions: _suggestions,\n onResume,\n onResumeToolCall,\n onRespondToToolApproval,\n onResumeError,\n joinStrategy,\n messageRepository,\n unstable_onBranchChange,\n ...chatInit\n } = options ?? {};\n // peel guard: any shared key left in `chatInit` collapses this to `never`\n true satisfies keyof typeof chatInit &\n keyof ExternalStoreSharedOptions extends never\n ? true\n : never;\n return {\n adapters,\n transport,\n throttle,\n toCreateMessage,\n onResume,\n onResumeToolCall,\n onRespondToToolApproval,\n onResumeError,\n joinStrategy,\n messageRepository,\n unstable_onBranchChange,\n chatInit,\n };\n};\n\ntype ChatCallbacks<UI_MESSAGE extends UIMessage> = Pick<\n ChatInit<UI_MESSAGE>,\n \"onToolCall\" | \"onData\" | \"onFinish\" | \"onError\" | \"sendAutomaticallyWhen\"\n>;\n\nconst requestsByChat = new WeakMap<object, symbol>();\n\n/**\n * Constructs a `Chat` whose callbacks read the latest options through\n * `callbacksRef`, the forwarding `useChat` applies only to a chat it\n * constructs itself.\n */\nexport const createChat = <UI_MESSAGE extends UIMessage>(\n init: ChatInit<UI_MESSAGE>,\n callbacksRef: { readonly current: ChatCallbacks<UI_MESSAGE> | undefined },\n): Chat<UI_MESSAGE> => {\n const transport = init.transport;\n const chat = new Chat<UI_MESSAGE>({\n ...init,\n ...(transport && {\n transport: {\n sendMessages: (options) => {\n requestsByChat.set(chat, Symbol());\n return transport.sendMessages(options);\n },\n reconnectToStream: (options) => {\n requestsByChat.set(chat, Symbol());\n return transport.reconnectToStream(options);\n },\n },\n }),\n onToolCall: (arg) => callbacksRef.current?.onToolCall?.(arg),\n onData: (arg) => callbacksRef.current?.onData?.(arg),\n onFinish: (arg) => callbacksRef.current?.onFinish?.(arg),\n onError: (arg) => callbacksRef.current?.onError?.(arg),\n sendAutomaticallyWhen: (arg) =>\n callbacksRef.current?.sendAutomaticallyWhen?.(arg) ?? false,\n });\n return chat;\n};\n\nexport const useChatThread = <UI_MESSAGE extends UIMessage = UIMessage>(\n options: ChatThreadOptions<UI_MESSAGE> | undefined,\n env: ChatThreadEnvironment<UI_MESSAGE>,\n): AssistantRuntime => {\n const {\n adapters,\n transport: transportOptions,\n throttle,\n toCreateMessage,\n onResume,\n onResumeToolCall,\n onRespondToToolApproval,\n onResumeError,\n joinStrategy,\n messageRepository,\n unstable_onBranchChange,\n chatInit: chatOptions,\n } = splitChatThreadOptions(options);\n\n const {\n id,\n isMainThread,\n getThreadListItem,\n stopOnClientDestroy = true,\n hostDestroySignal,\n chat: externalChat,\n messageRepositoryInstance,\n } = env;\n\n // Wiring below is per thread and mutated on the instance, so a transport\n // shared across simultaneously mounted threads is last-writer-wins. A\n // caller-owned chat is already bound to its own clone, so cloning again\n // here would wire a copy the chat never sends through.\n const sourceTransport = useMemo(\n () =>\n transportOptions === undefined\n ? new AssistantChatTransport()\n : externalChat === undefined &&\n transportOptions instanceof AssistantChatTransport\n ? transportOptions.__internal_clone()\n : transportOptions,\n [transportOptions, externalChat],\n );\n const transport = useDynamicChatTransport(sourceTransport);\n\n const latestChatOptionsRef = useRef(chatOptions);\n useEffect(() => {\n latestChatOptionsRef.current = chatOptions;\n });\n // `useChat` stops a chat it constructs whenever it unmounts, and a\n // resource's soft unmount runs that cleanup, so the thread owns its chat.\n const [ownedChat] = useState(\n () =>\n externalChat ??\n createChat({ ...chatOptions, id, transport }, latestChatOptionsRef),\n );\n\n const chat = useChat({\n chat: externalChat ?? ownedChat,\n ...(throttle !== undefined && { throttle }),\n });\n\n useResourceCleanup(\n stopOnClientDestroy,\n () => {\n void chat.stop().catch(() => {});\n },\n hostDestroySignal,\n );\n\n const runtime = useAISDKRuntime(chat, {\n adapters: {\n ...adapters,\n threadList: { threadId: id, ...adapters?.threadList },\n },\n ...pickExternalStoreSharedOptions(options ?? {}),\n ...(toCreateMessage && { toCreateMessage }),\n ...(onResume && { onResume }),\n ...(onResumeToolCall && { onResumeToolCall }),\n ...(onRespondToToolApproval && { onRespondToToolApproval }),\n ...(joinStrategy && { joinStrategy }),\n ...(messageRepository && { messageRepository }),\n ...(messageRepositoryInstance && {\n unstable_messageRepositoryInstance: messageRepositoryInstance,\n }),\n // The chat outlives this runtime when a host mounts only the visible\n // thread, so a host approval answer is kept with it. This is the Chat\n // instance, not the useChat helpers, which are re-minted every render and\n // would be a dead WeakMap key by the next one.\n unstable_hostApprovalOwner: externalChat ?? ownedChat,\n ...(unstable_onBranchChange && { unstable_onBranchChange }),\n });\n\n // Wire in render, not an effect: a send from a descendant's mount effect\n // runs before this hook's effect would (effects fire child-first), and must\n // see a wired transport. The clone is per thread, so a discarded render's\n // wiring is discarded with it and the committed render re-wires the same\n // instance.\n if (sourceTransport instanceof AssistantChatTransport) {\n sourceTransport.setRuntime(runtime);\n sourceTransport.__internal_setGetThreadListItem(getThreadListItem);\n }\n\n const subscribeToRuntime = useCallback(\n (callback: () => void) => runtime.thread.subscribe(callback),\n [runtime],\n );\n const getHistoryLoadingSnapshot = useCallback(\n () => runtime.thread.getState().isLoading,\n [runtime],\n );\n const isLoadingHistory = useSyncExternalStore(\n subscribeToRuntime,\n getHistoryLoadingSnapshot,\n getHistoryLoadingSnapshot,\n );\n\n const resumableStorage = useMemo(\n () => getResumableAdapter(sourceTransport)?.storage,\n [sourceTransport],\n );\n const subscribeToResumableStorage = useCallback(\n (callback: () => void) =>\n isMainThread\n ? (resumableStorage?.subscribe?.(callback, id) ?? (() => {}))\n : () => {},\n [id, isMainThread, resumableStorage],\n );\n const getPendingStreamId = useCallback(\n () => (isMainThread ? (resumableStorage?.getStreamId(id) ?? null) : null),\n [id, isMainThread, resumableStorage],\n );\n const pendingStreamId = useSyncExternalStore(\n subscribeToResumableStorage,\n getPendingStreamId,\n getNoPendingStreamId,\n );\n const isChatRunning =\n chat.status === \"submitted\" || chat.status === \"streaming\";\n\n const resumedStreamIds = useMemo(\n () => getResumedStreamIds(resumableStorage),\n [resumableStorage],\n );\n const onResumeErrorRef = useRef(onResumeError);\n useEffect(() => {\n onResumeErrorRef.current = onResumeError;\n });\n useEffect(() => {\n if (!pendingStreamId || resumedStreamIds.has(pendingStreamId)) {\n return;\n }\n if (isChatRunning) {\n resumedStreamIds.add(pendingStreamId);\n return;\n }\n if (isLoadingHistory) return;\n resumedStreamIds.add(pendingStreamId);\n const activeChat = externalChat ?? ownedChat;\n activeChat.clearError();\n const pending = chat.resumeStream();\n const request = requestsByChat.get(activeChat);\n pending\n .then(() => {\n // Chat.error is shared with sends and resumes that can start before\n // this promise settles, including inside the caller's onFinish.\n if (requestsByChat.get(activeChat) === request && activeChat.error) {\n throw activeChat.error;\n }\n })\n .catch((err: unknown) => {\n console.warn(\"[assistant-ui] resumable: resume failed\", err);\n try {\n onResumeErrorRef.current?.(err);\n } catch (callbackError) {\n console.error(\n \"[assistant-ui] resumable: onResumeError callback failed\",\n callbackError,\n );\n } finally {\n if (resumableStorage?.getStreamId(id) === pendingStreamId) {\n resumableStorage.clear(id);\n }\n }\n });\n }, [\n chat,\n externalChat,\n id,\n isChatRunning,\n isLoadingHistory,\n pendingStreamId,\n ownedChat,\n resumableStorage,\n resumedStreamIds,\n ]);\n\n return runtime;\n};\n"],"mappings":";;;;;;;;AA+EA,MAAM6C,2BACJC,cAC8B;CAC9B,MAAMC,eAAe5B,OAAkC2B,SAAS;CAChE7B,gBAAgB;EACd8B,aAAaC,UAAUF;CACzB,CAAC;CAcD,OAbyB5B,cAErB,IAAIgC,MAAMH,aAAaC,SAAS,EAC9BG,IAAIC,GAAGC,MAAM;EACX,MAAMC,MACJP,aAAaC,QAAQK;EACvB,OAAO,OAAOC,QAAQ,aAClBA,IAAIC,KAAKR,aAAaC,OAAO,IAC7BM;CACN,EACF,CAAC,GACH,CAAA,CAEKL;AACT;AAEA,MAAMO,uBACJV,cAC8C;CAC9C,IAAIA,qBAAqBlC,wBACvB,OAAOkC,UAAUU,oBAAoB;CAEvC,MAAMC,YAAaX,UAChBU;CACH,IAAI,OAAOC,cAAc,YAAY,OAAOC,KAAAA;CAC5C,OAAOD,UAAUE,KAAKb,SAAS;AACjC;AAEA,MAAMc,6BAA6B;AAEnC,MAAMC,4CAA4B,IAAIC,QAGpC;AAEF,MAAME,uBAAuBC,YAAgD;CAC3E,IAAI,CAACA,SAAS,uBAAO,IAAIF,IAAY;CACrC,IAAIG,mBAAmBL,0BAA0BV,IAAIc,OAAO;CAC5D,IAAI,CAACC,kBAAkB;EACrBA,mCAAmB,IAAIH,IAAI;EAC3BF,0BAA0BM,IAAIF,SAASC,gBAAgB;CACzD;CACA,OAAOA;AACT;;;;;;AAOA,MAAaE,0BACXC,YACG;CACH,MAAM,EACJ3C,UACAoB,WACArB,UACAE,iBACA2C,YAAYC,aACZC,gBAAgBC,iBAChBC,uBAAuBC,wBACvBC,aAAaC,cACbjD,UACAC,kBACAC,yBACAC,eACAE,cACAC,mBACAC,yBACA,GAAG2C,aACDT,WAAW,CAAC;CAMhB,OAAO;EACL3C;EACAoB;EACArB;EACAE;EACAC;EACAC;EACAC;EACAC;EACAE;EACAC;EACAC;EACA2C;CACF;AACF;AAOA,MAAMG,iCAAiB,IAAInB,QAAwB;;;;;;AAOnD,MAAaoB,cACXC,MACAC,iBACqB;CACrB,MAAMtC,YAAYqC,KAAKrC;CACvB,MAAMH,OAAO,IAAI3C,KAAiB;EAChC,GAAGmF;EACH,GAAIrC,aAAa,EACfA,WAAW;GACTuC,eAAehB,YAAY;IACzBY,eAAed,IAAIxB,MAAM2C,OAAO,CAAC;IACjC,OAAOxC,UAAUuC,aAAahB,OAAO;GACvC;GACAkB,oBAAoBlB,YAAY;IAC9BY,eAAed,IAAIxB,MAAM2C,OAAO,CAAC;IACjC,OAAOxC,UAAUyC,kBAAkBlB,OAAO;GAC5C;EACF,EACF;EACAmB,aAAaC,QAAQL,aAAapC,SAASwC,aAAaC,GAAG;EAC3DC,SAASD,QAAQL,aAAapC,SAAS0C,SAASD,GAAG;EACnDE,WAAWF,QAAQL,aAAapC,SAAS2C,WAAWF,GAAG;EACvDG,UAAUH,QAAQL,aAAapC,SAAS4C,UAAUH,GAAG;EACrDI,wBAAwBJ,QACtBL,aAAapC,SAAS6C,wBAAwBJ,GAAG,KAAK;CAC1D,CAAC;CACD,OAAO9C;AACT;AAEA,MAAamD,iBACXzB,SACA0B,QACqB;CACrB,MAAM,EACJrE,UACAoB,WAAWkD,kBACXvE,UACAE,iBACAC,UACAC,kBACAC,yBACAC,eACAE,cACAC,mBACAC,yBACA2C,UAAUmB,gBACR7B,uBAAuBC,OAAO;CAElC,MAAM,EACJhC,IACAC,cACAC,mBACAC,sBAAsB,MACtBC,mBACAE,MAAMuD,cACNtD,8BACEmD;CAMJ,MAAMI,kBAAkBjF,cAEpB8E,qBAAqBtC,KAAAA,IACjB,IAAI9C,uBAAuB,IAC3BsF,iBAAiBxC,KAAAA,KACfsC,4BAA4BpF,yBAC5BoF,iBAAiBI,iBAAiB,IAClCJ,kBACR,CAACA,kBAAkBE,YAAY,CACjC;CACA,MAAMpD,YAAYD,wBAAwBsD,eAAe;CAEzD,MAAME,uBAAuBlF,OAAO8E,WAAW;CAC/ChF,gBAAgB;EACdoF,qBAAqBrD,UAAUiD;CACjC,CAAC;CAGD,MAAM,CAACK,aAAalF,eAEhB8E,gBACAhB,WAAW;EAAE,GAAGe;EAAa5D;EAAIS;CAAU,GAAGuD,oBAAoB,CACtE;CAEA,MAAM1D,OAAO1C,QAAQ;EACnB0C,MAAMuD,gBAAgBI;EACtB,GAAI7E,aAAaiC,KAAAA,KAAa,EAAEjC,SAAS;CAC3C,CAAC;CAEDH,mBACEkB,2BACM;EACJ,KAAU+D,KAAK,CAAC,CAACC,YAAY,CAAC,CAAC;CACjC,GACA/D,iBACF;CAEA,MAAMgE,UAAUlG,gBAAgBoC,MAAM;EACpCjB,UAAU;GACR,GAAGA;GACHgF,YAAY;IAAEC,UAAUtE;IAAI,GAAGX,UAAUgF;GAAW;EACtD;EACA,GAAGtG,+BAA+BiE,WAAW,CAAC,CAAC;EAC/C,GAAI1C,mBAAmB,EAAEA,gBAAgB;EACzC,GAAIC,YAAY,EAAEA,SAAS;EAC3B,GAAIC,oBAAoB,EAAEA,iBAAiB;EAC3C,GAAIC,2BAA2B,EAAEA,wBAAwB;EACzD,GAAIG,gBAAgB,EAAEA,aAAa;EACnC,GAAIC,qBAAqB,EAAEA,kBAAkB;EAC7C,GAAIU,6BAA6B,EAC/BgE,oCAAoChE,0BACtC;EAKAiE,4BAA4BX,gBAAgBI;EAC5C,GAAInE,2BAA2B,EAAEA,wBAAwB;CAC3D,CAAC;CAOD,IAAIgE,2BAA2BvF,wBAAwB;EACrDuF,gBAAgBW,WAAWL,OAAO;EAClCN,gBAAgBY,gCAAgCxE,iBAAiB;CACnE;CAEA,MAAMyE,qBAAqBhG,aACxBiG,aAAyBR,QAAQS,OAAOC,UAAUF,QAAQ,GAC3D,CAACR,OAAO,CACV;CACA,MAAMW,4BAA4BpG,kBAC1ByF,QAAQS,OAAOG,SAAS,CAAC,CAACC,WAChC,CAACb,OAAO,CACV;CACA,MAAMc,mBAAmBlG,qBACvB2F,oBACAI,2BACAA,yBACF;CAEA,MAAMI,mBAAmBtG,cACjBsC,oBAAoB2C,eAAe,CAAC,EAAElC,SAC5C,CAACkC,eAAe,CAClB;CACA,MAAMsB,8BAA8BzG,aACjCiG,eACC3E,eACKkF,kBAAkBL,YAAYF,YAAU5E,EAAE,YAAY,CAAC,WAClD,CAAC,GACb;EAACA;EAAIC;EAAckF;CAAgB,CACrC;CACA,MAAME,qBAAqB1G,kBAClBsB,eAAgBkF,kBAAkBG,YAAYtF,EAAE,KAAK,OAAQ,MACpE;EAACA;EAAIC;EAAckF;CAAgB,CACrC;CACA,MAAMI,kBAAkBvG,qBACtBoG,6BACAC,oBACA9D,oBACF;CACA,MAAMiE,gBACJlF,KAAKmF,WAAW,eAAenF,KAAKmF,WAAW;CAEjD,MAAM5D,mBAAmBhD,cACjB8C,oBAAoBwD,gBAAgB,GAC1C,CAACA,gBAAgB,CACnB;CACA,MAAMO,mBAAmB5G,OAAOY,aAAa;CAC7Cd,gBAAgB;EACd8G,iBAAiB/E,UAAUjB;CAC7B,CAAC;CACDd,gBAAgB;EACd,IAAI,CAAC2G,mBAAmB1D,iBAAiB8D,IAAIJ,eAAe,GAC1D;EAEF,IAAIC,eAAe;GACjB3D,iBAAiB+D,IAAIL,eAAe;GACpC;EACF;EACA,IAAIL,kBAAkB;EACtBrD,iBAAiB+D,IAAIL,eAAe;EACpC,MAAMM,aAAahC,gBAAgBI;EACnC4B,WAAWC,WAAW;EACtB,MAAMC,UAAUzF,KAAK0F,aAAa;EAClC,MAAMC,UAAUrD,eAAe9B,IAAI+E,UAAU;EAC7CE,QACGG,WAAW;GAGV,IAAItD,eAAe9B,IAAI+E,UAAU,MAAMI,WAAWJ,WAAWlG,OAC3D,MAAMkG,WAAWlG;EAErB,CAAC,CAAC,CACDwE,OAAOgC,QAAiB;GACvBC,QAAQC,KAAK,2CAA2CF,GAAG;GAC3D,IAAI;IACFT,iBAAiB/E,UAAUwF,GAAG;GAChC,SAASG,eAAe;IACtBF,QAAQzG,MACN,2DACA2G,aACF;GACF,UAAU;IACR,IAAInB,kBAAkBG,YAAYtF,EAAE,MAAMuF,iBACxCJ,iBAAiBoB,MAAMvG,EAAE;GAE7B;EACF,CAAC;CACL,GAAG;EACDM;EACAuD;EACA7D;EACAwF;EACAN;EACAK;EACAtB;EACAkB;EACAtD;CAAgB,CACjB;CAED,OAAOuC;AACT"}
|
|
1
|
+
{"version":3,"file":"useChatThread.js","names":["Chat","useChat","UIMessage","MessageRepository","pickExternalStoreSharedOptions","AssistantRuntime","ExternalStoreSharedOptions","useAISDKRuntime","AISDKRuntimeAdapter","CustomToCreateMessageFunction","ChatInit","AssistantChatTransport","InitializableThreadListItem","useCallback","useEffect","useEffectEvent","useInsertionEffect","useMemo","useRef","useState","useSyncExternalStore","useResourceCleanup","DynamicChatTransport","getResumedStreamIds","getResumableAdapter","useDynamicChatTransport","ChatThreadOptions","UI_MESSAGE","throttle","adapters","toCreateMessage","onResume","onResumeToolCall","onRespondToToolApproval","onResumeError","error","joinStrategy","messageRepository","unstable_onBranchChange","ChatThreadEnvironment","id","isMainThread","getThreadListItem","stopOnClientDestroy","hostDestroySignal","AbortSignal","chat","messageRepositoryInstance","ChatThreadTransportBinding","runtime","getNoPendingStreamId","splitChatThreadOptions","options","transport","isDisabled","_isDisabled","isSendDisabled","_isSendDisabled","unstable_capabilities","_unstable_capabilities","suggestions","_suggestions","chatInit","ChatCallbacks","Pick","requestsByChat","WeakMap","createChat","init","callbacksRef","current","sendMessages","set","Symbol","reconnectToStream","onToolCall","arg","onData","onFinish","onError","sendAutomaticallyWhen","useChatThread","env","transportOptions","chatOptions","externalChat","defaultTransport","configuredTransport","undefined","transportContextOwner","getThreadListItemRef","getCurrentThreadListItem","resumableStorage","getCurrentResumableStorage","storage","transportBindingRef","initialTransportBinding","chatTransport","createThreadProxy","binding","Error","latestChatOptionsRef","ownedChat","stop","catch","threadList","threadId","unstable_messageRepositoryInstance","unstable_hostApprovalOwner","registerTransportContext","dynamicTransport","chatId","setThreadContext","unregisterThread","setRuntime","__internal_setGetThreadListItem","subscribeToRuntime","callback","thread","subscribe","getHistoryLoadingSnapshot","getState","isLoading","isLoadingHistory","subscribeToResumableStorage","getPendingStreamId","getStreamId","pendingStreamId","isChatRunning","status","staticResumedStreamIds","onResumeErrorRef","resumedStreamIds","has","add","activeChat","clearError","pending","resumeStream","request","get","then","err","console","warn","callbackError","clear"],"sources":["../../src/runtime/useChatThread.ts"],"sourcesContent":["\"use client\";\n\nimport { Chat, useChat, type UIMessage } from \"@ai-sdk/react\";\nimport type { MessageRepository } from \"@assistant-ui/core/internal\";\nimport {\n pickExternalStoreSharedOptions,\n type AssistantRuntime,\n type ExternalStoreSharedOptions,\n} from \"@assistant-ui/core\";\nimport {\n useAISDKRuntime,\n type AISDKRuntimeAdapter,\n type CustomToCreateMessageFunction,\n} from \"./useAISDKRuntime\";\nimport type { ChatInit } from \"ai\";\nimport {\n AssistantChatTransport,\n type InitializableThreadListItem,\n} from \"../transport/AssistantChatTransport\";\nimport {\n useCallback,\n useEffect,\n useEffectEvent,\n useInsertionEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport { useResourceCleanup } from \"./useResourceCleanup\";\nimport {\n DynamicChatTransport,\n getResumedStreamIds,\n} from \"./DynamicChatTransport\";\nimport { getResumableAdapter } from \"./getResumableAdapter\";\nimport { useDynamicChatTransport } from \"./useDynamicChatTransport\";\n\nexport type ChatThreadOptions<UI_MESSAGE extends UIMessage = UIMessage> =\n ChatInit<UI_MESSAGE> &\n ExternalStoreSharedOptions & {\n throttle?: number | undefined;\n adapters?: AISDKRuntimeAdapter[\"adapters\"] | undefined;\n toCreateMessage?: CustomToCreateMessageFunction;\n onResume?: AISDKRuntimeAdapter[\"onResume\"];\n onResumeToolCall?: AISDKRuntimeAdapter[\"onResumeToolCall\"];\n onRespondToToolApproval?: AISDKRuntimeAdapter[\"onRespondToToolApproval\"];\n /**\n * Called when an automatic resumable stream reconnect fails. Use this to\n * surface a toast, report telemetry, or mark the thread as needing a\n * retry. The failed stream id is cleared after the callback unless a\n * newer id has replaced it.\n */\n onResumeError?: ((error: unknown) => void) | undefined;\n joinStrategy?: AISDKRuntimeAdapter[\"joinStrategy\"];\n messageRepository?: AISDKRuntimeAdapter<UI_MESSAGE>[\"messageRepository\"];\n /** @deprecated Experimental since 2026-06-23. Not scheduled for removal; the API may change in any release. */\n unstable_onBranchChange?: AISDKRuntimeAdapter[\"unstable_onBranchChange\"];\n };\n\nexport type ChatThreadEnvironment<UI_MESSAGE extends UIMessage = UIMessage> = {\n id: string;\n isMainThread: boolean;\n getThreadListItem: () => InitializableThreadListItem | undefined;\n stopOnClientDestroy?: boolean;\n /**\n * Aborts when the React component hosting the runtime is deleted. A nested\n * runtime resolves the destroy signal of the provider above it, which\n * outlives the nested component, so this stops the chat on its own unmount.\n */\n hostDestroySignal?: AbortSignal | undefined;\n /**\n * An externally owned chat instance. State lives on the instance, so it\n * survives the hosting resource unmounting; construction options are read\n * from the instance.\n */\n chat?: Chat<UI_MESSAGE> | undefined;\n /**\n * An externally owned per-thread message repository. Hosts that route\n * multiple threads through one mounting pass a distinct instance per\n * thread so histories and branches stay isolated.\n */\n messageRepositoryInstance?: MessageRepository | undefined;\n};\n\ntype ChatThreadTransportBinding = {\n runtime: AssistantRuntime;\n getThreadListItem: () => InitializableThreadListItem | undefined;\n};\n\nconst getNoPendingStreamId = () => null;\n\n/**\n * Splits the combined options into the assistant-ui side and the `ChatInit`\n * remainder the AI SDK consumes, so external `Chat` construction forwards the\n * same fields `useChat` would.\n */\nexport const splitChatThreadOptions = <UI_MESSAGE extends UIMessage>(\n options: ChatThreadOptions<UI_MESSAGE> | undefined,\n) => {\n const {\n adapters,\n transport,\n throttle,\n toCreateMessage,\n isDisabled: _isDisabled,\n isSendDisabled: _isSendDisabled,\n unstable_capabilities: _unstable_capabilities,\n suggestions: _suggestions,\n onResume,\n onResumeToolCall,\n onRespondToToolApproval,\n onResumeError,\n joinStrategy,\n messageRepository,\n unstable_onBranchChange,\n ...chatInit\n } = options ?? {};\n // peel guard: any shared key left in `chatInit` collapses this to `never`\n true satisfies keyof typeof chatInit &\n keyof ExternalStoreSharedOptions extends never\n ? true\n : never;\n return {\n adapters,\n transport,\n throttle,\n toCreateMessage,\n onResume,\n onResumeToolCall,\n onRespondToToolApproval,\n onResumeError,\n joinStrategy,\n messageRepository,\n unstable_onBranchChange,\n chatInit,\n };\n};\n\ntype ChatCallbacks<UI_MESSAGE extends UIMessage> = Pick<\n ChatInit<UI_MESSAGE>,\n \"onToolCall\" | \"onData\" | \"onFinish\" | \"onError\" | \"sendAutomaticallyWhen\"\n>;\n\nconst requestsByChat = new WeakMap<object, symbol>();\n\n/**\n * Constructs a `Chat` whose callbacks read the latest options through\n * `callbacksRef`, the forwarding `useChat` applies only to a chat it\n * constructs itself.\n */\nexport const createChat = <UI_MESSAGE extends UIMessage>(\n init: ChatInit<UI_MESSAGE>,\n callbacksRef: { readonly current: ChatCallbacks<UI_MESSAGE> | undefined },\n): Chat<UI_MESSAGE> => {\n const transport = init.transport;\n const chat = new Chat<UI_MESSAGE>({\n ...init,\n ...(transport && {\n transport: {\n sendMessages: (options) => {\n requestsByChat.set(chat, Symbol());\n return transport.sendMessages(options);\n },\n reconnectToStream: (options) => {\n requestsByChat.set(chat, Symbol());\n return transport.reconnectToStream(options);\n },\n },\n }),\n onToolCall: (arg) => callbacksRef.current?.onToolCall?.(arg),\n onData: (arg) => callbacksRef.current?.onData?.(arg),\n onFinish: (arg) => callbacksRef.current?.onFinish?.(arg),\n onError: (arg) => callbacksRef.current?.onError?.(arg),\n sendAutomaticallyWhen: (arg) =>\n callbacksRef.current?.sendAutomaticallyWhen?.(arg) ?? false,\n });\n return chat;\n};\n\nexport const useChatThread = <UI_MESSAGE extends UIMessage = UIMessage>(\n options: ChatThreadOptions<UI_MESSAGE> | undefined,\n env: ChatThreadEnvironment<UI_MESSAGE>,\n): AssistantRuntime => {\n const {\n adapters,\n transport: transportOptions,\n throttle,\n toCreateMessage,\n onResume,\n onResumeToolCall,\n onRespondToToolApproval,\n onResumeError,\n joinStrategy,\n messageRepository,\n unstable_onBranchChange,\n chatInit: chatOptions,\n } = splitChatThreadOptions(options);\n\n const {\n id,\n isMainThread,\n getThreadListItem,\n stopOnClientDestroy = true,\n hostDestroySignal,\n chat: externalChat,\n messageRepositoryInstance,\n } = env;\n\n const defaultTransport = useMemo(() => new AssistantChatTransport(), []);\n const configuredTransport = transportOptions ?? defaultTransport;\n const transport = useDynamicChatTransport(\n configuredTransport,\n externalChat === undefined,\n );\n const transportContextOwner = useMemo(() => ({}), []);\n const getThreadListItemRef = useRef(getThreadListItem);\n useInsertionEffect(() => {\n getThreadListItemRef.current = getThreadListItem;\n }, [getThreadListItem]);\n const getCurrentThreadListItem = useCallback(\n () => getThreadListItemRef.current(),\n [],\n );\n const resumableStorage = useMemo(\n () =>\n transport instanceof DynamicChatTransport\n ? transport.getCurrentResumableStorage()\n : getResumableAdapter(transport)?.storage,\n [transport],\n );\n\n const transportBindingRef = useRef<ChatThreadTransportBinding | null>(null);\n // This is null when useMemo runs, so the proxy pins its mount render's binding. The fallback\n // serves only pre-commit sends; after insertion effects, transportBindingRef is authoritative.\n let initialTransportBinding: ChatThreadTransportBinding | null = null;\n const chatTransport = useMemo(\n () =>\n transport instanceof DynamicChatTransport\n ? transport.createThreadProxy(transportContextOwner, () => {\n const binding =\n transportBindingRef.current ?? initialTransportBinding;\n if (!binding) {\n throw new Error(\"Chat transport used before runtime setup\");\n }\n return binding;\n })\n : transport,\n [initialTransportBinding, transport, transportContextOwner],\n );\n\n const latestChatOptionsRef = useRef(chatOptions);\n useEffect(() => {\n latestChatOptionsRef.current = chatOptions;\n });\n // `useChat` stops a chat it constructs whenever it unmounts, and a\n // resource's soft unmount runs that cleanup, so the thread owns its chat.\n const [ownedChat] = useState(\n () =>\n externalChat ??\n createChat(\n { ...chatOptions, id, transport: chatTransport },\n latestChatOptionsRef,\n ),\n );\n\n const chat = useChat({\n chat: externalChat ?? ownedChat,\n ...(throttle !== undefined && { throttle }),\n });\n\n useResourceCleanup(\n stopOnClientDestroy,\n () => {\n void chat.stop().catch(() => {});\n },\n hostDestroySignal,\n );\n\n const runtime = useAISDKRuntime(chat, {\n adapters: {\n ...adapters,\n threadList: { threadId: id, ...adapters?.threadList },\n },\n ...pickExternalStoreSharedOptions(options ?? {}),\n ...(toCreateMessage && { toCreateMessage }),\n ...(onResume && { onResume }),\n ...(onResumeToolCall && { onResumeToolCall }),\n ...(onRespondToToolApproval && { onRespondToToolApproval }),\n ...(joinStrategy && { joinStrategy }),\n ...(messageRepository && { messageRepository }),\n ...(messageRepositoryInstance && {\n unstable_messageRepositoryInstance: messageRepositoryInstance,\n }),\n // The chat outlives this runtime when a host mounts only the visible\n // thread, so a host approval answer is kept with it. This is the Chat\n // instance, not the useChat helpers, which are re-minted every render and\n // would be a dead WeakMap key by the next one.\n unstable_hostApprovalOwner: externalChat ?? ownedChat,\n ...(unstable_onBranchChange && { unstable_onBranchChange }),\n });\n initialTransportBinding = {\n runtime,\n getThreadListItem: getCurrentThreadListItem,\n };\n useInsertionEffect(() => {\n transportBindingRef.current = {\n runtime,\n getThreadListItem: getCurrentThreadListItem,\n };\n }, [runtime, getCurrentThreadListItem]);\n\n const registerTransportContext = useEffectEvent(\n (dynamicTransport: DynamicChatTransport<UI_MESSAGE>, chatId: string) => {\n dynamicTransport.setThreadContext(\n chatId,\n transportContextOwner,\n runtime,\n getCurrentThreadListItem,\n );\n return () =>\n dynamicTransport.unregisterThread(chatId, transportContextOwner);\n },\n );\n useInsertionEffect(() => {\n if (!(transport instanceof DynamicChatTransport)) return undefined;\n return registerTransportContext(transport, id);\n }, [id, runtime, transport, transportContextOwner]);\n\n if (\n !(transport instanceof DynamicChatTransport) &&\n configuredTransport instanceof AssistantChatTransport\n ) {\n configuredTransport.setRuntime(runtime);\n configuredTransport.__internal_setGetThreadListItem(\n getCurrentThreadListItem,\n );\n }\n\n const subscribeToRuntime = useCallback(\n (callback: () => void) => runtime.thread.subscribe(callback),\n [runtime],\n );\n const getHistoryLoadingSnapshot = useCallback(\n () => runtime.thread.getState().isLoading,\n [runtime],\n );\n const isLoadingHistory = useSyncExternalStore(\n subscribeToRuntime,\n getHistoryLoadingSnapshot,\n getHistoryLoadingSnapshot,\n );\n\n const subscribeToResumableStorage = useCallback(\n (callback: () => void) =>\n isMainThread\n ? (resumableStorage?.subscribe?.(callback, id) ?? (() => {}))\n : () => {},\n [id, isMainThread, resumableStorage],\n );\n const getPendingStreamId = useCallback(\n () => (isMainThread ? (resumableStorage?.getStreamId(id) ?? null) : null),\n [id, isMainThread, resumableStorage],\n );\n const pendingStreamId = useSyncExternalStore(\n subscribeToResumableStorage,\n getPendingStreamId,\n getNoPendingStreamId,\n );\n const isChatRunning =\n chat.status === \"submitted\" || chat.status === \"streaming\";\n\n const staticResumedStreamIds = useMemo(\n () => getResumedStreamIds(resumableStorage),\n [resumableStorage],\n );\n const onResumeErrorRef = useRef(onResumeError);\n useEffect(() => {\n onResumeErrorRef.current = onResumeError;\n });\n useEffect(() => {\n const resumedStreamIds =\n transport instanceof DynamicChatTransport\n ? transport.getResumedStreamIds()\n : staticResumedStreamIds;\n if (!pendingStreamId || resumedStreamIds.has(pendingStreamId)) {\n return;\n }\n if (isChatRunning) {\n resumedStreamIds.add(pendingStreamId);\n return;\n }\n if (isLoadingHistory) return;\n resumedStreamIds.add(pendingStreamId);\n const activeChat = externalChat ?? ownedChat;\n activeChat.clearError();\n const pending = chat.resumeStream();\n const request = requestsByChat.get(activeChat);\n pending\n .then(() => {\n // Chat.error is shared with sends and resumes that can start before\n // this promise settles, including inside the caller's onFinish.\n if (requestsByChat.get(activeChat) === request && activeChat.error) {\n throw activeChat.error;\n }\n })\n .catch((err: unknown) => {\n console.warn(\"[assistant-ui] resumable: resume failed\", err);\n try {\n onResumeErrorRef.current?.(err);\n } catch (callbackError) {\n console.error(\n \"[assistant-ui] resumable: onResumeError callback failed\",\n callbackError,\n );\n } finally {\n if (resumableStorage?.getStreamId(id) === pendingStreamId) {\n resumableStorage.clear(id);\n }\n }\n });\n }, [\n chat,\n externalChat,\n id,\n isChatRunning,\n isLoadingHistory,\n pendingStreamId,\n ownedChat,\n resumableStorage,\n staticResumedStreamIds,\n transport,\n ]);\n\n return runtime;\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { UIMessage } from "@ai-sdk/react";
|
|
2
|
+
import type { ChatTransport } from "ai";
|
|
3
|
+
export declare const useDynamicChatTransport: <UI_MESSAGE extends UIMessage>(transport: ChatTransport<UI_MESSAGE>, enabled?: boolean) => ChatTransport<UI_MESSAGE>;
|
|
4
|
+
//# sourceMappingURL=useDynamicChatTransport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDynamicChatTransport.d.ts","sourceRoot":"","sources":["../../src/runtime/useDynamicChatTransport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAIxC,eAAO,MAAM,uBAAuB,GAAI,UAAU,SAAS,SAAS,EAClE,WAAW,aAAa,CAAC,UAAU,CAAC,EACpC,iBAAc,KACb,aAAa,CAAC,UAAU,CAiB1B,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { DynamicChatTransport } from "./DynamicChatTransport.js";
|
|
2
|
+
import { useEffect, useInsertionEffect, useState } from "@assistant-ui/tap/react-shim";
|
|
3
|
+
import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
|
|
4
|
+
//#region src/runtime/useDynamicChatTransport.ts
|
|
5
|
+
const useDynamicChatTransport = (transport, t0) => {
|
|
6
|
+
const $ = c(12);
|
|
7
|
+
const enabled = t0 === void 0 ? true : t0;
|
|
8
|
+
let t1;
|
|
9
|
+
if ($[0] !== transport) {
|
|
10
|
+
t1 = () => transport instanceof DynamicChatTransport ? transport : new DynamicChatTransport(transport);
|
|
11
|
+
$[0] = transport;
|
|
12
|
+
$[1] = t1;
|
|
13
|
+
} else t1 = $[1];
|
|
14
|
+
const [dynamicTransport] = useState(t1);
|
|
15
|
+
let t2;
|
|
16
|
+
let t3;
|
|
17
|
+
if ($[2] !== dynamicTransport || $[3] !== enabled || $[4] !== transport) {
|
|
18
|
+
t2 = () => {
|
|
19
|
+
if (!enabled || dynamicTransport === transport) return;
|
|
20
|
+
dynamicTransport.setTransport(transport);
|
|
21
|
+
};
|
|
22
|
+
t3 = [
|
|
23
|
+
dynamicTransport,
|
|
24
|
+
enabled,
|
|
25
|
+
transport
|
|
26
|
+
];
|
|
27
|
+
$[2] = dynamicTransport;
|
|
28
|
+
$[3] = enabled;
|
|
29
|
+
$[4] = transport;
|
|
30
|
+
$[5] = t2;
|
|
31
|
+
$[6] = t3;
|
|
32
|
+
} else {
|
|
33
|
+
t2 = $[5];
|
|
34
|
+
t3 = $[6];
|
|
35
|
+
}
|
|
36
|
+
useInsertionEffect(t2, t3);
|
|
37
|
+
let t4;
|
|
38
|
+
let t5;
|
|
39
|
+
if ($[7] !== dynamicTransport || $[8] !== enabled || $[9] !== transport) {
|
|
40
|
+
t4 = () => {
|
|
41
|
+
if (!enabled || dynamicTransport === transport) return;
|
|
42
|
+
dynamicTransport.flushTransportChange();
|
|
43
|
+
};
|
|
44
|
+
t5 = [
|
|
45
|
+
dynamicTransport,
|
|
46
|
+
enabled,
|
|
47
|
+
transport
|
|
48
|
+
];
|
|
49
|
+
$[7] = dynamicTransport;
|
|
50
|
+
$[8] = enabled;
|
|
51
|
+
$[9] = transport;
|
|
52
|
+
$[10] = t4;
|
|
53
|
+
$[11] = t5;
|
|
54
|
+
} else {
|
|
55
|
+
t4 = $[10];
|
|
56
|
+
t5 = $[11];
|
|
57
|
+
}
|
|
58
|
+
useEffect(t4, t5);
|
|
59
|
+
return enabled ? dynamicTransport : transport;
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
export { useDynamicChatTransport };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=useDynamicChatTransport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDynamicChatTransport.js","names":["UIMessage","ChatTransport","useEffect","useInsertionEffect","useState","DynamicChatTransport","useDynamicChatTransport","transport","t0","$","_c","enabled","undefined","t1","dynamicTransport","t2","t3","setTransport","t4","t5","flushTransportChange","_c"],"sources":["../../src/runtime/useDynamicChatTransport.ts"],"sourcesContent":["import type { UIMessage } from \"@ai-sdk/react\";\nimport type { ChatTransport } from \"ai\";\nimport { useEffect, useInsertionEffect, useState } from \"react\";\nimport { DynamicChatTransport } from \"./DynamicChatTransport\";\n\nexport const useDynamicChatTransport = <UI_MESSAGE extends UIMessage>(\n transport: ChatTransport<UI_MESSAGE>,\n enabled = true,\n): ChatTransport<UI_MESSAGE> => {\n const [dynamicTransport] = useState(() =>\n transport instanceof DynamicChatTransport\n ? transport\n : new DynamicChatTransport(transport),\n );\n\n useInsertionEffect(() => {\n if (!enabled || dynamicTransport === transport) return;\n dynamicTransport.setTransport(transport);\n }, [dynamicTransport, enabled, transport]);\n useEffect(() => {\n if (!enabled || dynamicTransport === transport) return;\n dynamicTransport.flushTransportChange();\n }, [dynamicTransport, enabled, transport]);\n\n return enabled ? dynamicTransport : transport;\n};\n"],"mappings":";;;;AAKA,MAAaM,2BAA0BC,WAAAC,OAAA;CAAA,MAAAC,IAAAC,EAAA,EAAA;CAErC,MAAAC,UAAAH,OAAAI,KAAAA,IAAA,OAAAJ;CAAc,IAAAK;CAAA,IAAAJ,EAAA,OAAAF,WAAA;EAEsBM,WAClCN,qBAAqBF,uBAArBE,YAAA,IAEQF,qBAAqBE,SAAS;EAACE,EAAA,KAAAF;EAAAE,EAAA,KAAAI;CAAA,OAAAA,KAAAJ,EAAA;CAHzC,MAAA,CAAAK,oBAA2BV,SAASS,EAIpC;CAAE,IAAAE;CAAA,IAAAC;CAAA,IAAAP,EAAA,OAAAK,oBAAAL,EAAA,OAAAE,WAAAF,EAAA,OAAAF,WAAA;EAEiBQ,WAAA;GACjB,IAAI,CAACJ,WAAWG,qBAAqBP,WAAS;GAC9CO,iBAAgBG,aAAcV,SAAS;EAAC;EACvCS,KAAA;GAACF;GAAkBH;GAASJ;EAAS;EAACE,EAAA,KAAAK;EAAAL,EAAA,KAAAE;EAAAF,EAAA,KAAAF;EAAAE,EAAA,KAAAM;EAAAN,EAAA,KAAAO;CAAA,OAAA;EAAAD,KAAAN,EAAA;EAAAO,KAAAP,EAAA;CAAA;CAHzCN,mBAAmBY,IAGhBC,EAAsC;CAAC,IAAAE;CAAA,IAAAC;CAAA,IAAAV,EAAA,OAAAK,oBAAAL,EAAA,OAAAE,WAAAF,EAAA,OAAAF,WAAA;EAChCW,WAAA;GACR,IAAI,CAACP,WAAWG,qBAAqBP,WAAS;GAC9CO,iBAAgBM,qBAAsB;EAAC;EACtCD,KAAA;GAACL;GAAkBH;GAASJ;EAAS;EAACE,EAAA,KAAAK;EAAAL,EAAA,KAAAE;EAAAF,EAAA,KAAAF;EAAAE,EAAA,MAAAS;EAAAT,EAAA,MAAAU;CAAA,OAAA;EAAAD,KAAAT,EAAA;EAAAU,KAAAV,EAAA;CAAA;CAHzCP,UAAUgB,IAGPC,EAAsC;CAAC,OAEnCR,UAAAG,mBAAAP;AAAsC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useExternalHistory.d.ts","sourceRoot":"","sources":["../../src/runtime/useExternalHistory.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,oBAAoB,EAEpB,uBAAuB,EACvB,yBAAyB,EACzB,4BAA4B,EAC5B,2BAA2B,EAC5B,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"useExternalHistory.d.ts","sourceRoot":"","sources":["../../src/runtime/useExternalHistory.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,oBAAoB,EAEpB,uBAAuB,EACvB,yBAAyB,EACzB,4BAA4B,EAC5B,2BAA2B,EAC5B,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAIL,KAAK,SAAS,EAGf,MAAM,OAAO,CAAC;AAUf,eAAO,MAAM,2BAA2B,GAAI,QAAQ,EAClD,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,EAC3D,UAAU,uBAAuB,CAAC,QAAQ,CAAC,KAC1C,yBAoBF,CAAC;AA2BF,eAAO,MAAM,kBAAkB,GAAI,QAAQ,EACzC,YAAY,SAAS,CAAC,gBAAgB,CAAC,EACvC,gBAAgB,oBAAoB,GAAG,SAAS,EAChD,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,EAC3D,sBAAsB,oBAAoB,CAAC,QAAQ,EAAE,GAAG,CAAC,EACzD,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,IAAI,EAC7C,gBAAgB,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,0BAA0B,MAAM,IAAI,EACpC,mBAAmB,GAAG,CAAC,MAAM,EAAE,2BAA2B,CAAC,EAC3D,6BAA6B,MAAM,IAAI,EACvC,wBAAwB,GAAG,CAAC,MAAM,EAAE,4BAA4B,CAAC,EACjE,kCAAkC,MAAM,IAAI;;+BAkexB,MAAM;yCA3UZ,MAAM;8CAAN,MAAM;CAyXrB,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
import { MessageRepository
|
|
2
|
+
import { addToolData, collectToolApprovalResponses, collectToolArtifacts, collectToolInteractions, restoreToolData } from "./toolHistoryCodec.js";
|
|
3
|
+
import { MessageRepository } from "@assistant-ui/core/internal";
|
|
4
4
|
import { getExternalStoreMessages } from "@assistant-ui/core";
|
|
5
5
|
import { useCallback, useEffect, useMemo, useRef, useState } from "@assistant-ui/tap/react-shim";
|
|
6
6
|
import { useAui } from "@assistant-ui/store";
|
|
@@ -28,107 +28,6 @@ const toExportedMessageRepository = (toThreadMessages, messages) => {
|
|
|
28
28
|
const hasUnansweredApproval = (message) => message.content.some((part) => part.type === "tool-call" && part.approval != null && part.approval.approved === void 0 && part.approval.resolution === void 0);
|
|
29
29
|
const isAwaitingToolApproval = (message) => message.status?.type === "requires-action" && (message.status.reason === "tool-calls" || message.status.reason === "interrupt" && hasUnansweredApproval(message));
|
|
30
30
|
const isTerminalMessage = (message) => message.status === void 0 || message.status.type === "complete" || message.status.type === "incomplete";
|
|
31
|
-
const TOOL_ARTIFACTS_METADATA_KEY = "__aui_toolArtifacts";
|
|
32
|
-
const TOOL_INTERACTIONS_METADATA_KEY = "__aui_toolInteractions";
|
|
33
|
-
const TOOL_APPROVAL_RESPONSES_METADATA_KEY = "__aui_toolApprovalResponses";
|
|
34
|
-
const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
35
|
-
const collectToolArtifacts = (message, toolArtifacts) => {
|
|
36
|
-
if (!toolArtifacts) return void 0;
|
|
37
|
-
const entries = message.content.flatMap((part) => {
|
|
38
|
-
if (part.type !== "tool-call") return [];
|
|
39
|
-
const artifact = toolArtifacts.get(part.toolCallId);
|
|
40
|
-
return artifact === void 0 ? [] : [[part.toolCallId, artifact]];
|
|
41
|
-
});
|
|
42
|
-
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
43
|
-
};
|
|
44
|
-
const collectToolInteractions = (message, toolInteractions) => {
|
|
45
|
-
if (!toolInteractions) return void 0;
|
|
46
|
-
const entries = message.content.flatMap((part) => {
|
|
47
|
-
if (part.type !== "tool-call") return [];
|
|
48
|
-
const interactions = toolInteractions.get(part.toolCallId);
|
|
49
|
-
return interactions === void 0 ? [] : [[part.toolCallId, interactions]];
|
|
50
|
-
});
|
|
51
|
-
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
52
|
-
};
|
|
53
|
-
const collectToolApprovalResponses = (message, toolApprovalResponses) => {
|
|
54
|
-
if (!toolApprovalResponses) return void 0;
|
|
55
|
-
const entries = message.content.flatMap((part) => {
|
|
56
|
-
if (part.type !== "tool-call" || !part.approval) return [];
|
|
57
|
-
const response = toolApprovalResponses.get(part.approval.id);
|
|
58
|
-
if (!response) return [];
|
|
59
|
-
return [[part.approval.id, {
|
|
60
|
-
approved: response.approved,
|
|
61
|
-
...response.optionId != null && { optionId: response.optionId },
|
|
62
|
-
...response.text != null && { text: response.text },
|
|
63
|
-
...response.answers != null && { answers: response.answers },
|
|
64
|
-
...response.reason != null && { reason: response.reason }
|
|
65
|
-
}]];
|
|
66
|
-
});
|
|
67
|
-
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
68
|
-
};
|
|
69
|
-
const addToolData = (message, toolArtifacts, toolInteractions, toolApprovalResponses) => {
|
|
70
|
-
if (!toolArtifacts && !toolInteractions && !toolApprovalResponses || !isRecord(message) || !Array.isArray(message.parts)) return message;
|
|
71
|
-
const toolCallIds = message.parts.flatMap((part) => {
|
|
72
|
-
if (!isRecord(part) || typeof part.toolCallId !== "string") return [];
|
|
73
|
-
return [part.toolCallId];
|
|
74
|
-
});
|
|
75
|
-
const artifacts = toolArtifacts ? Object.fromEntries(toolCallIds.flatMap((toolCallId) => Object.hasOwn(toolArtifacts, toolCallId) ? [[toolCallId, toolArtifacts[toolCallId]]] : [])) : void 0;
|
|
76
|
-
const interactions = toolInteractions ? Object.fromEntries(toolCallIds.flatMap((toolCallId) => Object.hasOwn(toolInteractions, toolCallId) ? [[toolCallId, toolInteractions[toolCallId]]] : [])) : void 0;
|
|
77
|
-
const approvalIds = message.parts.flatMap((part) => {
|
|
78
|
-
if (!isRecord(part) || !isRecord(part.approval)) return [];
|
|
79
|
-
const approvalId = part.approval.id;
|
|
80
|
-
return typeof approvalId === "string" ? [approvalId] : [];
|
|
81
|
-
});
|
|
82
|
-
const approvalResponses = toolApprovalResponses ? Object.fromEntries(approvalIds.flatMap((approvalId) => Object.hasOwn(toolApprovalResponses, approvalId) ? [[approvalId, toolApprovalResponses[approvalId]]] : [])) : void 0;
|
|
83
|
-
const hasArtifacts = !!artifacts && Object.keys(artifacts).length > 0;
|
|
84
|
-
const hasInteractions = !!interactions && Object.keys(interactions).length > 0;
|
|
85
|
-
const hasApprovalResponses = !!approvalResponses && Object.keys(approvalResponses).length > 0;
|
|
86
|
-
if (!hasArtifacts && !hasInteractions && !hasApprovalResponses) return message;
|
|
87
|
-
const metadata = isRecord(message.metadata) ? message.metadata : {};
|
|
88
|
-
return {
|
|
89
|
-
...message,
|
|
90
|
-
metadata: {
|
|
91
|
-
...metadata,
|
|
92
|
-
...hasArtifacts && { [TOOL_ARTIFACTS_METADATA_KEY]: artifacts },
|
|
93
|
-
...hasInteractions && { [TOOL_INTERACTIONS_METADATA_KEY]: interactions },
|
|
94
|
-
...hasApprovalResponses && { [TOOL_APPROVAL_RESPONSES_METADATA_KEY]: approvalResponses }
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
};
|
|
98
|
-
const restoreToolData = (message, toolArtifacts, toolInteractions, toolApprovalResponses) => {
|
|
99
|
-
if (!isRecord(message) || !isRecord(message.metadata)) return message;
|
|
100
|
-
const metadata = message.metadata;
|
|
101
|
-
const hasArtifacts = Object.hasOwn(metadata, TOOL_ARTIFACTS_METADATA_KEY);
|
|
102
|
-
const hasInteractions = Object.hasOwn(metadata, TOOL_INTERACTIONS_METADATA_KEY);
|
|
103
|
-
const hasApprovalResponses = Object.hasOwn(metadata, TOOL_APPROVAL_RESPONSES_METADATA_KEY);
|
|
104
|
-
if (!hasArtifacts && !hasInteractions && !hasApprovalResponses) return message;
|
|
105
|
-
const artifacts = metadata[TOOL_ARTIFACTS_METADATA_KEY];
|
|
106
|
-
if (toolArtifacts && isRecord(artifacts)) for (const [toolCallId, artifact] of Object.entries(artifacts)) toolArtifacts.set(toolCallId, artifact);
|
|
107
|
-
const interactions = metadata[TOOL_INTERACTIONS_METADATA_KEY];
|
|
108
|
-
if (toolInteractions && isRecord(interactions)) for (const [toolCallId, value] of Object.entries(interactions)) {
|
|
109
|
-
const log = readToolInteractionLog(value);
|
|
110
|
-
if (log) toolInteractions.set(toolCallId, log);
|
|
111
|
-
}
|
|
112
|
-
const approvalResponses = metadata[TOOL_APPROVAL_RESPONSES_METADATA_KEY];
|
|
113
|
-
if (toolApprovalResponses && isRecord(approvalResponses)) for (const [approvalId, value] of Object.entries(approvalResponses)) {
|
|
114
|
-
if (!isRecord(value) || typeof value.approved !== "boolean") continue;
|
|
115
|
-
const answers = normalizeToolApprovalAnswers(value.answers);
|
|
116
|
-
toolApprovalResponses.set(approvalId, {
|
|
117
|
-
approvalId,
|
|
118
|
-
approved: value.approved,
|
|
119
|
-
...typeof value.optionId === "string" && { optionId: value.optionId },
|
|
120
|
-
...typeof value.text === "string" && { text: value.text },
|
|
121
|
-
...answers && Object.keys(answers).length > 0 && { answers },
|
|
122
|
-
...typeof value.reason === "string" && { reason: value.reason }
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
const { [TOOL_ARTIFACTS_METADATA_KEY]: _, [TOOL_INTERACTIONS_METADATA_KEY]: __, [TOOL_APPROVAL_RESPONSES_METADATA_KEY]: ___, ...restMetadata } = metadata;
|
|
126
|
-
const { metadata: _metadata, ...restMessage } = message;
|
|
127
|
-
return Object.keys(restMetadata).length === 0 ? restMessage : {
|
|
128
|
-
...restMessage,
|
|
129
|
-
metadata: restMetadata
|
|
130
|
-
};
|
|
131
|
-
};
|
|
132
31
|
const encodeContent = (storageFormatAdapter, item) => JSON.stringify(storageFormatAdapter.encode(item));
|
|
133
32
|
const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storageFormatAdapter, onSetMessages, toolArtifacts, onToolArtifactsRestored, toolInteractions, onToolInteractionsRestored, toolApprovalResponses, onToolApprovalResponsesRestored) => {
|
|
134
33
|
const loadedRef = useRef(false);
|
|
@@ -154,6 +53,8 @@ const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storag
|
|
|
154
53
|
const loadHistory = async () => {
|
|
155
54
|
try {
|
|
156
55
|
const repo = await formatAdapter.load();
|
|
56
|
+
const threadState = runtimeRef.current.thread.getState();
|
|
57
|
+
if (threadState.isRunning || threadState.messages.length > 0) return;
|
|
157
58
|
toolArtifacts?.clear();
|
|
158
59
|
toolInteractions?.clear();
|
|
159
60
|
toolApprovalResponses?.clear();
|
|
@@ -198,13 +99,14 @@ const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storag
|
|
|
198
99
|
if (optionalThreadListItem()?.getState().remoteId) setItemEpoch((n) => n + 1);
|
|
199
100
|
});
|
|
200
101
|
}
|
|
201
|
-
const
|
|
202
|
-
if (
|
|
102
|
+
const threadState_0 = runtimeRef.current.thread.getState();
|
|
103
|
+
if (threadState_0.isRunning || threadState_0.messages.length > 0) {
|
|
203
104
|
loadedRef.current = true;
|
|
204
105
|
setHasLoaded(true);
|
|
205
106
|
return;
|
|
206
107
|
}
|
|
207
108
|
loadedRef.current = true;
|
|
109
|
+
setHasLoaded(false);
|
|
208
110
|
loadHistory();
|
|
209
111
|
}, [
|
|
210
112
|
formatAdapter,
|
|
@@ -277,12 +179,12 @@ const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storag
|
|
|
277
179
|
const idleScheduledInnerIds = /* @__PURE__ */ new Set();
|
|
278
180
|
const unpersistedInnerIds = (message_0) => isTerminalMessage(message_0) ? getExternalStoreMessages(message_0).map((innerMessage_1) => storageFormatAdapter.getId(innerMessage_1)).filter((innerId_0) => !persistedInnerMessages.current.has(innerId_0)) : [];
|
|
279
181
|
const unsubscribe = runtimeRef.current.thread.subscribe(() => {
|
|
280
|
-
const
|
|
281
|
-
const { isRunning } =
|
|
182
|
+
const threadState_1 = runtimeRef.current.thread.getState();
|
|
183
|
+
const { isRunning } = threadState_1;
|
|
282
184
|
const wasRunning = wasRunningRef.current;
|
|
283
185
|
wasRunningRef.current = isRunning;
|
|
284
186
|
if (runStartRef.current != null) {
|
|
285
|
-
const lastMsg =
|
|
187
|
+
const lastMsg = threadState_1.messages.at(-1);
|
|
286
188
|
if (lastMsg?.role === "assistant") {
|
|
287
189
|
const currentToolCallCount = lastMsg.content.filter((p) => p.type === "tool-call").length;
|
|
288
190
|
while (toolCallCountRef.current < currentToolCallCount) {
|
|
@@ -307,7 +209,7 @@ const useExternalHistory = (runtimeRef, historyAdapter, toThreadMessages, storag
|
|
|
307
209
|
if (wasRunning) {
|
|
308
210
|
if (runStartRef.current != null) stepBoundariesRef.current.push(Date.now() - runStartRef.current);
|
|
309
211
|
} else {
|
|
310
|
-
const pending =
|
|
212
|
+
const pending = threadState_1.messages.flatMap(unpersistedInnerIds);
|
|
311
213
|
if (pending.every((innerId_1) => idleScheduledInnerIds.has(innerId_1))) return;
|
|
312
214
|
for (const innerId_2 of pending) idleScheduledInnerIds.add(innerId_2);
|
|
313
215
|
}
|