@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,193 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { flushTapSync } from "@assistant-ui/tap";
3
+ import { AuiConfig, createAssistantClient } from "@assistant-ui/store/client";
4
+ import { AISDKChat } from "./AISDKChat";
5
+ import {
6
+ createCancellableTransport,
7
+ createControlledTransport,
8
+ } from "./__tests__/controlled-transport";
9
+
10
+ describe("AISDKChat as a standalone client config entry", () => {
11
+ afterEach(() => {
12
+ vi.unstubAllGlobals();
13
+ });
14
+
15
+ it("streams a chat round trip without React", async () => {
16
+ const { transport, emit, close } = createControlledTransport();
17
+ const handle = createAssistantClient(
18
+ AuiConfig({ threads: AISDKChat({ transport }) }),
19
+ );
20
+ try {
21
+ handle.subscribe(() => {});
22
+ const aui = handle.getClient();
23
+
24
+ expect(aui.thread.getState().messages).toHaveLength(0);
25
+
26
+ flushTapSync(() => aui.composer.setText("hi"));
27
+ flushTapSync(() => aui.composer.send());
28
+
29
+ await vi.waitFor(() => {
30
+ const state = aui.thread.getState();
31
+ expect(state.isRunning).toBe(true);
32
+ expect(state.messages[0]).toMatchObject({ role: "user" });
33
+ });
34
+
35
+ emit(
36
+ { type: "start" },
37
+ { type: "text-start", id: "t1" },
38
+ { type: "text-delta", id: "t1", delta: "hello " },
39
+ );
40
+ await vi.waitFor(() => {
41
+ const last = aui.thread.getState().messages.at(-1);
42
+ expect(last?.role).toBe("assistant");
43
+ expect(last?.content).toContainEqual(
44
+ expect.objectContaining({ type: "text", text: "hello " }),
45
+ );
46
+ });
47
+
48
+ emit(
49
+ { type: "text-delta", id: "t1", delta: "world" },
50
+ { type: "text-end", id: "t1" },
51
+ { type: "finish" },
52
+ );
53
+ close();
54
+
55
+ await vi.waitFor(() => {
56
+ const state = aui.thread.getState();
57
+ expect(state.isRunning).toBe(false);
58
+ expect(state.messages).toHaveLength(2);
59
+ expect(state.messages.at(-1)?.content).toContainEqual(
60
+ expect.objectContaining({ type: "text", text: "hello world" }),
61
+ );
62
+ });
63
+ } finally {
64
+ handle.destroy();
65
+ }
66
+ });
67
+
68
+ it("stops an in-flight chat when its client is destroyed", async () => {
69
+ const { transport, getCancelCount } = createCancellableTransport();
70
+ const handle = createAssistantClient(
71
+ AuiConfig({ threads: AISDKChat({ transport }) }),
72
+ );
73
+ handle.subscribe(() => {});
74
+ const aui = handle.getClient();
75
+
76
+ try {
77
+ flushTapSync(() => aui.composer.setText("stop me"));
78
+ flushTapSync(() => aui.composer.send());
79
+ await vi.waitFor(() => {
80
+ expect(aui.thread.getState().isRunning).toBe(true);
81
+ });
82
+ } finally {
83
+ handle.destroy();
84
+ }
85
+
86
+ await vi.waitFor(() => {
87
+ expect(getCancelCount()).toBe(1);
88
+ });
89
+ });
90
+
91
+ it("stops a soft-unmounted chat on later client destruction", async () => {
92
+ const { transport, getCancelCount } = createCancellableTransport();
93
+ const handle = createAssistantClient(
94
+ AuiConfig({ threads: AISDKChat({ transport }) }),
95
+ );
96
+ const release = handle.subscribe(() => {});
97
+ const aui = handle.getClient();
98
+
99
+ flushTapSync(() => aui.composer.setText("keep streaming"));
100
+ flushTapSync(() => aui.composer.send());
101
+ await vi.waitFor(() => {
102
+ expect(aui.thread.getState().isRunning).toBe(true);
103
+ });
104
+
105
+ release();
106
+ await new Promise((resolve) => setTimeout(resolve, 0));
107
+ expect(getCancelCount()).toBe(0);
108
+
109
+ handle.destroy();
110
+ await vi.waitFor(() => {
111
+ expect(getCancelCount()).toBe(1);
112
+ });
113
+ });
114
+
115
+ it("installs the RuntimeAdapter scope defaults", () => {
116
+ const { transport } = createControlledTransport();
117
+ const handle = createAssistantClient(
118
+ AuiConfig({ threads: AISDKChat({ transport }) }),
119
+ );
120
+ try {
121
+ handle.subscribe(() => {});
122
+ const aui = handle.getClient();
123
+
124
+ expect(aui.threads.getState().mainThreadId).toBeDefined();
125
+ expect(aui.tools.getState()).toBeDefined();
126
+ expect(aui.dataRenderers.getState()).toBeDefined();
127
+ expect(aui.thread.getState().isRunning).toBe(false);
128
+ } finally {
129
+ handle.destroy();
130
+ }
131
+ });
132
+
133
+ it("mounts with the default transport when no options are given", () => {
134
+ const handle = createAssistantClient(AuiConfig({ threads: AISDKChat({}) }));
135
+ try {
136
+ handle.subscribe(() => {});
137
+ const aui = handle.getClient();
138
+
139
+ expect(aui.thread.getState().messages).toHaveLength(0);
140
+ expect(aui.thread.getState().isRunning).toBe(false);
141
+ } finally {
142
+ handle.destroy();
143
+ }
144
+ });
145
+
146
+ it("sends through the default AssistantChatTransport with the generated id", async () => {
147
+ const sse = [
148
+ { type: "start" },
149
+ { type: "text-start", id: "t1" },
150
+ { type: "text-delta", id: "t1", delta: "ok" },
151
+ { type: "text-end", id: "t1" },
152
+ { type: "finish" },
153
+ ]
154
+ .map((chunk) => `data: ${JSON.stringify(chunk)}\n\n`)
155
+ .join("");
156
+ const fetchMock = vi.fn(
157
+ async () =>
158
+ new Response(sse, {
159
+ headers: { "content-type": "text/event-stream" },
160
+ }),
161
+ );
162
+ vi.stubGlobal("fetch", fetchMock);
163
+
164
+ const handle = createAssistantClient(
165
+ AuiConfig({ threads: AISDKChat({ id: "test-thread-1" }) }),
166
+ );
167
+ try {
168
+ handle.subscribe(() => {});
169
+ const aui = handle.getClient();
170
+
171
+ flushTapSync(() => aui.composer.setText("hi"));
172
+ flushTapSync(() => aui.composer.send());
173
+
174
+ await vi.waitFor(() => {
175
+ expect(fetchMock).toHaveBeenCalledTimes(1);
176
+ const state = aui.thread.getState();
177
+ expect(state.isRunning).toBe(false);
178
+ expect(state.messages).toHaveLength(2);
179
+ });
180
+
181
+ const [url, init] = fetchMock.mock.calls[0]! as [
182
+ RequestInfo,
183
+ RequestInit,
184
+ ];
185
+ expect(String(url)).toContain("/api/chat");
186
+ const body = JSON.parse(init.body as string);
187
+ expect(body.id).toBe("test-thread-1");
188
+ expect(body.messages).toHaveLength(1);
189
+ } finally {
190
+ handle.destroy();
191
+ }
192
+ });
193
+ });
@@ -0,0 +1,47 @@
1
+ "use client";
2
+
3
+ import { resource, useResource } from "@assistant-ui/tap";
4
+ import { useState } from "react";
5
+ import { generateId } from "ai";
6
+ import type { UIMessage } from "@ai-sdk/react";
7
+ import {
8
+ RuntimeAdapter,
9
+ runtimeAdapterTransformScopes,
10
+ } from "@assistant-ui/core/store";
11
+ import { attachTransformScopes } from "@assistant-ui/store/client";
12
+ import { useChatThread, type ChatThreadOptions } from "./useChatThread";
13
+
14
+ export type AISDKChatOptions<UI_MESSAGE extends UIMessage = UIMessage> =
15
+ ChatThreadOptions<UI_MESSAGE>;
16
+
17
+ const useAISDKChat = <UI_MESSAGE extends UIMessage = UIMessage>(
18
+ options?: AISDKChatOptions<UI_MESSAGE>,
19
+ ) => {
20
+ const [id] = useState(() => options?.id ?? generateId());
21
+ // The transport resolves the request id from the thread list item, falling
22
+ // back to the runtime's main item, whose id here is the external store's
23
+ // placeholder constant. The single thread of this entry is the chat itself,
24
+ // so the handed-over item initializes to the chat id.
25
+ const [threadListItem] = useState(() => ({
26
+ initialize: async () => ({ remoteId: id, externalId: undefined }),
27
+ }));
28
+ const runtime = useChatThread(options, {
29
+ id,
30
+ isMainThread: true,
31
+ getThreadListItem: () => threadListItem,
32
+ stopOnClientDestroy: true,
33
+ });
34
+ return useResource(RuntimeAdapter(runtime));
35
+ };
36
+
37
+ /**
38
+ * `AuiConfig` entry that runs the AI SDK chat as the `threads` scope. Hosts the
39
+ * same orchestration as `useChatRuntime` inside the client's own resource tree,
40
+ * so it works with any `AssistantClient` host, React or not. Single thread; the
41
+ * multi-thread and assistant-cloud surface is {@link AISDKThreads}. The chat
42
+ * id is captured when the entry mounts, so a later `id` change in the options
43
+ * has no effect.
44
+ */
45
+ export const AISDKChat = resource(useAISDKChat);
46
+
47
+ attachTransformScopes(useAISDKChat, runtimeAdapterTransformScopes);
@@ -0,0 +1,136 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { flushTapSync } from "@assistant-ui/tap";
5
+ import { AuiConfig, createAssistantClient } from "@assistant-ui/store/client";
6
+ import type { AssistantCloud } from "assistant-cloud";
7
+ import type { RemoteThreadListAdapter } from "@assistant-ui/core";
8
+ import type { ThreadHistoryAdapter } from "@assistant-ui/core";
9
+
10
+ const load = vi.hoisted(() => vi.fn(async () => ({ messages: [] })));
11
+ const append = vi.hoisted(() => vi.fn(async () => {}));
12
+
13
+ const mocks = vi.hoisted(() => {
14
+ const history: ThreadHistoryAdapter = {
15
+ load: async () => ({ messages: [] }),
16
+ append: async () => {},
17
+ withFormat: () => ({
18
+ load,
19
+ append,
20
+ delete: async () => {},
21
+ reportTelemetry: () => {},
22
+ }),
23
+ };
24
+ const adapter: RemoteThreadListAdapter = {
25
+ list: vi.fn(async () => ({
26
+ threads: [
27
+ { status: "regular" as const, remoteId: "t1", title: "One" },
28
+ { status: "regular" as const, remoteId: "t2", title: "Two" },
29
+ ],
30
+ })),
31
+ initialize: vi.fn(async (threadId: string) => ({
32
+ remoteId: threadId,
33
+ externalId: undefined,
34
+ })),
35
+ rename: vi.fn(async () => {}),
36
+ archive: vi.fn(async () => {}),
37
+ unarchive: vi.fn(async () => {}),
38
+ delete: vi.fn(async () => {}),
39
+ generateTitle: vi.fn(
40
+ async () =>
41
+ new ReadableStream({
42
+ start(controller) {
43
+ controller.close();
44
+ },
45
+ }) as never,
46
+ ),
47
+ fetch: vi.fn(async (id: string) => ({
48
+ status: "regular" as const,
49
+ remoteId: id,
50
+ externalId: undefined,
51
+ title: id,
52
+ })),
53
+ unstable_useAdapters: function useAdapters() {
54
+ return { history };
55
+ },
56
+ };
57
+ return { adapter };
58
+ });
59
+
60
+ vi.mock("@assistant-ui/core/react", async (importOriginal) => ({
61
+ ...(await importOriginal<typeof import("@assistant-ui/core/react")>()),
62
+ useCloudThreadListAdapter: () => mocks.adapter,
63
+ }));
64
+
65
+ import { AISDKThreads } from "./AISDKThreads";
66
+ import { createCancellableTransport } from "./__tests__/controlled-transport";
67
+
68
+ describe("AISDKThreads cloud", () => {
69
+ it("reloads history when switching a keyed cloud thread", async () => {
70
+ const handle = createAssistantClient(
71
+ AuiConfig({
72
+ threads: AISDKThreads({
73
+ cloud: {} as AssistantCloud,
74
+ threadId: "t1",
75
+ }),
76
+ }),
77
+ );
78
+ handle.subscribe(() => {});
79
+ const aui = handle.getClient();
80
+ await aui.threads.getLoadThreadsPromise();
81
+ await vi.waitFor(() => {
82
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t1");
83
+ });
84
+ await vi.waitFor(() => {
85
+ expect(load).toHaveBeenCalled();
86
+ });
87
+ const afterFirst = load.mock.calls.length;
88
+ flushTapSync(() => aui.threads.switchToThread("t2"));
89
+ await vi.waitFor(() => {
90
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t2");
91
+ });
92
+ await vi.waitFor(() => {
93
+ expect(load.mock.calls.length).toBeGreaterThan(afterFirst);
94
+ });
95
+ handle.destroy();
96
+ });
97
+
98
+ it("stops an in-flight cloud chat when switching away", async () => {
99
+ const chat = createCancellableTransport();
100
+ const handle = createAssistantClient(
101
+ AuiConfig({
102
+ threads: AISDKThreads({
103
+ cloud: {} as AssistantCloud,
104
+ threadId: "t1",
105
+ transport: () => chat.transport,
106
+ }),
107
+ }),
108
+ );
109
+ handle.subscribe(() => {});
110
+ const aui = handle.getClient();
111
+ try {
112
+ await aui.threads.getLoadThreadsPromise();
113
+ await vi.waitFor(() => {
114
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t1");
115
+ });
116
+ await vi.waitFor(() => expect(load).toHaveBeenCalled());
117
+ await vi.waitFor(() => {
118
+ expect(handle.getClient().thread.getState().isLoading).toBe(false);
119
+ });
120
+ flushTapSync(() => handle.getClient().composer.setText("stream me"));
121
+ flushTapSync(() => handle.getClient().composer.send());
122
+ await vi.waitFor(() => {
123
+ expect(handle.getClient().thread.getState().isRunning).toBe(true);
124
+ });
125
+ flushTapSync(() => handle.getClient().threads.switchToThread("t2"));
126
+ await vi.waitFor(() => {
127
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t2");
128
+ });
129
+ await vi.waitFor(() => {
130
+ expect(chat.getCancelCount()).toBe(1);
131
+ });
132
+ } finally {
133
+ handle.destroy();
134
+ }
135
+ });
136
+ });