@assistant-ui/core 0.2.21 → 0.2.22
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/adapters/attachment.d.ts.map +1 -1
- package/dist/adapters/speech.d.ts.map +1 -1
- package/dist/adapters/speech.js +9 -2
- package/dist/adapters/speech.js.map +1 -1
- package/dist/adapters/voice.d.ts.map +1 -1
- package/dist/adapters/voice.js +13 -5
- package/dist/adapters/voice.js.map +1 -1
- package/dist/internal.d.ts +3 -1
- package/dist/internal.js +3 -1
- package/dist/model-context/frame/host.d.ts +1 -0
- package/dist/model-context/frame/host.d.ts.map +1 -1
- package/dist/model-context/frame/host.js +5 -0
- package/dist/model-context/frame/host.js.map +1 -1
- package/dist/model-context/frame/provider.d.ts.map +1 -1
- package/dist/model-context/frame/provider.js +1 -0
- package/dist/model-context/frame/provider.js.map +1 -1
- package/dist/model-context/interactable-composer-metadata.js.map +1 -1
- package/dist/react/AssistantProvider.js.map +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.js +77 -50
- package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
- package/dist/react/client/Interactables.js.map +1 -1
- package/dist/react/client/Tools.d.ts.map +1 -1
- package/dist/react/client/interactable-model-context.d.ts.map +1 -1
- package/dist/react/model-context/useInteractable.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarCopy.js +13 -13
- package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
- package/dist/react/primitive-hooks/useComposerAddAttachment.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
- package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.d.ts.map +1 -1
- package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.js +1 -1
- package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.js.map +1 -1
- package/dist/react/primitives/composer/ComposerAttachments.js.map +1 -1
- package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
- package/dist/react/primitives/composer/ComposerQueue.js.map +1 -1
- package/dist/react/primitives/generativeUI/GenerativeUI.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
- package/dist/react/primitives/message/MessageParts.d.ts +2 -1
- package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageParts.js +46 -36
- package/dist/react/primitives/message/MessageParts.js.map +1 -1
- package/dist/react/primitives/part/PartMessages.js.map +1 -1
- package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
- package/dist/react/primitives/thread/ThreadSuggestions.js.map +1 -1
- package/dist/react/primitives/threadList/ThreadListItems.js.map +1 -1
- package/dist/react/providers/ChainOfThoughtPartByIndexProvider.d.ts.map +1 -1
- package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js +18 -30
- package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js.map +1 -1
- package/dist/react/providers/PartByIndexProvider.d.ts.map +1 -1
- package/dist/react/providers/PartByIndexProvider.js +18 -30
- package/dist/react/providers/PartByIndexProvider.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +1 -0
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +9 -2
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +49 -8
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js +2 -1
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js.map +1 -1
- package/dist/react/runtimes/createMessageConverter.d.ts.map +1 -1
- package/dist/react/runtimes/external-message-converter.js +1 -1
- package/dist/react/runtimes/external-message-converter.js.map +1 -1
- package/dist/react/runtimes/useLocalRuntime.d.ts +1 -0
- package/dist/react/runtimes/useLocalRuntime.d.ts.map +1 -1
- package/dist/react/runtimes/useLocalRuntime.js +9 -1
- package/dist/react/runtimes/useLocalRuntime.js.map +1 -1
- package/dist/runtime/api/assistant-runtime.d.ts.map +1 -1
- package/dist/runtime/api/attachment-runtime.d.ts.map +1 -1
- package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
- package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
- package/dist/runtime/api/message-runtime.d.ts.map +1 -1
- package/dist/runtime/api/message-runtime.js +4 -4
- package/dist/runtime/api/message-runtime.js.map +1 -1
- package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-list-item-runtime.js +2 -1
- package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
- package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts +4 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js +41 -14
- package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.js +2 -1
- package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.js +4 -2
- package/dist/runtime/base/default-edit-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.js +2 -2
- package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
- package/dist/runtime/utils/message-repository.d.ts.map +1 -1
- package/dist/runtime/utils/thread-message-like.js +4 -1
- package/dist/runtime/utils/thread-message-like.js.map +1 -1
- package/dist/runtimes/external-store/external-store-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/local/local-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +5 -2
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/optimistic-state.d.ts +4 -4
- package/dist/runtimes/remote-thread-list/optimistic-state.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/optimistic-state.js +22 -9
- package/dist/runtimes/remote-thread-list/optimistic-state.js.map +1 -1
- package/dist/runtimes/remote-thread-list/types.d.ts +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +1 -2
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
- package/dist/store/clients/chain-of-thought-client.d.ts.map +1 -1
- package/dist/store/clients/no-op-composer-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/attachment-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/message-part-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
- package/dist/subscribable/subscribable.d.ts +1 -1
- package/dist/subscribable/subscribable.d.ts.map +1 -1
- package/dist/subscribable/subscribable.js +11 -3
- package/dist/subscribable/subscribable.js.map +1 -1
- package/dist/tests/remote-thread-list-test-helpers.d.ts +2 -1
- package/dist/tests/remote-thread-list-test-helpers.d.ts.map +1 -1
- package/dist/tests/remote-thread-list-test-helpers.js +5 -2
- package/dist/tests/remote-thread-list-test-helpers.js.map +1 -1
- package/dist/types/attachment.d.ts +2 -1
- package/dist/types/attachment.d.ts.map +1 -1
- package/dist/types/attachment.js +6 -0
- package/dist/types/attachment.js.map +1 -0
- package/dist/types/message.d.ts +1 -0
- package/dist/types/message.d.ts.map +1 -1
- package/dist/types/message.js.map +1 -1
- package/dist/utils/data-url.d.ts +9 -0
- package/dist/utils/data-url.d.ts.map +1 -0
- package/dist/utils/data-url.js +14 -0
- package/dist/utils/data-url.js.map +1 -0
- package/dist/utils/notify-event-listeners.d.ts +6 -0
- package/dist/utils/notify-event-listeners.d.ts.map +1 -0
- package/dist/utils/notify-event-listeners.js +16 -0
- package/dist/utils/notify-event-listeners.js.map +1 -0
- package/package.json +10 -9
- package/src/adapters/speech.test.ts +88 -0
- package/src/adapters/speech.ts +17 -2
- package/src/adapters/voice.test.ts +130 -0
- package/src/adapters/voice.ts +26 -6
- package/src/internal.ts +5 -0
- package/src/model-context/frame/host.test.ts +100 -0
- package/src/model-context/frame/host.ts +10 -0
- package/src/model-context/frame/provider.test.ts +83 -0
- package/src/model-context/frame/provider.ts +1 -0
- package/src/react/adapters/LocalStorageThreadListAdapter.test.ts +82 -0
- package/src/react/adapters/LocalStorageThreadListAdapter.tsx +94 -54
- package/src/react/primitive-hooks/useActionBarCopy.test.ts +79 -0
- package/src/react/primitive-hooks/useActionBarCopy.ts +4 -3
- package/src/react/primitives/chainOfThought/ChainOfThoughtParts.tsx +1 -3
- package/src/react/primitives/message/MessageParts.tsx +48 -35
- package/src/react/providers/ChainOfThoughtPartByIndexProvider.tsx +19 -2
- package/src/react/providers/PartByIndexProvider.tsx +16 -2
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +18 -0
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +1 -0
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +48 -7
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +69 -0
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +1 -1
- package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.test.ts +16 -0
- package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.ts +2 -1
- package/src/react/runtimes/external-message-converter.test.tsx +21 -0
- package/src/react/runtimes/external-message-converter.ts +1 -1
- package/src/react/runtimes/useLocalRuntime.test.tsx +56 -0
- package/src/react/runtimes/useLocalRuntime.ts +13 -1
- package/src/runtime/api/message-runtime.test.ts +81 -0
- package/src/runtime/api/message-runtime.ts +4 -6
- package/src/runtime/api/thread-list-item-runtime.ts +6 -1
- package/src/runtime/base/base-composer-runtime-core.ts +77 -31
- package/src/runtime/base/base-thread-runtime-core.ts +2 -1
- package/src/runtime/base/default-edit-composer-runtime-core.ts +4 -2
- package/src/runtime/base/default-thread-composer-runtime-core.ts +2 -2
- package/src/runtime/utils/thread-message-like.ts +1 -0
- package/src/runtimes/local/local-thread-runtime-core.test.ts +150 -1
- package/src/runtimes/local/local-thread-runtime-core.ts +4 -2
- package/src/runtimes/remote-thread-list/optimistic-state.test.ts +63 -0
- package/src/runtimes/remote-thread-list/optimistic-state.ts +49 -17
- package/src/runtimes/remote-thread-list/types.ts +1 -1
- package/src/runtimes/tool-invocations/EDGE_CASES.md +13 -19
- package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +127 -6
- package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +4 -2
- package/src/subscribable/subscribable.test.ts +58 -0
- package/src/subscribable/subscribable.ts +13 -3
- package/src/tests/OptimisticState-list-race.test.ts +33 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-switchToThread-order.test.ts +223 -0
- package/src/tests/base-composer-runtime-core-addAttachment.test.ts +44 -0
- package/src/tests/base-composer-runtime-core-send.test.ts +433 -5
- package/src/tests/default-edit-composer-runtime-core.test.ts +43 -5
- package/src/tests/event-subscription-listener-errors.test.ts +205 -0
- package/src/tests/remote-thread-list-test-helpers.ts +14 -7
- package/src/tests/thread-message-like.test.ts +57 -0
- package/src/types/attachment.ts +5 -0
- package/src/types/message.ts +1 -0
- package/src/utils/data-url.test.ts +50 -0
- package/src/utils/data-url.ts +9 -0
- package/src/utils/notify-event-listeners.ts +30 -0
|
@@ -23,6 +23,37 @@ export type AsyncStorageLike = {
|
|
|
23
23
|
removeItem(key: string): Promise<void>;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
class KeyedMutationQueue {
|
|
27
|
+
private readonly tails = new Map<string, Promise<void>>();
|
|
28
|
+
|
|
29
|
+
run<T>(key: string, mutation: () => Promise<T>): Promise<T> {
|
|
30
|
+
const previous = this.tails.get(key);
|
|
31
|
+
const result = previous ? previous.then(mutation) : mutation();
|
|
32
|
+
const tail = result.then(
|
|
33
|
+
() => undefined,
|
|
34
|
+
() => undefined,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
this.tails.set(key, tail);
|
|
38
|
+
void tail.then(() => {
|
|
39
|
+
if (this.tails.get(key) === tail) this.tails.delete(key);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const mutationQueues = new WeakMap<AsyncStorageLike, KeyedMutationQueue>();
|
|
47
|
+
|
|
48
|
+
const getMutationQueue = (storage: AsyncStorageLike): KeyedMutationQueue => {
|
|
49
|
+
let queue = mutationQueues.get(storage);
|
|
50
|
+
if (!queue) {
|
|
51
|
+
queue = new KeyedMutationQueue();
|
|
52
|
+
mutationQueues.set(storage, queue);
|
|
53
|
+
}
|
|
54
|
+
return queue;
|
|
55
|
+
};
|
|
56
|
+
|
|
26
57
|
type LocalStorageAdapterOptions = {
|
|
27
58
|
storage: AsyncStorageLike;
|
|
28
59
|
prefix?: string | undefined;
|
|
@@ -248,6 +279,7 @@ class AsyncStorageHistoryAdapter implements ThreadHistoryAdapter {
|
|
|
248
279
|
private storage: AsyncStorageLike,
|
|
249
280
|
private aui: ReturnType<typeof useAui>,
|
|
250
281
|
private prefix: string,
|
|
282
|
+
private mutationQueue: KeyedMutationQueue,
|
|
251
283
|
) {}
|
|
252
284
|
|
|
253
285
|
private _messagesKey(remoteId: string) {
|
|
@@ -266,31 +298,34 @@ class AsyncStorageHistoryAdapter implements ThreadHistoryAdapter {
|
|
|
266
298
|
const { remoteId } = await this.aui.threadListItem().initialize();
|
|
267
299
|
|
|
268
300
|
const key = this._messagesKey(remoteId);
|
|
269
|
-
|
|
270
|
-
|
|
301
|
+
await this.mutationQueue.run(key, async () => {
|
|
302
|
+
const raw = await this.storage.getItem(key);
|
|
303
|
+
const repo = parseStoredMessageRepository(raw);
|
|
304
|
+
|
|
305
|
+
const idx = repo.messages.findIndex(
|
|
306
|
+
(m) => m.message.id === item.message.id,
|
|
307
|
+
);
|
|
308
|
+
if (idx >= 0) {
|
|
309
|
+
repo.messages[idx] = item;
|
|
310
|
+
} else {
|
|
311
|
+
repo.messages.push(item);
|
|
312
|
+
}
|
|
313
|
+
repo.headId = item.message.id;
|
|
271
314
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
);
|
|
275
|
-
if (idx >= 0) {
|
|
276
|
-
repo.messages[idx] = item;
|
|
277
|
-
} else {
|
|
278
|
-
repo.messages.push(item);
|
|
279
|
-
}
|
|
280
|
-
repo.headId = item.message.id;
|
|
281
|
-
|
|
282
|
-
await this.storage.setItem(key, JSON.stringify(repo));
|
|
315
|
+
await this.storage.setItem(key, JSON.stringify(repo));
|
|
316
|
+
});
|
|
283
317
|
}
|
|
284
318
|
}
|
|
285
319
|
|
|
286
320
|
const createHistoryProvider = (
|
|
287
321
|
storage: AsyncStorageLike,
|
|
288
322
|
prefix: string,
|
|
323
|
+
mutationQueue: KeyedMutationQueue,
|
|
289
324
|
): FC<PropsWithChildren> => {
|
|
290
325
|
const Provider: FC<PropsWithChildren> = ({ children }) => {
|
|
291
326
|
const aui = useAui();
|
|
292
327
|
const history = useMemo(
|
|
293
|
-
() => new AsyncStorageHistoryAdapter(storage, aui, prefix),
|
|
328
|
+
() => new AsyncStorageHistoryAdapter(storage, aui, prefix, mutationQueue),
|
|
294
329
|
[aui],
|
|
295
330
|
);
|
|
296
331
|
const adapters = useMemo(() => ({ history }), [history]);
|
|
@@ -311,6 +346,7 @@ export const createLocalStorageAdapter = (
|
|
|
311
346
|
|
|
312
347
|
const threadsKey = `${prefix}threads`;
|
|
313
348
|
const messagesKey = (threadId: string) => `${prefix}messages:${threadId}`;
|
|
349
|
+
const mutationQueue = getMutationQueue(storage);
|
|
314
350
|
|
|
315
351
|
const loadThreadMetadata = async (): Promise<StoredThreadMetadata[]> => {
|
|
316
352
|
const raw = await storage.getItem(threadsKey);
|
|
@@ -323,8 +359,22 @@ export const createLocalStorageAdapter = (
|
|
|
323
359
|
await storage.setItem(threadsKey, JSON.stringify(threads));
|
|
324
360
|
};
|
|
325
361
|
|
|
362
|
+
const updateThreadMetadata = async (
|
|
363
|
+
remoteId: string,
|
|
364
|
+
update: (thread: StoredThreadMetadata) => void,
|
|
365
|
+
): Promise<void> => {
|
|
366
|
+
await mutationQueue.run(threadsKey, async () => {
|
|
367
|
+
const threads = await loadThreadMetadata();
|
|
368
|
+
const thread = threads.find((item) => item.remoteId === remoteId);
|
|
369
|
+
if (thread) {
|
|
370
|
+
update(thread);
|
|
371
|
+
await saveThreadMetadata(threads);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
};
|
|
375
|
+
|
|
326
376
|
const adapter: RemoteThreadListAdapter = {
|
|
327
|
-
unstable_Provider: createHistoryProvider(storage, prefix),
|
|
377
|
+
unstable_Provider: createHistoryProvider(storage, prefix, mutationQueue),
|
|
328
378
|
|
|
329
379
|
async list(): Promise<RemoteThreadListResponse> {
|
|
330
380
|
const threads = await loadThreadMetadata();
|
|
@@ -343,64 +393,57 @@ export const createLocalStorageAdapter = (
|
|
|
343
393
|
threadId: string,
|
|
344
394
|
): Promise<RemoteThreadInitializeResponse> {
|
|
345
395
|
const remoteId = threadId;
|
|
346
|
-
|
|
396
|
+
return mutationQueue.run(threadsKey, async () => {
|
|
397
|
+
const threads = await loadThreadMetadata();
|
|
347
398
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
399
|
+
// Only add if not already present
|
|
400
|
+
if (!threads.some((t) => t.remoteId === remoteId)) {
|
|
401
|
+
threads.unshift({
|
|
402
|
+
remoteId,
|
|
403
|
+
status: "regular",
|
|
404
|
+
});
|
|
405
|
+
await saveThreadMetadata(threads);
|
|
406
|
+
}
|
|
356
407
|
|
|
357
|
-
|
|
408
|
+
return { remoteId, externalId: undefined };
|
|
409
|
+
});
|
|
358
410
|
},
|
|
359
411
|
|
|
360
412
|
async rename(remoteId: string, newTitle: string): Promise<void> {
|
|
361
|
-
|
|
362
|
-
const thread = threads.find((t) => t.remoteId === remoteId);
|
|
363
|
-
if (thread) {
|
|
413
|
+
await updateThreadMetadata(remoteId, (thread) => {
|
|
364
414
|
thread.title = newTitle;
|
|
365
|
-
|
|
366
|
-
}
|
|
415
|
+
});
|
|
367
416
|
},
|
|
368
417
|
|
|
369
418
|
async updateCustom(
|
|
370
419
|
remoteId: string,
|
|
371
420
|
custom: Record<string, unknown> | undefined,
|
|
372
421
|
): Promise<void> {
|
|
373
|
-
|
|
374
|
-
const thread = threads.find((t) => t.remoteId === remoteId);
|
|
375
|
-
if (thread) {
|
|
422
|
+
await updateThreadMetadata(remoteId, (thread) => {
|
|
376
423
|
thread.custom = custom;
|
|
377
|
-
|
|
378
|
-
}
|
|
424
|
+
});
|
|
379
425
|
},
|
|
380
426
|
|
|
381
427
|
async archive(remoteId: string): Promise<void> {
|
|
382
|
-
|
|
383
|
-
const thread = threads.find((t) => t.remoteId === remoteId);
|
|
384
|
-
if (thread) {
|
|
428
|
+
await updateThreadMetadata(remoteId, (thread) => {
|
|
385
429
|
thread.status = "archived";
|
|
386
|
-
|
|
387
|
-
}
|
|
430
|
+
});
|
|
388
431
|
},
|
|
389
432
|
|
|
390
433
|
async unarchive(remoteId: string): Promise<void> {
|
|
391
|
-
|
|
392
|
-
const thread = threads.find((t) => t.remoteId === remoteId);
|
|
393
|
-
if (thread) {
|
|
434
|
+
await updateThreadMetadata(remoteId, (thread) => {
|
|
394
435
|
thread.status = "regular";
|
|
395
|
-
|
|
396
|
-
}
|
|
436
|
+
});
|
|
397
437
|
},
|
|
398
438
|
|
|
399
439
|
async delete(remoteId: string): Promise<void> {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
440
|
+
await mutationQueue.run(threadsKey, async () => {
|
|
441
|
+
const threads = await loadThreadMetadata();
|
|
442
|
+
const filtered = threads.filter((t) => t.remoteId !== remoteId);
|
|
443
|
+
await saveThreadMetadata(filtered);
|
|
444
|
+
});
|
|
445
|
+
const key = messagesKey(remoteId);
|
|
446
|
+
await mutationQueue.run(key, () => storage.removeItem(key));
|
|
404
447
|
},
|
|
405
448
|
|
|
406
449
|
async fetch(threadId: string): Promise<RemoteThreadMetadata> {
|
|
@@ -427,12 +470,9 @@ export const createLocalStorageAdapter = (
|
|
|
427
470
|
const title = await titleGenerator.generateTitle(messages);
|
|
428
471
|
|
|
429
472
|
// Update the stored title
|
|
430
|
-
|
|
431
|
-
const thread = threads.find((t) => t.remoteId === remoteId);
|
|
432
|
-
if (thread) {
|
|
473
|
+
await updateThreadMetadata(remoteId, (thread) => {
|
|
433
474
|
thread.title = title;
|
|
434
|
-
|
|
435
|
-
}
|
|
475
|
+
});
|
|
436
476
|
|
|
437
477
|
// Return a stream with a single text part
|
|
438
478
|
return createAssistantStream((controller) => {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const mocks = vi.hoisted(() => {
|
|
4
|
+
const setIsCopied = vi.fn();
|
|
5
|
+
|
|
6
|
+
return {
|
|
7
|
+
setIsCopied,
|
|
8
|
+
state: {
|
|
9
|
+
message: {
|
|
10
|
+
role: "assistant",
|
|
11
|
+
status: { type: "complete", reason: "stop" },
|
|
12
|
+
parts: [{ type: "text", text: "Hello" }],
|
|
13
|
+
isCopied: false,
|
|
14
|
+
},
|
|
15
|
+
composer: {
|
|
16
|
+
isEditing: false,
|
|
17
|
+
text: "",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
aui: {
|
|
21
|
+
message: () => ({
|
|
22
|
+
getCopyText: () => "Hello",
|
|
23
|
+
setIsCopied,
|
|
24
|
+
}),
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
vi.mock("react", async (importOriginal) => ({
|
|
30
|
+
...(await importOriginal<typeof import("react")>()),
|
|
31
|
+
useCallback: ((callback: unknown) =>
|
|
32
|
+
callback) as typeof import("react").useCallback,
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => ({
|
|
36
|
+
...(await importOriginal<typeof import("@assistant-ui/store")>()),
|
|
37
|
+
useAui: () => mocks.aui,
|
|
38
|
+
useAuiState: ((selector: (state: typeof mocks.state) => unknown) =>
|
|
39
|
+
selector(mocks.state)) as typeof import("@assistant-ui/store").useAuiState,
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
import { useActionBarCopy } from "./useActionBarCopy";
|
|
43
|
+
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
vi.clearAllMocks();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("useActionBarCopy", () => {
|
|
49
|
+
it("does not report copy success without a clipboard handler", async () => {
|
|
50
|
+
const { copy, disabled } = useActionBarCopy();
|
|
51
|
+
|
|
52
|
+
copy();
|
|
53
|
+
await Promise.resolve();
|
|
54
|
+
|
|
55
|
+
expect(disabled).toBe(true);
|
|
56
|
+
expect(mocks.setIsCopied).not.toHaveBeenCalled();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("reports copy success after the clipboard handler resolves", async () => {
|
|
60
|
+
const copyToClipboard = vi.fn();
|
|
61
|
+
const { copy } = useActionBarCopy({ copyToClipboard });
|
|
62
|
+
|
|
63
|
+
copy();
|
|
64
|
+
await Promise.resolve();
|
|
65
|
+
|
|
66
|
+
expect(copyToClipboard).toHaveBeenCalledWith("Hello");
|
|
67
|
+
expect(mocks.setIsCopied).toHaveBeenCalledWith(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("does not report copy success when the clipboard handler rejects", async () => {
|
|
71
|
+
const copyToClipboard = vi.fn().mockRejectedValue(new Error("denied"));
|
|
72
|
+
const { copy } = useActionBarCopy({ copyToClipboard });
|
|
73
|
+
|
|
74
|
+
copy();
|
|
75
|
+
await Promise.resolve();
|
|
76
|
+
|
|
77
|
+
expect(mocks.setIsCopied).not.toHaveBeenCalled();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -23,13 +23,14 @@ export const useActionBarCopy = ({
|
|
|
23
23
|
const composerValue = useAuiState((s) => s.composer.text);
|
|
24
24
|
|
|
25
25
|
const copy = useCallback(() => {
|
|
26
|
+
if (!copyToClipboard) return;
|
|
27
|
+
|
|
26
28
|
const valueToCopy = isEditing ? composerValue : aui.message().getCopyText();
|
|
27
29
|
if (!valueToCopy) return;
|
|
28
30
|
|
|
29
|
-
const write = copyToClipboard ?? (() => {});
|
|
30
31
|
// The rejection handler swallows clipboard write failures (permission denied,
|
|
31
32
|
// API unavailable) so they don't surface as unhandled promise rejections.
|
|
32
|
-
Promise.resolve(
|
|
33
|
+
Promise.resolve(copyToClipboard(valueToCopy)).then(
|
|
33
34
|
() => {
|
|
34
35
|
aui.message().setIsCopied(true);
|
|
35
36
|
setTimeout(() => aui.message().setIsCopied(false), copiedDuration);
|
|
@@ -38,5 +39,5 @@ export const useActionBarCopy = ({
|
|
|
38
39
|
);
|
|
39
40
|
}, [aui, isEditing, composerValue, copiedDuration, copyToClipboard]);
|
|
40
41
|
|
|
41
|
-
return { copy, disabled, isCopied };
|
|
42
|
+
return { copy, disabled: disabled || !copyToClipboard, isCopied };
|
|
42
43
|
};
|
|
@@ -51,9 +51,7 @@ const ChainOfThoughtPrimitivePartsInner: FC<{
|
|
|
51
51
|
Array.from({ length: partsLength }, (_, index) => (
|
|
52
52
|
<ChainOfThoughtPartByIndexProvider key={index} index={index}>
|
|
53
53
|
<RenderChildrenWithAccessor
|
|
54
|
-
getItemState={(aui) =>
|
|
55
|
-
aui.chainOfThought().part({ index }).getState()
|
|
56
|
-
}
|
|
54
|
+
getItemState={(aui) => aui.part().getState()}
|
|
57
55
|
>
|
|
58
56
|
{(getItem) =>
|
|
59
57
|
children({
|
|
@@ -728,50 +728,63 @@ const EMPTY_RUNNING_TEXT_PART: Extract<EnrichedPartState, { type: "text" }> =
|
|
|
728
728
|
* `<MessagePrimitive.GroupedParts>`. Returns whatever `children`
|
|
729
729
|
* returns — callers decide how to handle a `null` return.
|
|
730
730
|
*/
|
|
731
|
-
|
|
731
|
+
type MessagePartChildrenProps = {
|
|
732
732
|
index: number;
|
|
733
733
|
children: (value: { part: EnrichedPartState }) => ReactNode;
|
|
734
|
-
}
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
const MessagePartChildrenInner: FC<
|
|
737
|
+
Pick<MessagePartChildrenProps, "children">
|
|
738
|
+
> = ({ children }) => {
|
|
735
739
|
const aui = useAui();
|
|
736
740
|
// Subscribed (not snapshotted like `tools`) so fallbacks registered
|
|
737
741
|
// after the first render trigger a re-render and `hasUI` re-evaluates.
|
|
738
742
|
const dataRenderers = useAuiState((s) => s.dataRenderers);
|
|
739
743
|
|
|
744
|
+
return (
|
|
745
|
+
<RenderChildrenWithAccessor
|
|
746
|
+
getItemState={(client) => client.part().getState()}
|
|
747
|
+
>
|
|
748
|
+
{(getItem) =>
|
|
749
|
+
children({
|
|
750
|
+
get part() {
|
|
751
|
+
const state = getItem();
|
|
752
|
+
if (state.type === "tool-call") {
|
|
753
|
+
const toolsState = aui.tools().getState();
|
|
754
|
+
const hasUI = resolveToolRender(toolsState, state) !== null;
|
|
755
|
+
const partMethods = aui.part();
|
|
756
|
+
return {
|
|
757
|
+
...state,
|
|
758
|
+
toolUI: hasUI ? <RegisteredToolUI /> : null,
|
|
759
|
+
addResult: partMethods.addToolResult,
|
|
760
|
+
resume: partMethods.resumeToolCall,
|
|
761
|
+
respondToApproval: partMethods.respondToToolApproval,
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
if (state.type === "data") {
|
|
765
|
+
const hasUI =
|
|
766
|
+
getDataRenderer(dataRenderers, state.name, undefined) !==
|
|
767
|
+
undefined;
|
|
768
|
+
return {
|
|
769
|
+
...state,
|
|
770
|
+
dataRendererUI: hasUI ? <RegisteredDataRendererUI /> : null,
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
return state;
|
|
774
|
+
},
|
|
775
|
+
})
|
|
776
|
+
}
|
|
777
|
+
</RenderChildrenWithAccessor>
|
|
778
|
+
);
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
export const MessagePartChildren: FC<MessagePartChildrenProps> = ({
|
|
782
|
+
index,
|
|
783
|
+
children,
|
|
784
|
+
}) => {
|
|
740
785
|
return (
|
|
741
786
|
<PartByIndexProvider index={index}>
|
|
742
|
-
<
|
|
743
|
-
getItemState={(aui) => aui.message().part({ index }).getState()}
|
|
744
|
-
>
|
|
745
|
-
{(getItem) =>
|
|
746
|
-
children({
|
|
747
|
-
get part() {
|
|
748
|
-
const state = getItem();
|
|
749
|
-
if (state.type === "tool-call") {
|
|
750
|
-
const toolsState = aui.tools().getState();
|
|
751
|
-
const hasUI = resolveToolRender(toolsState, state) !== null;
|
|
752
|
-
const partMethods = aui.message().part({ index });
|
|
753
|
-
return {
|
|
754
|
-
...state,
|
|
755
|
-
toolUI: hasUI ? <RegisteredToolUI /> : null,
|
|
756
|
-
addResult: partMethods.addToolResult,
|
|
757
|
-
resume: partMethods.resumeToolCall,
|
|
758
|
-
respondToApproval: partMethods.respondToToolApproval,
|
|
759
|
-
};
|
|
760
|
-
}
|
|
761
|
-
if (state.type === "data") {
|
|
762
|
-
const hasUI =
|
|
763
|
-
getDataRenderer(dataRenderers, state.name, undefined) !==
|
|
764
|
-
undefined;
|
|
765
|
-
return {
|
|
766
|
-
...state,
|
|
767
|
-
dataRendererUI: hasUI ? <RegisteredDataRendererUI /> : null,
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
return state;
|
|
771
|
-
},
|
|
772
|
-
})
|
|
773
|
-
}
|
|
774
|
-
</RenderChildrenWithAccessor>
|
|
787
|
+
<MessagePartChildrenInner>{children}</MessagePartChildrenInner>
|
|
775
788
|
</PartByIndexProvider>
|
|
776
789
|
);
|
|
777
790
|
};
|
|
@@ -1,16 +1,33 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { useMemo, type FC, type PropsWithChildren } from "react";
|
|
2
2
|
import { useAui, AuiProvider, Derived } from "@assistant-ui/store";
|
|
3
|
+
import type { PartMethods } from "../../store/scopes/part";
|
|
3
4
|
|
|
4
5
|
export const ChainOfThoughtPartByIndexProvider: FC<
|
|
5
6
|
PropsWithChildren<{
|
|
6
7
|
index: number;
|
|
7
8
|
}>
|
|
8
9
|
> = ({ index, children }) => {
|
|
10
|
+
const lastPartRef = useMemo(
|
|
11
|
+
() => ({ index, current: null as PartMethods | null }),
|
|
12
|
+
[index],
|
|
13
|
+
);
|
|
9
14
|
const aui = useAui({
|
|
10
15
|
part: Derived({
|
|
11
16
|
source: "chainOfThought",
|
|
12
17
|
query: { type: "index", index },
|
|
13
|
-
get: (aui) =>
|
|
18
|
+
get: (aui) => {
|
|
19
|
+
const chainOfThought = aui.chainOfThought();
|
|
20
|
+
if (
|
|
21
|
+
index >= chainOfThought.getState().parts.length &&
|
|
22
|
+
lastPartRef.current
|
|
23
|
+
) {
|
|
24
|
+
return lastPartRef.current;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const part = chainOfThought.part({ index });
|
|
28
|
+
lastPartRef.current = part;
|
|
29
|
+
return part;
|
|
30
|
+
},
|
|
14
31
|
}),
|
|
15
32
|
});
|
|
16
33
|
|
|
@@ -1,16 +1,30 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { useMemo, type FC, type PropsWithChildren } from "react";
|
|
2
2
|
import { useAui, AuiProvider, Derived } from "@assistant-ui/store";
|
|
3
|
+
import type { PartMethods } from "../../store/scopes/part";
|
|
3
4
|
|
|
4
5
|
export const PartByIndexProvider: FC<
|
|
5
6
|
PropsWithChildren<{
|
|
6
7
|
index: number;
|
|
7
8
|
}>
|
|
8
9
|
> = ({ index, children }) => {
|
|
10
|
+
const lastPartRef = useMemo(
|
|
11
|
+
() => ({ index, current: null as PartMethods | null }),
|
|
12
|
+
[index],
|
|
13
|
+
);
|
|
9
14
|
const aui = useAui({
|
|
10
15
|
part: Derived({
|
|
11
16
|
source: "message",
|
|
12
17
|
query: { type: "index", index },
|
|
13
|
-
get: (aui) =>
|
|
18
|
+
get: (aui) => {
|
|
19
|
+
const message = aui.message();
|
|
20
|
+
if (index >= message.getState().parts.length && lastPartRef.current) {
|
|
21
|
+
return lastPartRef.current;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const part = message.part({ index });
|
|
25
|
+
lastPartRef.current = part;
|
|
26
|
+
return part;
|
|
27
|
+
},
|
|
14
28
|
}),
|
|
15
29
|
});
|
|
16
30
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { ThreadListRuntimeCore } from "../../runtime/interfaces/thread-list-runtime-core";
|
|
3
|
+
import { RemoteThreadListHookInstanceManager } from "./RemoteThreadListHookInstanceManager";
|
|
4
|
+
|
|
5
|
+
describe("RemoteThreadListHookInstanceManager", () => {
|
|
6
|
+
it("rejects a pending start when the thread runtime is stopped", async () => {
|
|
7
|
+
const manager = new RemoteThreadListHookInstanceManager(() => {
|
|
8
|
+
throw new Error("Runtime hook should not render during this test");
|
|
9
|
+
}, {} as ThreadListRuntimeCore);
|
|
10
|
+
|
|
11
|
+
const startPromise = manager.startThreadRuntime("thread-1");
|
|
12
|
+
manager.stopThreadRuntime("thread-1");
|
|
13
|
+
|
|
14
|
+
await expect(startPromise).rejects.toThrow(
|
|
15
|
+
"Thread was deleted before runtime was started",
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -85,6 +85,7 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
|
|
|
85
85
|
public stopThreadRuntime(threadId: string) {
|
|
86
86
|
this.instances.delete(threadId);
|
|
87
87
|
this.useAliveThreadsKeysChanged.setState({}, true);
|
|
88
|
+
this._notifySubscribers();
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
public setRuntimeHook(newRuntimeHook: RemoteThreadListHook) {
|
|
@@ -44,6 +44,8 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
44
44
|
private _loadThreadsPromise: Promise<void> | undefined;
|
|
45
45
|
private _loadMorePromise: Promise<void> | undefined;
|
|
46
46
|
private _loadGeneration = 0;
|
|
47
|
+
private _switchGeneration = 0;
|
|
48
|
+
private _switchTask: Promise<void> | undefined;
|
|
47
49
|
|
|
48
50
|
private _mainThreadId!: string;
|
|
49
51
|
private readonly _state = new OptimisticState<RemoteThreadState>({
|
|
@@ -307,15 +309,28 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
307
309
|
return getThreadData(this._state.value, threadIdOrRemoteId);
|
|
308
310
|
}
|
|
309
311
|
|
|
310
|
-
public
|
|
312
|
+
public switchToThread(
|
|
311
313
|
threadIdOrRemoteId: string,
|
|
312
314
|
options?: { unarchive?: boolean },
|
|
315
|
+
): Promise<void> {
|
|
316
|
+
const generation = ++this._switchGeneration;
|
|
317
|
+
const task = this._switchToThread(threadIdOrRemoteId, options, generation);
|
|
318
|
+
this._switchTask = task;
|
|
319
|
+
return task;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private async _switchToThread(
|
|
323
|
+
threadIdOrRemoteId: string,
|
|
324
|
+
options: { unarchive?: boolean } | undefined,
|
|
325
|
+
generation: number,
|
|
313
326
|
): Promise<void> {
|
|
314
327
|
let data = this.getItemById(threadIdOrRemoteId);
|
|
315
328
|
|
|
316
329
|
if (!data) {
|
|
317
330
|
const remoteMetadata =
|
|
318
331
|
await this._options.adapter.fetch(threadIdOrRemoteId);
|
|
332
|
+
if (generation !== this._switchGeneration) return;
|
|
333
|
+
|
|
319
334
|
const state = this._state.value;
|
|
320
335
|
const mappingId = createThreadMappingId(remoteMetadata.remoteId);
|
|
321
336
|
|
|
@@ -380,8 +395,11 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
380
395
|
task.then(() => this._notifySubscribers());
|
|
381
396
|
}
|
|
382
397
|
|
|
398
|
+
if (generation !== this._switchGeneration) return;
|
|
399
|
+
|
|
383
400
|
if (data.status === "archived" && options?.unarchive !== false) {
|
|
384
401
|
await this.unarchive(data.id);
|
|
402
|
+
if (generation !== this._switchGeneration) return;
|
|
385
403
|
}
|
|
386
404
|
this._mainThreadId = data.id;
|
|
387
405
|
|
|
@@ -389,13 +407,21 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
389
407
|
this._notifyThreadIdChange();
|
|
390
408
|
}
|
|
391
409
|
|
|
392
|
-
public
|
|
410
|
+
public switchToNewThread(): Promise<void> {
|
|
411
|
+
const generation = ++this._switchGeneration;
|
|
412
|
+
const task = this._switchToNewThread(generation);
|
|
413
|
+
this._switchTask = task;
|
|
414
|
+
return task;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private async _switchToNewThread(generation: number): Promise<void> {
|
|
393
418
|
// an initialization transaction is in progress, wait for it to settle
|
|
394
419
|
while (
|
|
395
420
|
this._state.baseValue.newThreadId !== undefined &&
|
|
396
421
|
this._state.value.newThreadId === undefined
|
|
397
422
|
) {
|
|
398
423
|
await this._state.waitForUpdate();
|
|
424
|
+
if (generation !== this._switchGeneration) return;
|
|
399
425
|
}
|
|
400
426
|
|
|
401
427
|
const state = this._state.value;
|
|
@@ -427,7 +453,7 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
427
453
|
});
|
|
428
454
|
}
|
|
429
455
|
|
|
430
|
-
return this.
|
|
456
|
+
return this._switchToThread(id, undefined, generation);
|
|
431
457
|
}
|
|
432
458
|
|
|
433
459
|
public initialize = async (threadId: string) => {
|
|
@@ -436,10 +462,11 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
436
462
|
if (!data) throw threadNotFoundError(threadId, "initializing it");
|
|
437
463
|
if (data.status === "new")
|
|
438
464
|
throw threadStatusError(threadId, data.status, "be initialized here");
|
|
439
|
-
|
|
465
|
+
const { remoteId, externalId } = await data.initializeTask;
|
|
466
|
+
return { remoteId, externalId };
|
|
440
467
|
}
|
|
441
468
|
|
|
442
|
-
|
|
469
|
+
const { remoteId, externalId } = await this._state.optimisticUpdate({
|
|
443
470
|
execute: () => {
|
|
444
471
|
return this._options.adapter.initialize(threadId);
|
|
445
472
|
},
|
|
@@ -482,6 +509,7 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
482
509
|
};
|
|
483
510
|
},
|
|
484
511
|
});
|
|
512
|
+
return { remoteId, externalId };
|
|
485
513
|
};
|
|
486
514
|
|
|
487
515
|
public generateTitle = async (threadId: string) => {
|
|
@@ -604,8 +632,21 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
604
632
|
if (threadId === this.newThreadId)
|
|
605
633
|
throw new Error("Cannot ensure new thread is not main");
|
|
606
634
|
|
|
607
|
-
|
|
608
|
-
|
|
635
|
+
let lastAwaitedTask: Promise<void> | undefined;
|
|
636
|
+
|
|
637
|
+
while (threadId === this._mainThreadId) {
|
|
638
|
+
let switchTask = this._switchTask;
|
|
639
|
+
const startedFallback = !switchTask || switchTask === lastAwaitedTask;
|
|
640
|
+
if (startedFallback) switchTask = this.switchToNewThread();
|
|
641
|
+
lastAwaitedTask = switchTask;
|
|
642
|
+
|
|
643
|
+
try {
|
|
644
|
+
await switchTask;
|
|
645
|
+
} catch (error) {
|
|
646
|
+
if (startedFallback && this._switchTask === switchTask) {
|
|
647
|
+
throw error;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
609
650
|
}
|
|
610
651
|
}
|
|
611
652
|
|