@assistant-ui/core 0.3.0 → 0.3.2

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 (143) hide show
  1. package/dist/internal.d.ts +1 -2
  2. package/dist/internal.js +1 -2
  3. package/dist/react/AssistantProvider.d.ts +1 -1
  4. package/dist/react/AssistantProvider.d.ts.map +1 -1
  5. package/dist/react/AssistantProvider.js +35 -9
  6. package/dist/react/AssistantProvider.js.map +1 -1
  7. package/dist/react/AssistantRuntimeProvider.d.ts +1 -1
  8. package/dist/react/AssistantRuntimeProvider.js +1 -1
  9. package/dist/react/RuntimeAdapter.js +2 -2
  10. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  11. package/dist/react/adapters/LocalStorageThreadListAdapter.js +17 -33
  12. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  13. package/dist/react/client/DataRenderers.js +1 -1
  14. package/dist/react/client/Interactables.js +1 -1
  15. package/dist/react/client/Tools.js +2 -2
  16. package/dist/react/index.d.ts +2 -1
  17. package/dist/react/index.js +2 -1
  18. package/dist/react/interactables-legacy/Interactables.js +1 -1
  19. package/dist/react/interactables-legacy/useAssistantInteractable.js +1 -1
  20. package/dist/react/interactables-legacy/useInteractableState.js +2 -2
  21. package/dist/react/model-context/useAssistantContext.js +1 -1
  22. package/dist/react/model-context/useAssistantDataUI.js +2 -2
  23. package/dist/react/model-context/useAssistantInstructions.js +2 -2
  24. package/dist/react/model-context/useAssistantTool.js +2 -2
  25. package/dist/react/model-context/useAssistantToolUI.js +2 -2
  26. package/dist/react/model-context/useAuiToolOverrides.js +1 -1
  27. package/dist/react/model-context/useInlineRender.js +1 -1
  28. package/dist/react/model-context/useInteractable.d.ts.map +1 -1
  29. package/dist/react/model-context/useInteractable.js +3 -5
  30. package/dist/react/model-context/useInteractable.js.map +1 -1
  31. package/dist/react/model-context/useInteractableState.js +2 -2
  32. package/dist/react/model-context/useInteractableVersions.js +2 -2
  33. package/dist/react/model-context/useToolArgsStatus.js +2 -2
  34. package/dist/react/primitive-hooks/useActionBarCopy.js +2 -2
  35. package/dist/react/primitive-hooks/useActionBarEdit.js +2 -2
  36. package/dist/react/primitive-hooks/useActionBarFeedback.js +2 -2
  37. package/dist/react/primitive-hooks/useActionBarReload.js +2 -2
  38. package/dist/react/primitive-hooks/useActionBarSpeak.js +2 -2
  39. package/dist/react/primitive-hooks/useActionBarStopSpeaking.js +2 -2
  40. package/dist/react/primitive-hooks/useBranchPickerNext.js +2 -2
  41. package/dist/react/primitive-hooks/useBranchPickerPrevious.js +2 -2
  42. package/dist/react/primitive-hooks/useComposerAddAttachment.js +2 -2
  43. package/dist/react/primitive-hooks/useComposerCancel.js +2 -2
  44. package/dist/react/primitive-hooks/useComposerDictate.js +2 -2
  45. package/dist/react/primitive-hooks/useComposerSend.js +2 -2
  46. package/dist/react/primitive-hooks/useEditComposerCancel.js +2 -2
  47. package/dist/react/primitive-hooks/useEditComposerSend.js +2 -2
  48. package/dist/react/primitive-hooks/useMessageBranching.js +2 -2
  49. package/dist/react/primitive-hooks/useMessageReload.js +2 -2
  50. package/dist/react/primitive-hooks/useSuggestionTrigger.js +2 -2
  51. package/dist/react/primitive-hooks/useThreadListItemArchive.js +2 -2
  52. package/dist/react/primitive-hooks/useThreadListItemDelete.js +2 -2
  53. package/dist/react/primitive-hooks/useThreadListItemTrigger.js +2 -2
  54. package/dist/react/primitive-hooks/useThreadListItemUnarchive.js +2 -2
  55. package/dist/react/primitive-hooks/useThreadListLoadMore.js +2 -2
  56. package/dist/react/primitive-hooks/useThreadListNew.js +2 -2
  57. package/dist/react/primitive-hooks/useThreadMessageIds.js +1 -1
  58. package/dist/react/primitive-hooks/useVoice.js +2 -2
  59. package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.js +2 -2
  60. package/dist/react/primitives/composer/ComposerAttachments.js +2 -2
  61. package/dist/react/primitives/composer/ComposerIf.js +1 -1
  62. package/dist/react/primitives/composer/ComposerQueue.js +1 -1
  63. package/dist/react/primitives/generativeUI/GenerativeUI.js +2 -2
  64. package/dist/react/primitives/message/MessageAttachments.js +2 -2
  65. package/dist/react/primitives/message/MessageGroupedParts.js +1 -1
  66. package/dist/react/primitives/message/MessageParts.js +2 -2
  67. package/dist/react/primitives/message/MessageQuote.js +2 -2
  68. package/dist/react/primitives/part/PartMessages.js +2 -2
  69. package/dist/react/primitives/thread/ThreadMessages.js +2 -2
  70. package/dist/react/primitives/thread/ThreadSuggestions.js +2 -2
  71. package/dist/react/primitives/threadList/ThreadListItems.js +2 -2
  72. package/dist/react/primitives/threadListItem/ThreadListItemTitle.js +1 -1
  73. package/dist/react/providers/AttachmentByIndexProvider.js +1 -1
  74. package/dist/react/providers/ChainOfThoughtByIndicesProvider.js +1 -1
  75. package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js +1 -1
  76. package/dist/react/providers/MessageByIdProvider.js +1 -1
  77. package/dist/react/providers/MessageByIndexProvider.js +1 -1
  78. package/dist/react/providers/PartByIndexProvider.js +1 -1
  79. package/dist/react/providers/QueueItemByIndexProvider.js +1 -1
  80. package/dist/react/providers/ReadonlyThreadProvider.js +2 -2
  81. package/dist/react/providers/SuggestionByIndexProvider.js +1 -1
  82. package/dist/react/providers/TextMessagePartProvider.js +2 -2
  83. package/dist/react/providers/ThreadListItemByIndexProvider.js +1 -1
  84. package/dist/react/providers/ThreadListItemRuntimeProvider.js +1 -1
  85. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  86. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +14 -13
  87. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  88. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +3 -0
  89. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  90. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +22 -12
  91. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  92. package/dist/react/runtimes/RuntimeAdapterProvider.js +1 -1
  93. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +9 -23
  94. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  95. package/dist/react/runtimes/external-message-converter.js +1 -1
  96. package/dist/react/runtimes/useExternalStoreRuntime.js +1 -1
  97. package/dist/react/runtimes/useExternalStoreSharedOptions.js +1 -1
  98. package/dist/react/runtimes/useLocalRuntime.js +1 -1
  99. package/dist/react/runtimes/useRemoteThreadListRuntime.js +7 -13
  100. package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
  101. package/dist/react/utils/useJSONEqual.js +1 -1
  102. package/dist/runtime/utils/thread-message-like.js +2 -2
  103. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  104. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  105. package/dist/runtimes/local/local-thread-runtime-core.js +4 -3
  106. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  107. package/dist/runtimes/remote-thread-list/types.d.ts +5 -4
  108. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  109. package/dist/store/clients/chain-of-thought-client.js +1 -1
  110. package/dist/store/clients/model-context-client.js +1 -1
  111. package/dist/store/clients/no-op-composer-client.js +1 -1
  112. package/dist/store/clients/suggestions.js +2 -2
  113. package/dist/store/clients/thread-message-client.js +2 -2
  114. package/dist/store/runtime-clients/composer-runtime-client.js +2 -2
  115. package/dist/store/runtime-clients/message-runtime-client.js +2 -2
  116. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +2 -2
  117. package/dist/store/runtime-clients/thread-list-runtime-client.js +2 -2
  118. package/dist/store/runtime-clients/thread-runtime-client.js +2 -2
  119. package/dist/store/scopes/thread.d.ts +1 -1
  120. package/dist/store/scopes/thread.d.ts.map +1 -1
  121. package/dist/utils/data-url.js +2 -2
  122. package/dist/utils/data-url.js.map +1 -1
  123. package/package.json +4 -4
  124. package/src/internal.ts +0 -7
  125. package/src/react/AssistantProvider.tsx +28 -15
  126. package/src/react/AssistantRuntimeProvider.test.tsx +101 -0
  127. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +28 -7
  128. package/src/react/index.ts +4 -0
  129. package/src/react/model-context/useInteractable.ts +2 -6
  130. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +16 -13
  131. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +53 -10
  132. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +7 -5
  133. package/src/react/runtimes/useRemoteThreadListRuntime.ts +9 -15
  134. package/src/runtime/utils/thread-message-like.ts +2 -2
  135. package/src/runtimes/local/local-thread-runtime-core.test.ts +38 -0
  136. package/src/runtimes/local/local-thread-runtime-core.ts +6 -3
  137. package/src/runtimes/remote-thread-list/types.ts +5 -4
  138. package/src/store/scopes/thread.ts +1 -1
  139. package/src/tests/remote-thread-list-reactive-threadId.test.ts +0 -92
  140. package/src/tests/thread-message-like.test.ts +15 -0
  141. package/src/tests/useRemoteThreadListRuntime-controlled.test.tsx +293 -0
  142. package/src/utils/data-url.test.ts +26 -2
  143. package/src/utils/data-url.ts +2 -2
