@assistant-ui/ai-sdk 0.0.1

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.
Files changed (167) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +32 -0
  3. package/dist/adapters/aiSDKFormatAdapter.d.ts +8 -0
  4. package/dist/adapters/aiSDKFormatAdapter.d.ts.map +1 -0
  5. package/dist/adapters/aiSDKFormatAdapter.js +26 -0
  6. package/dist/adapters/aiSDKFormatAdapter.js.map +1 -0
  7. package/dist/adapters/vercelAttachmentAdapter.d.ts +6 -0
  8. package/dist/adapters/vercelAttachmentAdapter.d.ts.map +1 -0
  9. package/dist/adapters/vercelAttachmentAdapter.js +37 -0
  10. package/dist/adapters/vercelAttachmentAdapter.js.map +1 -0
  11. package/dist/aiSDKExtras.d.ts +11 -0
  12. package/dist/aiSDKExtras.d.ts.map +1 -0
  13. package/dist/aiSDKExtras.js +7 -0
  14. package/dist/aiSDKExtras.js.map +1 -0
  15. package/dist/converters/convertMessage.d.ts +28 -0
  16. package/dist/converters/convertMessage.d.ts.map +1 -0
  17. package/dist/converters/convertMessage.js +249 -0
  18. package/dist/converters/convertMessage.js.map +1 -0
  19. package/dist/converters/modelContentEnvelope.d.ts +16 -0
  20. package/dist/converters/modelContentEnvelope.d.ts.map +1 -0
  21. package/dist/converters/modelContentEnvelope.js +22 -0
  22. package/dist/converters/modelContentEnvelope.js.map +1 -0
  23. package/dist/converters/toCreateMessage.d.ts +7 -0
  24. package/dist/converters/toCreateMessage.d.ts.map +1 -0
  25. package/dist/converters/toCreateMessage.js +58 -0
  26. package/dist/converters/toCreateMessage.js.map +1 -0
  27. package/dist/converters/toolOutputConversion.d.ts +29 -0
  28. package/dist/converters/toolOutputConversion.d.ts.map +1 -0
  29. package/dist/converters/toolOutputConversion.js +31 -0
  30. package/dist/converters/toolOutputConversion.js.map +1 -0
  31. package/dist/hooks.d.ts +19 -0
  32. package/dist/hooks.d.ts.map +1 -0
  33. package/dist/hooks.js +20 -0
  34. package/dist/hooks.js.map +1 -0
  35. package/dist/index.d.ts +14 -0
  36. package/dist/index.js +13 -0
  37. package/dist/index.native.d.ts +12 -0
  38. package/dist/index.native.js +11 -0
  39. package/dist/model-context/injectInteractableContext.d.ts +37 -0
  40. package/dist/model-context/injectInteractableContext.d.ts.map +1 -0
  41. package/dist/model-context/injectInteractableContext.js +52 -0
  42. package/dist/model-context/injectInteractableContext.js.map +1 -0
  43. package/dist/model-context/injectQuoteContext.d.ts +27 -0
  44. package/dist/model-context/injectQuoteContext.d.ts.map +1 -0
  45. package/dist/model-context/injectQuoteContext.js +51 -0
  46. package/dist/model-context/injectQuoteContext.js.map +1 -0
  47. package/dist/runtime/AISDKChat.d.ts +16 -0
  48. package/dist/runtime/AISDKChat.d.ts.map +1 -0
  49. package/dist/runtime/AISDKChat.js +69 -0
  50. package/dist/runtime/AISDKChat.js.map +1 -0
  51. package/dist/runtime/AISDKThreads.d.ts +45 -0
  52. package/dist/runtime/AISDKThreads.d.ts.map +1 -0
  53. package/dist/runtime/AISDKThreads.js +203 -0
  54. package/dist/runtime/AISDKThreads.js.map +1 -0
  55. package/dist/runtime/useAISDKRuntime.d.ts +49 -0
  56. package/dist/runtime/useAISDKRuntime.d.ts.map +1 -0
  57. package/dist/runtime/useAISDKRuntime.js +266 -0
  58. package/dist/runtime/useAISDKRuntime.js.map +1 -0
  59. package/dist/runtime/useChatRuntime.d.ts +13 -0
  60. package/dist/runtime/useChatRuntime.d.ts.map +1 -0
  61. package/dist/runtime/useChatRuntime.js +52 -0
  62. package/dist/runtime/useChatRuntime.js.map +1 -0
  63. package/dist/runtime/useChatThread.d.ts +77 -0
  64. package/dist/runtime/useChatThread.d.ts.map +1 -0
  65. package/dist/runtime/useChatThread.js +134 -0
  66. package/dist/runtime/useChatThread.js.map +1 -0
  67. package/dist/runtime/useExternalHistory.d.ts +11 -0
  68. package/dist/runtime/useExternalHistory.d.ts.map +1 -0
  69. package/dist/runtime/useExternalHistory.js +278 -0
  70. package/dist/runtime/useExternalHistory.js.map +1 -0
  71. package/dist/runtime/useResourceCleanup.d.ts +5 -0
  72. package/dist/runtime/useResourceCleanup.d.ts.map +1 -0
  73. package/dist/runtime/useResourceCleanup.js +47 -0
  74. package/dist/runtime/useResourceCleanup.js.map +1 -0
  75. package/dist/runtime/useStreamingTiming.d.ts +15 -0
  76. package/dist/runtime/useStreamingTiming.d.ts.map +1 -0
  77. package/dist/runtime/useStreamingTiming.js +38 -0
  78. package/dist/runtime/useStreamingTiming.js.map +1 -0
  79. package/dist/tools/frontendTools.d.ts +33 -0
  80. package/dist/tools/frontendTools.d.ts.map +1 -0
  81. package/dist/tools/frontendTools.js +33 -0
  82. package/dist/tools/frontendTools.js.map +1 -0
  83. package/dist/tools/generativeTools.d.ts +73 -0
  84. package/dist/tools/generativeTools.d.ts.map +1 -0
  85. package/dist/tools/generativeTools.js +245 -0
  86. package/dist/tools/generativeTools.js.map +1 -0
  87. package/dist/tools/mcp-stdio.node.d.ts +2 -0
  88. package/dist/tools/mcp-stdio.node.js +2 -0
  89. package/dist/tools/mcp-stdio.unsupported.d.ts +6 -0
  90. package/dist/tools/mcp-stdio.unsupported.d.ts.map +1 -0
  91. package/dist/tools/mcp-stdio.unsupported.js +11 -0
  92. package/dist/tools/mcp-stdio.unsupported.js.map +1 -0
  93. package/dist/transport/AssistantChatTransport.d.ts +23 -0
  94. package/dist/transport/AssistantChatTransport.d.ts.map +1 -0
  95. package/dist/transport/AssistantChatTransport.js +134 -0
  96. package/dist/transport/AssistantChatTransport.js.map +1 -0
  97. package/dist/transport/resumable.d.ts +38 -0
  98. package/dist/transport/resumable.d.ts.map +1 -0
  99. package/dist/transport/resumable.js +113 -0
  100. package/dist/transport/resumable.js.map +1 -0
  101. package/dist/usage.d.ts +19 -0
  102. package/dist/usage.d.ts.map +1 -0
  103. package/dist/usage.js +127 -0
  104. package/dist/usage.js.map +1 -0
  105. package/dist/utils/getVercelAIMessages.d.ts +7 -0
  106. package/dist/utils/getVercelAIMessages.d.ts.map +1 -0
  107. package/dist/utils/getVercelAIMessages.js +9 -0
  108. package/dist/utils/getVercelAIMessages.js.map +1 -0
  109. package/dist/utils/sliceMessagesUntil.d.ts +6 -0
  110. package/dist/utils/sliceMessagesUntil.d.ts.map +1 -0
  111. package/dist/utils/sliceMessagesUntil.js +12 -0
  112. package/dist/utils/sliceMessagesUntil.js.map +1 -0
  113. package/package.json +100 -0
  114. package/src/__tests__/standalone.e2e.ts +59 -0
  115. package/src/adapters/aiSDKFormatAdapter.ts +41 -0
  116. package/src/adapters/vercelAttachmentAdapter.test.ts +29 -0
  117. package/src/adapters/vercelAttachmentAdapter.ts +36 -0
  118. package/src/aiSDKExtras.ts +11 -0
  119. package/src/converters/convertMessage.test.ts +1130 -0
  120. package/src/converters/convertMessage.ts +457 -0
  121. package/src/converters/modelContentEnvelope.ts +41 -0
  122. package/src/converters/toCreateMessage.test.ts +596 -0
  123. package/src/converters/toCreateMessage.ts +99 -0
  124. package/src/converters/toolOutputConversion.ts +26 -0
  125. package/src/hooks.ts +23 -0
  126. package/src/index.native.ts +21 -0
  127. package/src/index.ts +29 -0
  128. package/src/model-context/injectInteractableContext.test.ts +167 -0
  129. package/src/model-context/injectInteractableContext.ts +61 -0
  130. package/src/model-context/injectQuoteContext.test.ts +33 -0
  131. package/src/model-context/injectQuoteContext.ts +62 -0
  132. package/src/runtime/AISDKChat.integration.test.tsx +47 -0
  133. package/src/runtime/AISDKChat.react-compiler.test.ts +49 -0
  134. package/src/runtime/AISDKChat.test.ts +193 -0
  135. package/src/runtime/AISDKChat.ts +47 -0
  136. package/src/runtime/AISDKThreads.cloud.test.ts +136 -0
  137. package/src/runtime/AISDKThreads.test.ts +548 -0
  138. package/src/runtime/AISDKThreads.ts +223 -0
  139. package/src/runtime/__tests__/controlled-transport.ts +43 -0
  140. package/src/runtime/useAISDKRuntime.approval.test.tsx +69 -0
  141. package/src/runtime/useAISDKRuntime.test.ts +1021 -0
  142. package/src/runtime/useAISDKRuntime.tool-output.test.tsx +113 -0
  143. package/src/runtime/useAISDKRuntime.ts +524 -0
  144. package/src/runtime/useChatOnTap.test.ts +58 -0
  145. package/src/runtime/useChatRuntime.integration.test.tsx +67 -0
  146. package/src/runtime/useChatRuntime.test.ts +522 -0
  147. package/src/runtime/useChatRuntime.ts +49 -0
  148. package/src/runtime/useChatThread.ts +286 -0
  149. package/src/runtime/useExternalHistory.test.ts +1289 -0
  150. package/src/runtime/useExternalHistory.ts +435 -0
  151. package/src/runtime/useResourceCleanup.ts +32 -0
  152. package/src/runtime/useStreamingTiming.ts +68 -0
  153. package/src/tools/frontendTools.test.ts +205 -0
  154. package/src/tools/frontendTools.ts +83 -0
  155. package/src/tools/generativeTools.test.ts +785 -0
  156. package/src/tools/generativeTools.ts +470 -0
  157. package/src/tools/mcp-stdio.node.ts +1 -0
  158. package/src/tools/mcp-stdio.unsupported.ts +12 -0
  159. package/src/transport/AssistantChatTransport.test.ts +300 -0
  160. package/src/transport/AssistantChatTransport.ts +204 -0
  161. package/src/transport/resumable.test.ts +262 -0
  162. package/src/transport/resumable.ts +161 -0
  163. package/src/usage.test.ts +186 -0
  164. package/src/usage.ts +174 -0
  165. package/src/utils/getVercelAIMessages.ts +11 -0
  166. package/src/utils/sliceMessagesUntil.test.ts +55 -0
  167. package/src/utils/sliceMessagesUntil.ts +20 -0
