@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,223 @@
1
+ "use client";
2
+
3
+ import { resource, useResource, withKey } from "@assistant-ui/tap";
4
+ import { useEffect, useMemo, useState } from "react";
5
+ import { Chat, type UIMessage } from "@ai-sdk/react";
6
+ import type { ChatTransport } from "ai";
7
+ import type { AssistantCloud } from "assistant-cloud";
8
+ import {
9
+ InMemoryThreadList,
10
+ RemoteThreadList,
11
+ inMemoryThreadListTransformScopes,
12
+ } from "@assistant-ui/core/store";
13
+ import { ThreadClient } from "@assistant-ui/core/store/internal";
14
+ import { useCloudThreadListAdapter } from "@assistant-ui/core/react";
15
+ import {
16
+ attachTransformScopes,
17
+ useAssistantClientRef,
18
+ useAssistantScopeEffect,
19
+ } from "@assistant-ui/store/client";
20
+ import { useAui } from "@assistant-ui/store";
21
+ import { AssistantChatTransport } from "../transport/AssistantChatTransport";
22
+ import {
23
+ splitChatThreadOptions,
24
+ useChatThread,
25
+ type ChatThreadOptions,
26
+ } from "./useChatThread";
27
+ import { useResourceCleanup } from "./useResourceCleanup";
28
+
29
+ export type AISDKThreadsOptions<UI_MESSAGE extends UIMessage = UIMessage> =
30
+ Omit<ChatThreadOptions<UI_MESSAGE>, "id" | "transport" | "messages"> & {
31
+ /**
32
+ * The transport threads send through. A factory is invoked once per
33
+ * thread so each thread owns its instance. A plain
34
+ * `AssistantChatTransport` instance is cloned per thread (its
35
+ * assistant-ui wiring is per thread); any other transport instance is
36
+ * shared as-is. Defaults to one `AssistantChatTransport` per thread.
37
+ */
38
+ transport?:
39
+ | ChatTransport<UI_MESSAGE>
40
+ | (() => ChatTransport<UI_MESSAGE>)
41
+ | undefined;
42
+ /**
43
+ * When set, the thread list is a `RemoteThreadList` backed by this
44
+ * assistant-cloud. Omit it to keep the in-memory list. The thread
45
+ * factory is keyed so cloud history reloads on a switch, and an
46
+ * in-flight run does not continue in the background.
47
+ */
48
+ cloud?: AssistantCloud | undefined;
49
+ /**
50
+ * Controlled thread id for the cloud list. Ignored without `cloud`.
51
+ */
52
+ threadId?: string | undefined;
53
+ /**
54
+ * Called with the settled remote id when the cloud list changes thread.
55
+ */
56
+ onThreadIdChange?: ((threadId: string | undefined) => void) | undefined;
57
+ };
58
+
59
+ type AISDKThreadChatOptions<UI_MESSAGE extends UIMessage = UIMessage> = Omit<
60
+ AISDKThreadsOptions<UI_MESSAGE>,
61
+ "cloud" | "threadId" | "onThreadIdChange"
62
+ >;
63
+
64
+ type ChatEntry<UI_MESSAGE extends UIMessage> = {
65
+ chat: Chat<UI_MESSAGE>;
66
+ transport: ChatTransport<UI_MESSAGE>;
67
+ };
68
+
69
+ const createChatEntry = <UI_MESSAGE extends UIMessage>(
70
+ threadId: string,
71
+ options: AISDKThreadChatOptions<UI_MESSAGE> | undefined,
72
+ ): ChatEntry<UI_MESSAGE> => {
73
+ const { chatInit } = splitChatThreadOptions(
74
+ options as ChatThreadOptions<UI_MESSAGE> | undefined,
75
+ );
76
+ const transport =
77
+ typeof options?.transport === "function"
78
+ ? options.transport()
79
+ : options?.transport === undefined
80
+ ? new AssistantChatTransport()
81
+ : options.transport instanceof AssistantChatTransport
82
+ ? options.transport.__internal_clone()
83
+ : options.transport;
84
+ return {
85
+ chat: new Chat<UI_MESSAGE>({ ...chatInit, id: threadId, transport }),
86
+ transport,
87
+ };
88
+ };
89
+
90
+ const getOrCreateChatEntry = <UI_MESSAGE extends UIMessage>(
91
+ threadId: string,
92
+ options: AISDKThreadChatOptions<UI_MESSAGE> | undefined,
93
+ chats: Map<string, ChatEntry<UI_MESSAGE>>,
94
+ ): ChatEntry<UI_MESSAGE> => {
95
+ const existing = chats.get(threadId);
96
+ if (existing) return existing;
97
+ const created = createChatEntry(threadId, options);
98
+ chats.set(threadId, created);
99
+ return created;
100
+ };
101
+
102
+ const useAISDKChatThread = <UI_MESSAGE extends UIMessage = UIMessage>({
103
+ threadId,
104
+ options,
105
+ chats,
106
+ cloud,
107
+ }: {
108
+ threadId: string;
109
+ options: AISDKThreadChatOptions<UI_MESSAGE> | undefined;
110
+ chats: Map<string, ChatEntry<UI_MESSAGE>>;
111
+ cloud: boolean;
112
+ }) => {
113
+ const [owned] = useState(() =>
114
+ cloud ? createChatEntry(threadId, options) : undefined,
115
+ );
116
+ const { chat, transport } =
117
+ owned ?? getOrCreateChatEntry(threadId, options, chats);
118
+
119
+ useEffect(() => {
120
+ if (!cloud) return undefined;
121
+ return () => {
122
+ void chat.stop().catch(() => {});
123
+ };
124
+ }, [chat, cloud]);
125
+
126
+ const aui = useAui();
127
+ const fallbackItem = useMemo(
128
+ () => ({
129
+ initialize: async () => ({
130
+ remoteId: threadId,
131
+ externalId: undefined,
132
+ }),
133
+ }),
134
+ [threadId],
135
+ );
136
+ const runtime = useChatThread(
137
+ { ...options, transport } as ChatThreadOptions<UI_MESSAGE>,
138
+ {
139
+ id: threadId,
140
+ isMainThread: true,
141
+ getThreadListItem: () =>
142
+ cloud
143
+ ? aui.threadListItem.source
144
+ ? aui.threadListItem
145
+ : undefined
146
+ : fallbackItem,
147
+ chat,
148
+ stopOnClientDestroy: cloud,
149
+ },
150
+ );
151
+
152
+ const clientRef = useAssistantClientRef();
153
+ useAssistantScopeEffect(
154
+ "modelContext",
155
+ () =>
156
+ runtime.registerModelContextProvider(clientRef.current!.modelContext()),
157
+ [runtime],
158
+ );
159
+
160
+ return useResource(ThreadClient({ runtime: runtime.thread }));
161
+ };
162
+
163
+ const AISDKChatThread = resource(useAISDKChatThread);
164
+
165
+ const useAISDKThreads = <UI_MESSAGE extends UIMessage = UIMessage>(
166
+ options?: AISDKThreadsOptions<UI_MESSAGE>,
167
+ ) => {
168
+ const { cloud, threadId, onThreadIdChange, ...threadOptions } = options ?? {};
169
+ const [chats] = useState(() => new Map<string, ChatEntry<UI_MESSAGE>>());
170
+ const bindCloud = cloud !== undefined;
171
+
172
+ useResourceCleanup(true, () => {
173
+ for (const { chat } of chats.values()) {
174
+ void chat.stop().catch(() => {});
175
+ }
176
+ });
177
+
178
+ const cloudAdapter = useCloudThreadListAdapter({ cloud });
179
+ const thread = (id: string) => {
180
+ const element = AISDKChatThread({
181
+ threadId: id,
182
+ options: threadOptions,
183
+ chats,
184
+ cloud: bindCloud,
185
+ });
186
+ return bindCloud ? withKey(id, element) : element;
187
+ };
188
+
189
+ return useResource(
190
+ bindCloud
191
+ ? RemoteThreadList({
192
+ adapter: cloudAdapter,
193
+ thread,
194
+ threadId,
195
+ onThreadIdChange,
196
+ })
197
+ : InMemoryThreadList({
198
+ thread,
199
+ onDelete: (id) => {
200
+ void chats
201
+ .get(id)
202
+ ?.chat.stop()
203
+ .catch(() => {});
204
+ chats.delete(id);
205
+ },
206
+ }),
207
+ );
208
+ };
209
+
210
+ /**
211
+ * `AuiConfig` entry that runs one AI SDK chat per thread. Hosts the same
212
+ * per-thread orchestration as {@link AISDKChat} inside the client's own
213
+ * resource tree, so it works with any `AssistantClient` host, React or not.
214
+ * Without `cloud`, threads live in memory for the client's lifetime and keep
215
+ * their history across switches; each thread's chat id is its thread id.
216
+ * With `cloud`, the list is a `RemoteThreadList` and the factory is keyed so
217
+ * cloud history reloads on a switch. The store entry mounts only the visible
218
+ * thread, so a switch cancels an in-flight run. Model context is
219
+ * registered on the visible thread only.
220
+ */
221
+ export const AISDKThreads = resource(useAISDKThreads);
222
+
223
+ attachTransformScopes(useAISDKThreads, inMemoryThreadListTransformScopes);
@@ -0,0 +1,43 @@
1
+ import type { ChatTransport, UIMessage, UIMessageChunk } from "ai";
2
+
3
+ export const createControlledTransport = () => {
4
+ let controller!: ReadableStreamDefaultController<UIMessageChunk>;
5
+ const transport: ChatTransport<UIMessage> = {
6
+ sendMessages: async () =>
7
+ new ReadableStream<UIMessageChunk>({
8
+ start(c) {
9
+ controller = c;
10
+ },
11
+ }),
12
+ reconnectToStream: async () => null,
13
+ };
14
+ return {
15
+ transport,
16
+ emit: (...chunks: UIMessageChunk[]) => {
17
+ for (const chunk of chunks) controller.enqueue(chunk);
18
+ },
19
+ close: () => controller.close(),
20
+ };
21
+ };
22
+
23
+ export const createCancellableTransport = () => {
24
+ let cancelCount = 0;
25
+ let controller!: ReadableStreamDefaultController<UIMessageChunk>;
26
+ const transport: ChatTransport<UIMessage> = {
27
+ sendMessages: async () =>
28
+ new ReadableStream<UIMessageChunk>({
29
+ start(c) {
30
+ controller = c;
31
+ },
32
+ cancel() {
33
+ cancelCount++;
34
+ },
35
+ }),
36
+ reconnectToStream: async () => null,
37
+ };
38
+ return {
39
+ transport,
40
+ getCancelCount: () => cancelCount,
41
+ close: () => controller.close(),
42
+ };
43
+ };
@@ -0,0 +1,69 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { renderHook } from "@testing-library/react";
4
+ import type { ExternalStoreAdapter } from "@assistant-ui/core";
5
+ import { describe, expect, it, vi } from "vitest";
6
+
7
+ const mocks = vi.hoisted(() => ({
8
+ adapter: undefined as ExternalStoreAdapter | undefined,
9
+ }));
10
+
11
+ vi.mock("@assistant-ui/core/react", async (importOriginal) => {
12
+ const original =
13
+ await importOriginal<typeof import("@assistant-ui/core/react")>();
14
+ return {
15
+ ...original,
16
+ useExternalStoreRuntime: vi.fn((adapter: ExternalStoreAdapter) => {
17
+ mocks.adapter = adapter;
18
+ return {};
19
+ }),
20
+ useRuntimeAdapters: vi.fn(() => ({})),
21
+ };
22
+ });
23
+
24
+ vi.mock("./useExternalHistory", async (importOriginal) => {
25
+ const original =
26
+ await importOriginal<typeof import("./useExternalHistory")>();
27
+ return {
28
+ ...original,
29
+ useExternalHistory: vi.fn(() => ({
30
+ isLoading: false,
31
+ deleteMessage: vi.fn().mockResolvedValue(undefined),
32
+ })),
33
+ };
34
+ });
35
+
36
+ import { useAISDKRuntime } from "./useAISDKRuntime";
37
+
38
+ describe("useAISDKRuntime tool approvals", () => {
39
+ it("forwards the AI SDK approval promise to the external-store adapter", () => {
40
+ const approvalPromise = Promise.resolve();
41
+ const addToolApprovalResponse = vi.fn(() => approvalPromise);
42
+ const chat = {
43
+ id: "chat-1",
44
+ status: "ready",
45
+ error: undefined,
46
+ messages: [],
47
+ setMessages: vi.fn(),
48
+ sendMessage: vi.fn(),
49
+ regenerate: vi.fn(),
50
+ addToolOutput: vi.fn(),
51
+ addToolApprovalResponse,
52
+ stop: vi.fn(),
53
+ };
54
+
55
+ renderHook(() => useAISDKRuntime(chat as never));
56
+
57
+ const result = mocks.adapter?.onRespondToToolApproval?.({
58
+ approvalId: "approval-1",
59
+ approved: true,
60
+ });
61
+
62
+ expect(result).toBe(approvalPromise);
63
+ expect(addToolApprovalResponse).toHaveBeenCalledWith({
64
+ id: "approval-1",
65
+ approved: true,
66
+ options: { metadata: undefined },
67
+ });
68
+ });
69
+ });