@@ -0,0 +1,293 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { act, render, waitFor } from "@testing-library/react";
4
+ import { useEffect } from "react";
5
+ import { describe, expect, it, vi } from "vitest";
6
+ import { AssistantRuntimeProvider } from "../react/AssistantRuntimeProvider";
7
+ import { useExternalStoreRuntime } from "../react/runtimes/useExternalStoreRuntime";
8
+ import { useRemoteThreadListRuntime } from "../react/runtimes/useRemoteThreadListRuntime";
9
+ import type { AssistantRuntime } from "../runtime/api/assistant-runtime";
10
+ import type {
11
+ RemoteThreadListAdapter,
12
+ RemoteThreadMetadata,
13
+ } from "../runtimes/remote-thread-list/types";
14
+ import { deferred, makeAdapter } from "./remote-thread-list-test-helpers";
15
+
16
+ const EMPTY_MESSAGES: readonly never[] = [];
17
+
18
+ const makeThreadMetadata = (remoteId: string): RemoteThreadMetadata => ({
19
+ status: "regular",
20
+ remoteId,
21
+ externalId: remoteId,
22
+ title: "Test",
23
+ });
24
+
25
+ const useTestThreadRuntime = () =>
26
+ useExternalStoreRuntime({
27
+ messages: EMPTY_MESSAGES,
28
+ isRunning: false,
29
+ onNew: async () => {},
30
+ });
31
+
32
+ type RuntimeRef = {
33
+ current: AssistantRuntime | null;
34
+ };
35
+
36
+ const ControlledRuntime = ({
37
+ adapter,
38
+ threadId,
39
+ onThreadIdChange,
40
+ runtimeRef,
41
+ }: {
42
+ adapter: RemoteThreadListAdapter;
43
+ threadId: string | undefined;
44
+ onThreadIdChange: (threadId: string | undefined) => void;
45
+ runtimeRef: RuntimeRef;
46
+ }) => {
47
+ const runtime = useRemoteThreadListRuntime({
48
+ adapter,
49
+ threadId,
50
+ onThreadIdChange,
51
+ runtimeHook: useTestThreadRuntime,
52
+ });
53
+
54
+ useEffect(() => {
55
+ runtimeRef.current = runtime;
56
+ }, [runtime, runtimeRef]);
57
+
58
+ return (
59
+ <AssistantRuntimeProvider runtime={runtime}>
60
+ {null}
61
+ </AssistantRuntimeProvider>
62
+ );
63
+ };
64
+
65
+ const waitForRemoteThread = async (
66
+ runtimeRef: RuntimeRef,
67
+ remoteId: string,
68
+ ) => {
69
+ await waitFor(() => {
70
+ expect(runtimeRef.current).not.toBeNull();
71
+ expect(runtimeRef.current!.threads.mainItem.getState().remoteId).toBe(
72
+ remoteId,
73
+ );
74
+ });
75
+ };
76
+
77
+ describe("useRemoteThreadListRuntime controlled threadId", () => {
78
+ it("treats an initial empty string as a supplied thread ID", async () => {
79
+ const adapter = makeAdapter();
80
+ const onThreadIdChange = vi.fn();
81
+ const runtimeRef: RuntimeRef = { current: null };
82
+
83
+ render(
84
+ <ControlledRuntime
85
+ adapter={adapter}
86
+ threadId=""
87
+ onThreadIdChange={onThreadIdChange}
88
+ runtimeRef={runtimeRef}
89
+ />,
90
+ );
91
+
92
+ await waitForRemoteThread(runtimeRef, "");
93
+ expect(adapter.fetch).toHaveBeenCalledWith("");
94
+ expect(onThreadIdChange).not.toHaveBeenCalled();
95
+ });
96
+
97
+ it("switches to an empty string supplied after mount", async () => {
98
+ const adapter = makeAdapter();
99
+ const onThreadIdChange = vi.fn();
100
+ const runtimeRef: RuntimeRef = { current: null };
101
+
102
+ const { rerender } = render(
103
+ <ControlledRuntime
104
+ adapter={adapter}
105
+ threadId="thread-a"
106
+ onThreadIdChange={onThreadIdChange}
107
+ runtimeRef={runtimeRef}
108
+ />,
109
+ );
110
+ await waitForRemoteThread(runtimeRef, "thread-a");
111
+
112
+ rerender(
113
+ <ControlledRuntime
114
+ adapter={adapter}
115
+ threadId=""
116
+ onThreadIdChange={onThreadIdChange}
117
+ runtimeRef={runtimeRef}
118
+ />,
119
+ );
120
+ await waitForRemoteThread(runtimeRef, "");
121
+ expect(adapter.fetch).toHaveBeenLastCalledWith("");
122
+ expect(onThreadIdChange).not.toHaveBeenCalled();
123
+ });
124
+
125
+ it("does not echo prop-driven thread switches", async () => {
126
+ const adapter = makeAdapter();
127
+ const onThreadIdChange = vi.fn();
128
+ const runtimeRef: RuntimeRef = { current: null };
129
+
130
+ const { rerender } = render(
131
+ <ControlledRuntime
132
+ adapter={adapter}
133
+ threadId="thread-a"
134
+ onThreadIdChange={onThreadIdChange}
135
+ runtimeRef={runtimeRef}
136
+ />,
137
+ );
138
+
139
+ await waitForRemoteThread(runtimeRef, "thread-a");
140
+ expect(onThreadIdChange).not.toHaveBeenCalled();
141
+
142
+ rerender(
143
+ <ControlledRuntime
144
+ adapter={adapter}
145
+ threadId="thread-b"
146
+ onThreadIdChange={onThreadIdChange}
147
+ runtimeRef={runtimeRef}
148
+ />,
149
+ );
150
+
151
+ await waitForRemoteThread(runtimeRef, "thread-b");
152
+ expect(onThreadIdChange).not.toHaveBeenCalled();
153
+
154
+ const previousMainThreadId =
155
+ runtimeRef.current!.threads.getState().mainThreadId;
156
+ rerender(
157
+ <ControlledRuntime
158
+ adapter={adapter}
159
+ threadId={undefined}
160
+ onThreadIdChange={onThreadIdChange}
161
+ runtimeRef={runtimeRef}
162
+ />,
163
+ );
164
+
165
+ await waitFor(() => {
166
+ expect(runtimeRef.current!.threads.getState().mainThreadId).not.toBe(
167
+ previousMainThreadId,
168
+ );
169
+ expect(
170
+ runtimeRef.current!.threads.mainItem.getState().remoteId,
171
+ ).toBeUndefined();
172
+ });
173
+ expect(onThreadIdChange).not.toHaveBeenCalled();
174
+ });
175
+
176
+ it("still emits runtime-initiated thread switches", async () => {
177
+ const adapter = makeAdapter();
178
+ const onThreadIdChange = vi.fn();
179
+ const runtimeRef: RuntimeRef = { current: null };
180
+
181
+ render(
182
+ <ControlledRuntime
183
+ adapter={adapter}
184
+ threadId="thread-a"
185
+ onThreadIdChange={onThreadIdChange}
186
+ runtimeRef={runtimeRef}
187
+ />,
188
+ );
189
+
190
+ await waitForRemoteThread(runtimeRef, "thread-a");
191
+ onThreadIdChange.mockClear();
192
+
193
+ await act(async () => {
194
+ await runtimeRef.current!.threads.switchToThread("thread-b");
195
+ });
196
+ expect(onThreadIdChange).toHaveBeenLastCalledWith("thread-b");
197
+
198
+ onThreadIdChange.mockClear();
199
+ await act(async () => {
200
+ await runtimeRef.current!.threads.switchToNewThread();
201
+ });
202
+ expect(onThreadIdChange).toHaveBeenLastCalledWith(undefined);
203
+ });
204
+
205
+ it("does not retain suppression after an initial switch fails", async () => {
206
+ let threadAFetchCount = 0;
207
+ const adapter = makeAdapter({
208
+ fetch: vi.fn(async (threadId) => {
209
+ if (threadId === "thread-a" && threadAFetchCount++ === 0) {
210
+ throw new Error("initial fetch failed");
211
+ }
212
+ return makeThreadMetadata(threadId);
213
+ }),
214
+ });
215
+ const onThreadIdChange = vi.fn();
216
+ const runtimeRef: RuntimeRef = { current: null };
217
+
218
+ render(
219
+ <ControlledRuntime
220
+ adapter={adapter}
221
+ threadId="thread-a"
222
+ onThreadIdChange={onThreadIdChange}
223
+ runtimeRef={runtimeRef}
224
+ />,
225
+ );
226
+
227
+ await waitFor(() => {
228
+ expect(adapter.fetch).toHaveBeenCalledWith("thread-a");
229
+ expect(runtimeRef.current).not.toBeNull();
230
+ });
231
+
232
+ await act(async () => {
233
+ await runtimeRef.current!.threads.switchToThread("thread-b");
234
+ });
235
+ expect(onThreadIdChange).toHaveBeenLastCalledWith("thread-b");
236
+
237
+ onThreadIdChange.mockClear();
238
+ await act(async () => {
239
+ await runtimeRef.current!.threads.switchToThread("thread-a");
240
+ });
241
+ expect(onThreadIdChange).toHaveBeenLastCalledWith("thread-a");
242
+ });
243
+
244
+ it("emits a concurrent runtime switch to the controlled target", async () => {
245
+ const firstThreadBFetch = deferred<RemoteThreadMetadata>();
246
+ let threadBFetchCount = 0;
247
+ const adapter = makeAdapter({
248
+ fetch: vi.fn(async (threadId) => {
249
+ if (threadId === "thread-b" && threadBFetchCount++ === 0) {
250
+ return firstThreadBFetch.promise;
251
+ }
252
+ return makeThreadMetadata(threadId);
253
+ }),
254
+ });
255
+ const onThreadIdChange = vi.fn();
256
+ const runtimeRef: RuntimeRef = { current: null };
257
+
258
+ const { rerender } = render(
259
+ <ControlledRuntime
260
+ adapter={adapter}
261
+ threadId="thread-a"
262
+ onThreadIdChange={onThreadIdChange}
263
+ runtimeRef={runtimeRef}
264
+ />,
265
+ );
266
+
267
+ await waitForRemoteThread(runtimeRef, "thread-a");
268
+ onThreadIdChange.mockClear();
269
+
270
+ rerender(
271
+ <ControlledRuntime
272
+ adapter={adapter}
273
+ threadId="thread-b"
274
+ onThreadIdChange={onThreadIdChange}
275
+ runtimeRef={runtimeRef}
276
+ />,
277
+ );
278
+ await waitFor(() => {
279
+ expect(adapter.fetch).toHaveBeenCalledWith("thread-b");
280
+ });
281
+
282
+ await act(async () => {
283
+ await runtimeRef.current!.threads.switchToThread("thread-b");
284
+ });
285
+ expect(onThreadIdChange).toHaveBeenCalledExactlyOnceWith("thread-b");
286
+
287
+ await act(async () => {
288
+ firstThreadBFetch.resolve(makeThreadMetadata("thread-b"));
289
+ await firstThreadBFetch.promise;
290
+ });
291
+ expect(onThreadIdChange).toHaveBeenCalledExactlyOnceWith("thread-b");
292
+ });
293
+ });
@@ -20,8 +20,32 @@ describe("parseDataUrl", () => {
20
20
  expect(parseDataUrl("data:text/plain,hello")).toBeNull();
21
21
  });