@@ -0,0 +1,286 @@
1
+ "use client";
2
+
3
+ import { useChat, type Chat, type UIMessage } from "@ai-sdk/react";
4
+ import {
5
+ pickExternalStoreSharedOptions,
6
+ type AssistantRuntime,
7
+ type ExternalStoreSharedOptions,
8
+ } from "@assistant-ui/core";
9
+ import {
10
+ useAISDKRuntime,
11
+ type AISDKRuntimeAdapter,
12
+ type CustomToCreateMessageFunction,
13
+ } from "./useAISDKRuntime";
14
+ import type { ChatInit, ChatTransport } from "ai";
15
+ import {
16
+ AssistantChatTransport,
17
+ type InitializableThreadListItem,
18
+ } from "../transport/AssistantChatTransport";
19
+ import type {
20
+ AssistantChatResumableOptions,
21
+ ResumableClientStorage,
22
+ } from "../transport/resumable";
23
+ import {
24
+ useCallback,
25
+ useEffect,
26
+ useMemo,
27
+ useRef,
28
+ useSyncExternalStore,
29
+ } from "react";
30
+ import { useResourceCleanup } from "./useResourceCleanup";
31
+
32
+ export type ChatThreadOptions<UI_MESSAGE extends UIMessage = UIMessage> =
33
+ ChatInit<UI_MESSAGE> &
34
+ ExternalStoreSharedOptions & {
35
+ adapters?: AISDKRuntimeAdapter["adapters"] | undefined;
36
+ toCreateMessage?: CustomToCreateMessageFunction;
37
+ onResume?: AISDKRuntimeAdapter["onResume"];
38
+ onResumeToolCall?: AISDKRuntimeAdapter["onResumeToolCall"];
39
+ /**
40
+ * Called when an automatic resumable stream reconnect fails. Use this to
41
+ * surface a toast, report telemetry, or mark the thread as needing a
42
+ * retry. The failed stream id is cleared after the callback unless a
43
+ * newer id has replaced it.
44
+ */
45
+ onResumeError?: ((error: unknown) => void) | undefined;
46
+ joinStrategy?: AISDKRuntimeAdapter["joinStrategy"];
47
+ };
48
+
49
+ export type ChatThreadEnvironment<UI_MESSAGE extends UIMessage = UIMessage> = {
50
+ id: string;
51
+ isMainThread: boolean;
52
+ getThreadListItem: () => InitializableThreadListItem | undefined;
53
+ stopOnClientDestroy?: boolean;
54
+ /**
55
+ * An externally owned chat instance. State lives on the instance, so it
56
+ * survives the hosting resource unmounting; construction options are read
57
+ * from the instance.
58
+ */
59
+ chat?: Chat<UI_MESSAGE> | undefined;
60
+ };
61
+
62
+ const useDynamicChatTransport = <UI_MESSAGE extends UIMessage = UIMessage>(
63
+ transport: ChatTransport<UI_MESSAGE>,
64
+ ): ChatTransport<UI_MESSAGE> => {
65
+ const transportRef = useRef<ChatTransport<UI_MESSAGE>>(transport);
66
+ useEffect(() => {
67
+ transportRef.current = transport;
68
+ });
69
+ const dynamicTransport = useMemo(
70
+ () =>
71
+ new Proxy(transportRef.current, {
72
+ get(_, prop) {
73
+ const res =
74
+ transportRef.current[prop as keyof ChatTransport<UI_MESSAGE>];
75
+ return typeof res === "function"
76
+ ? res.bind(transportRef.current)
77
+ : res;
78
+ },
79
+ }),
80
+ [],
81
+ );
82
+ return dynamicTransport;
83
+ };
84
+
85
+ const getResumableAdapter = <UI_MESSAGE extends UIMessage>(
86
+ transport: ChatTransport<UI_MESSAGE>,
87
+ ): AssistantChatResumableOptions | undefined => {
88
+ if (transport instanceof AssistantChatTransport) {
89
+ return transport.getResumableAdapter();
90
+ }
91
+ const candidate = (transport as { getResumableAdapter?: () => unknown })
92
+ .getResumableAdapter;
93
+ if (typeof candidate !== "function") return undefined;
94
+ return candidate.call(transport) as AssistantChatResumableOptions | undefined;
95
+ };
96
+
97
+ const getNoPendingStreamId = () => null;
98
+
99
+ const resumedStreamIdsByStorage = new WeakMap<
100
+ ResumableClientStorage,
101
+ Set<string>
102
+ >();
103
+
104
+ const getResumedStreamIds = (storage: ResumableClientStorage | undefined) => {
105
+ if (!storage) return new Set<string>();
106
+ let resumedStreamIds = resumedStreamIdsByStorage.get(storage);
107
+ if (!resumedStreamIds) {
108
+ resumedStreamIds = new Set();
109
+ resumedStreamIdsByStorage.set(storage, resumedStreamIds);
110
+ }
111
+ return resumedStreamIds;
112
+ };
113
+
114
+ /**
115
+ * Splits the combined options into the assistant-ui side and the `ChatInit`
116
+ * remainder the AI SDK consumes, so external `Chat` construction forwards the
117
+ * same fields `useChat` would.
118
+ */
119
+ export const splitChatThreadOptions = <UI_MESSAGE extends UIMessage>(
120
+ options: ChatThreadOptions<UI_MESSAGE> | undefined,
121
+ ) => {
122
+ const {
123
+ adapters,
124
+ transport,
125
+ toCreateMessage,
126
+ isDisabled: _isDisabled,
127
+ isSendDisabled: _isSendDisabled,
128
+ unstable_capabilities: _unstable_capabilities,
129
+ suggestions: _suggestions,
130
+ onResume,
131
+ onResumeToolCall,
132
+ onResumeError,
133
+ joinStrategy,
134
+ ...chatInit
135
+ } = options ?? {};
136
+ // peel guard: any shared key left in `chatInit` collapses this to `never`
137
+ true satisfies keyof typeof chatInit &
138
+ keyof ExternalStoreSharedOptions extends never
139
+ ? true
140
+ : never;
141
+ return {
142
+ adapters,
143
+ transport,
144
+ toCreateMessage,
145
+ onResume,
146
+ onResumeToolCall,
147
+ onResumeError,
148
+ joinStrategy,
149
+ chatInit,
150
+ };
151
+ };
152
+
153
+ export const useChatThread = <UI_MESSAGE extends UIMessage = UIMessage>(
154
+ options: ChatThreadOptions<UI_MESSAGE> | undefined,
155
+ env: ChatThreadEnvironment<UI_MESSAGE>,
156
+ ): AssistantRuntime => {
157
+ const {
158
+ adapters,
159
+ transport: transportOptions,
160
+ toCreateMessage,
161
+ onResume,
162
+ onResumeToolCall,
163
+ onResumeError,
164
+ joinStrategy,
165
+ chatInit: chatOptions,
166
+ } = splitChatThreadOptions(options);
167
+
168
+ const {
169
+ id,
170
+ isMainThread,
171
+ getThreadListItem,
172
+ stopOnClientDestroy = false,
173
+ chat: externalChat,
174
+ } = env;
175
+
176
+ const defaultTransport = useMemo(() => new AssistantChatTransport(), []);
177
+ const sourceTransport = transportOptions ?? defaultTransport;
178
+ const transport = useDynamicChatTransport(sourceTransport);
179
+
180
+ const chat = useChat({
181
+ ...chatOptions,
182
+ id,
183
+ transport,
184
+ ...(externalChat !== undefined && { chat: externalChat }),
185
+ });
186
+
187
+ useResourceCleanup(stopOnClientDestroy, () => {
188
+ void chat.stop().catch(() => {});
189
+ });
190
+
191
+ const runtime = useAISDKRuntime(chat, {
192
+ adapters,
193
+ ...pickExternalStoreSharedOptions(options ?? {}),
194
+ ...(toCreateMessage && { toCreateMessage }),
195
+ ...(onResume && { onResume }),
196
+ ...(onResumeToolCall && { onResumeToolCall }),
197
+ ...(joinStrategy && { joinStrategy }),
198
+ });
199
+
200
+ if (sourceTransport instanceof AssistantChatTransport) {
201
+ sourceTransport.setRuntime(runtime);
202
+ sourceTransport.__internal_setGetThreadListItem(getThreadListItem);
203
+ }
204
+
205
+ const subscribeToRuntime = useCallback(
206
+ (callback: () => void) => runtime.thread.subscribe(callback),
207
+ [runtime],
208
+ );
209
+ const getHistoryLoadingSnapshot = useCallback(
210
+ () => runtime.thread.getState().isLoading,
211
+ [runtime],
212
+ );
213
+ const isLoadingHistory = useSyncExternalStore(
214
+ subscribeToRuntime,
215
+ getHistoryLoadingSnapshot,
216
+ getHistoryLoadingSnapshot,
217
+ );
218
+
219
+ const resumableStorage = useMemo(
220
+ () => getResumableAdapter(sourceTransport)?.storage,
221
+ [sourceTransport],
222
+ );
223
+ const subscribeToResumableStorage = useCallback(
224
+ (callback: () => void) =>
225
+ isMainThread
226
+ ? (resumableStorage?.subscribe?.(callback, id) ?? (() => {}))
227
+ : () => {},
228
+ [id, isMainThread, resumableStorage],
229
+ );
230
+ const getPendingStreamId = useCallback(
231
+ () => (isMainThread ? (resumableStorage?.getStreamId(id) ?? null) : null),
232
+ [id, isMainThread, resumableStorage],
233
+ );
234
+ const pendingStreamId = useSyncExternalStore(
235
+ subscribeToResumableStorage,
236
+ getPendingStreamId,
237
+ getNoPendingStreamId,
238
+ );
239
+ const isChatRunning =
240
+ chat.status === "submitted" || chat.status === "streaming";
241
+
242
+ const resumedStreamIds = useMemo(
243
+ () => getResumedStreamIds(resumableStorage),
244
+ [resumableStorage],
245
+ );
246
+ const onResumeErrorRef = useRef(onResumeError);
247
+ useEffect(() => {
248
+ onResumeErrorRef.current = onResumeError;
249
+ });
250
+ useEffect(() => {
251
+ if (!pendingStreamId || resumedStreamIds.has(pendingStreamId)) {
252
+ return;
253
+ }
254
+ if (isChatRunning) {
255
+ resumedStreamIds.add(pendingStreamId);
256
+ return;
257
+ }
258
+ if (isLoadingHistory) return;
259
+ resumedStreamIds.add(pendingStreamId);
260
+ chat.resumeStream().catch((err: unknown) => {
261
+ console.warn("[assistant-ui] resumable: resume failed", err);
262
+ try {
263
+ onResumeErrorRef.current?.(err);
264
+ } catch (callbackError) {
265
+ console.error(
266
+ "[assistant-ui] resumable: onResumeError callback failed",
267
+ callbackError,
268
+ );
269
+ } finally {
270
+ if (resumableStorage?.getStreamId(id) === pendingStreamId) {
271
+ resumableStorage.clear(id);
272
+ }
273
+ }
274
+ });
275
+ }, [
276
+ chat,
277
+ id,
278
+ isChatRunning,
279
+ isLoadingHistory,
280
+ pendingStreamId,
281
+ resumableStorage,
282
+ resumedStreamIds,
283
+ ]);
284
+
285
+ return runtime;
286
+ };