22
22
 
23
- it("returns null for data URLs with an empty payload", () => {
24
- expect(parseDataUrl("data:image/png;base64,")).toBeNull();
23
+ it("parses a data URL with an empty payload as zero-byte data", () => {
24
+ expect(parseDataUrl("data:image/png;base64,")).toEqual({
25
+ mimeType: "image/png",
26
+ data: "",
27
+ });
28
+ });
29
+
30
+ it("parses an uppercase scheme", () => {
31
+ expect(parseDataUrl("DATA:image/png;base64,aGVsbG8=")).toEqual({
32
+ mimeType: "image/png",
33
+ data: "aGVsbG8=",
34
+ });
35
+ });
36
+
37
+ it("parses a mixed-case scheme and base64 token", () => {
38
+ expect(parseDataUrl("Data:image/png;Base64,aGVsbG8=")).toEqual({
39
+ mimeType: "image/png",
40
+ data: "aGVsbG8=",
41
+ });
42
+ });
43
+
44
+ it("lowercases the captured mime type", () => {
45
+ expect(parseDataUrl("data:IMAGE/PNG;base64,aGVsbG8=")).toEqual({
46
+ mimeType: "image/png",
47
+ data: "aGVsbG8=",
48
+ });
25
49
  });
26
50
 
27
51
  it("returns null for plain strings", () => {
@@ -3,7 +3,7 @@ export const httpUrlPattern = /^https?:\/\//i;
3
3
  export function parseDataUrl(
4
4
  value: string,
5
5
  ): { mimeType: string; data: string } | null {
6
- const match = value.match(/^data:([^;,]+)(?:;[^;,]+)*;base64,(.+)$/);
6
+ const match = value.match(/^data:([^;,]+)(?:;[^;,]+)*;base64,(.*)$/i);
7
7
  if (!match) return null;
8
- return { mimeType: match[1]!, data: match[2]! };
8
+ return { mimeType: match[1]!.toLowerCase(), data: match[2]! };
9
9
  }