@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
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
ThreadComposerRuntimeImpl,
|
|
4
|
+
type ThreadComposerRuntimeCoreBinding,
|
|
5
|
+
} from "../runtime/api/composer-runtime";
|
|
6
|
+
import {
|
|
7
|
+
ThreadRuntimeImpl,
|
|
8
|
+
type ThreadListItemRuntimeBinding,
|
|
9
|
+
type ThreadRuntimeCoreBinding,
|
|
10
|
+
} from "../runtime/api/thread-runtime";
|
|
11
|
+
import { ThreadListItemRuntimeImpl } from "../runtime/api/thread-list-item-runtime";
|
|
12
|
+
import type { ThreadListRuntimeCoreBinding } from "../runtime/api/thread-list-runtime";
|
|
13
|
+
import { ReadonlyThreadRuntimeCore } from "../runtimes/readonly/ReadonlyThreadRuntimeCore";
|
|
14
|
+
import type { Unsubscribe } from "../types/unsubscribe";
|
|
15
|
+
|
|
16
|
+
type EventListener = (payload?: unknown) => unknown;
|
|
17
|
+
|
|
18
|
+
class GuardedEventSource {
|
|
19
|
+
private listeners = new Set<EventListener>();
|
|
20
|
+
|
|
21
|
+
public unstable_on = (
|
|
22
|
+
_event: string,
|
|
23
|
+
listener: EventListener,
|
|
24
|
+
): Unsubscribe => {
|
|
25
|
+
this.listeners.add(listener);
|
|
26
|
+
return () => this.listeners.delete(listener);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
public emit(payload?: unknown) {
|
|
30
|
+
for (const listener of this.listeners) {
|
|
31
|
+
try {
|
|
32
|
+
const result = listener(payload);
|
|
33
|
+
if (
|
|
34
|
+
typeof result === "object" &&
|
|
35
|
+
result !== null &&
|
|
36
|
+
"then" in result &&
|
|
37
|
+
typeof result.then === "function"
|
|
38
|
+
) {
|
|
39
|
+
void Promise.resolve(result).catch((error) => {
|
|
40
|
+
console.error("[test source] listener error", error);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error("[test source] listener error", error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type RuntimeHarness = {
|
|
51
|
+
subscribe: (listener: EventListener) => Unsubscribe;
|
|
52
|
+
emit: () => void;
|
|
53
|
+
errorContext: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const makeComposerHarness = (): RuntimeHarness => {
|
|
57
|
+
const source = new GuardedEventSource();
|
|
58
|
+
const binding = {
|
|
59
|
+
path: {
|
|
60
|
+
ref: "test.composer",
|
|
61
|
+
threadSelector: { type: "main" },
|
|
62
|
+
composerSource: "thread",
|
|
63
|
+
},
|
|
64
|
+
getState: () => ({ unstable_on: source.unstable_on }),
|
|
65
|
+
subscribe: () => () => {},
|
|
66
|
+
} as unknown as ThreadComposerRuntimeCoreBinding;
|
|
67
|
+
const runtime = new ThreadComposerRuntimeImpl(binding);
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
subscribe: (listener) =>
|
|
71
|
+
runtime.unstable_on("attachmentAdd", listener as never),
|
|
72
|
+
emit: () => source.emit({}),
|
|
73
|
+
errorContext: 'Runtime event "attachmentAdd"',
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const makeThreadHarness = (): RuntimeHarness => {
|
|
78
|
+
const source = new GuardedEventSource();
|
|
79
|
+
const core = new ReadonlyThreadRuntimeCore();
|
|
80
|
+
core.unstable_on = source.unstable_on as typeof core.unstable_on;
|
|
81
|
+
|
|
82
|
+
const path = {
|
|
83
|
+
ref: "test.thread",
|
|
84
|
+
threadSelector: { type: "main" as const },
|
|
85
|
+
};
|
|
86
|
+
const threadBinding: ThreadRuntimeCoreBinding = {
|
|
87
|
+
path,
|
|
88
|
+
getState: () => core,
|
|
89
|
+
subscribe: (listener) => core.subscribe(listener),
|
|
90
|
+
outerSubscribe: (listener) => core.subscribe(listener),
|
|
91
|
+
};
|
|
92
|
+
const threadListItemBinding: ThreadListItemRuntimeBinding = {
|
|
93
|
+
path,
|
|
94
|
+
getState: () => ({
|
|
95
|
+
id: "test",
|
|
96
|
+
remoteId: undefined,
|
|
97
|
+
externalId: undefined,
|
|
98
|
+
isMain: true,
|
|
99
|
+
status: "regular",
|
|
100
|
+
title: undefined,
|
|
101
|
+
}),
|
|
102
|
+
subscribe: () => () => {},
|
|
103
|
+
};
|
|
104
|
+
const runtime = new ThreadRuntimeImpl(threadBinding, threadListItemBinding);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
subscribe: (listener) => runtime.unstable_on("initialize", listener),
|
|
108
|
+
emit: () => source.emit({}),
|
|
109
|
+
errorContext: 'Runtime event "initialize"',
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const makeThreadListItemHarness = (): RuntimeHarness => {
|
|
114
|
+
let isMain = true;
|
|
115
|
+
const stateListeners = new Set<() => void>();
|
|
116
|
+
const binding = {
|
|
117
|
+
path: {
|
|
118
|
+
ref: "test.threadListItem",
|
|
119
|
+
threadSelector: { type: "main" as const },
|
|
120
|
+
},
|
|
121
|
+
getState: () => ({
|
|
122
|
+
id: "test",
|
|
123
|
+
remoteId: undefined,
|
|
124
|
+
externalId: undefined,
|
|
125
|
+
isMain,
|
|
126
|
+
status: "regular" as const,
|
|
127
|
+
title: undefined,
|
|
128
|
+
}),
|
|
129
|
+
subscribe: (listener: () => void) => {
|
|
130
|
+
stateListeners.add(listener);
|
|
131
|
+
return () => stateListeners.delete(listener);
|
|
132
|
+
},
|
|
133
|
+
} as unknown as ThreadListItemRuntimeBinding;
|
|
134
|
+
const runtime = new ThreadListItemRuntimeImpl(
|
|
135
|
+
binding,
|
|
136
|
+
{} as ThreadListRuntimeCoreBinding,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
subscribe: (listener) =>
|
|
141
|
+
runtime.unstable_on("switchedAway", listener as never),
|
|
142
|
+
emit: () => {
|
|
143
|
+
isMain = !isMain;
|
|
144
|
+
for (const listener of [...stateListeners]) listener();
|
|
145
|
+
},
|
|
146
|
+
errorContext: 'Thread list item "switchedAway"',
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
afterEach(() => {
|
|
151
|
+
vi.restoreAllMocks();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe.each([
|
|
155
|
+
["composer", makeComposerHarness],
|
|
156
|
+
["thread", makeThreadHarness],
|
|
157
|
+
["thread list item", makeThreadListItemHarness],
|
|
158
|
+
] as const)("%s public unstable_on", (_name, makeHarness) => {
|
|
159
|
+
it("continues notifying sibling listeners after a synchronous error", () => {
|
|
160
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
161
|
+
const harness = makeHarness();
|
|
162
|
+
const sibling = vi.fn();
|
|
163
|
+
|
|
164
|
+
harness.subscribe(() => {
|
|
165
|
+
throw new Error("listener failed");
|
|
166
|
+
});
|
|
167
|
+
harness.subscribe(sibling);
|
|
168
|
+
|
|
169
|
+
harness.emit();
|
|
170
|
+
|
|
171
|
+
expect(sibling).toHaveBeenCalledOnce();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it.each(["object", "function"] as const)(
|
|
175
|
+
"handles rejected %s thenables returned by listeners",
|
|
176
|
+
async (thenableType) => {
|
|
177
|
+
const consoleError = vi
|
|
178
|
+
.spyOn(console, "error")
|
|
179
|
+
.mockImplementation(() => {});
|
|
180
|
+
const harness = makeHarness();
|
|
181
|
+
const listenerError = new Error("async listener failed");
|
|
182
|
+
const then = vi.fn(
|
|
183
|
+
(
|
|
184
|
+
_resolve: (value?: unknown) => void,
|
|
185
|
+
reject: (reason?: unknown) => void,
|
|
186
|
+
) => reject(listenerError),
|
|
187
|
+
);
|
|
188
|
+
const thenable =
|
|
189
|
+
thenableType === "function"
|
|
190
|
+
? Object.assign(() => {}, { then })
|
|
191
|
+
: { then };
|
|
192
|
+
|
|
193
|
+
harness.subscribe(() => thenable);
|
|
194
|
+
harness.emit();
|
|
195
|
+
|
|
196
|
+
await vi.waitFor(() => {
|
|
197
|
+
expect(then).toHaveBeenCalledOnce();
|
|
198
|
+
expect(consoleError).toHaveBeenCalledWith(
|
|
199
|
+
expect.stringContaining(harness.errorContext),
|
|
200
|
+
listenerError,
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
},
|
|
204
|
+
);
|
|
205
|
+
});
|
|
@@ -18,6 +18,19 @@ export const contextProvider: ModelContextProvider = {
|
|
|
18
18
|
subscribe: () => () => {},
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
export function setStartThreadRuntime(
|
|
22
|
+
core: RemoteThreadListThreadListRuntimeCore,
|
|
23
|
+
startThreadRuntime: (id: string) => Promise<unknown>,
|
|
24
|
+
) {
|
|
25
|
+
(
|
|
26
|
+
core as unknown as {
|
|
27
|
+
_hookManager: {
|
|
28
|
+
startThreadRuntime: (id: string) => Promise<unknown>;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
)._hookManager.startThreadRuntime = startThreadRuntime;
|
|
32
|
+
}
|
|
33
|
+
|
|
21
34
|
export function makeAdapter(
|
|
22
35
|
overrides: Partial<RemoteThreadListAdapter> = {},
|
|
23
36
|
): RemoteThreadListAdapter {
|
|
@@ -60,12 +73,6 @@ export function createCore(
|
|
|
60
73
|
);
|
|
61
74
|
// `startThreadRuntime` blocks until a React component attaches a runtime;
|
|
62
75
|
// stub it so non-React unit tests don't hang.
|
|
63
|
-
(
|
|
64
|
-
core as unknown as {
|
|
65
|
-
_hookManager: {
|
|
66
|
-
startThreadRuntime: (id: string) => Promise<unknown>;
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
)._hookManager.startThreadRuntime = async () => ({});
|
|
76
|
+
setStartThreadRuntime(core, async () => ({}));
|
|
70
77
|
return core;
|
|
71
78
|
}
|
|
@@ -200,4 +200,61 @@ describe("fromThreadMessageLike", () => {
|
|
|
200
200
|
});
|
|
201
201
|
});
|
|
202
202
|
});
|
|
203
|
+
|
|
204
|
+
describe("metadata.isOptimistic", () => {
|
|
205
|
+
it("preserves isOptimistic on user messages", () => {
|
|
206
|
+
const result = fromThreadMessageLike(
|
|
207
|
+
{
|
|
208
|
+
role: "user",
|
|
209
|
+
content: [{ type: "text", text: "hello" }],
|
|
210
|
+
metadata: { isOptimistic: true },
|
|
211
|
+
},
|
|
212
|
+
fallbackId,
|
|
213
|
+
fallbackStatus,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
expect(result.metadata.isOptimistic).toBe(true);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("preserves isOptimistic on assistant messages", () => {
|
|
220
|
+
const result = fromThreadMessageLike(
|
|
221
|
+
{
|
|
222
|
+
role: "assistant",
|
|
223
|
+
content: [{ type: "text", text: "hello" }],
|
|
224
|
+
metadata: { isOptimistic: true },
|
|
225
|
+
},
|
|
226
|
+
fallbackId,
|
|
227
|
+
fallbackStatus,
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
expect(result.metadata.isOptimistic).toBe(true);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("omits isOptimistic when false", () => {
|
|
234
|
+
const result = fromThreadMessageLike(
|
|
235
|
+
{
|
|
236
|
+
role: "user",
|
|
237
|
+
content: [{ type: "text", text: "hello" }],
|
|
238
|
+
metadata: { isOptimistic: false },
|
|
239
|
+
},
|
|
240
|
+
fallbackId,
|
|
241
|
+
fallbackStatus,
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
expect(result.metadata).not.toHaveProperty("isOptimistic");
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("omits isOptimistic when not set", () => {
|
|
248
|
+
const result = fromThreadMessageLike(
|
|
249
|
+
{
|
|
250
|
+
role: "user",
|
|
251
|
+
content: [{ type: "text", text: "hello" }],
|
|
252
|
+
},
|
|
253
|
+
fallbackId,
|
|
254
|
+
fallbackStatus,
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
expect(result.metadata).not.toHaveProperty("isOptimistic");
|
|
258
|
+
});
|
|
259
|
+
});
|
|
203
260
|
});
|
package/src/types/attachment.ts
CHANGED
|
@@ -52,3 +52,8 @@ export type CreateAttachment = {
|
|
|
52
52
|
contentType?: string;
|
|
53
53
|
content: ThreadUserMessagePart[];
|
|
54
54
|
};
|
|
55
|
+
|
|
56
|
+
export const isCreateAttachment = (
|
|
57
|
+
attachment: File | CreateAttachment,
|
|
58
|
+
): attachment is CreateAttachment =>
|
|
59
|
+
"content" in attachment && !("lastModified" in attachment);
|
package/src/types/message.ts
CHANGED
|
@@ -349,6 +349,7 @@ export type ThreadUserMessage = MessageCommonProps & {
|
|
|
349
349
|
readonly steps?: undefined;
|
|
350
350
|
readonly submittedFeedback?: undefined;
|
|
351
351
|
readonly timing?: undefined;
|
|
352
|
+
readonly isOptimistic?: boolean;
|
|
352
353
|
readonly custom: Record<string, unknown>;
|
|
353
354
|
};
|
|
354
355
|
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { httpUrlPattern, parseDataUrl } from "./data-url";
|
|
3
|
+
|
|
4
|
+
describe("parseDataUrl", () => {
|
|
5
|
+
it("parses a base64 data URL", () => {
|
|
6
|
+
expect(parseDataUrl("data:image/png;base64,aGVsbG8=")).toEqual({
|
|
7
|
+
mimeType: "image/png",
|
|
8
|
+
data: "aGVsbG8=",
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("parses a data URL with extra parameters", () => {
|
|
13
|
+
expect(parseDataUrl("data:text/plain;charset=utf-8;base64,aGk=")).toEqual({
|
|
14
|
+
mimeType: "text/plain",
|
|
15
|
+
data: "aGk=",
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("returns null for non-base64 data URLs", () => {
|
|
20
|
+
expect(parseDataUrl("data:text/plain,hello")).toBeNull();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("returns null for data URLs with an empty payload", () => {
|
|
24
|
+
expect(parseDataUrl("data:image/png;base64,")).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("returns null for plain strings", () => {
|
|
28
|
+
expect(parseDataUrl("aGVsbG8=")).toBeNull();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("returns null for http URLs", () => {
|
|
32
|
+
expect(parseDataUrl("https://example.com/a.png")).toBeNull();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("httpUrlPattern", () => {
|
|
37
|
+
it.each(["http://example.com", "https://example.com", "HTTPS://EXAMPLE.COM"])(
|
|
38
|
+
"matches %s",
|
|
39
|
+
(value) => {
|
|
40
|
+
expect(httpUrlPattern.test(value)).toBe(true);
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
it.each(["ftp://example.com", "data:image/png;base64,aGk=", "/relative"])(
|
|
45
|
+
"does not match %s",
|
|
46
|
+
(value) => {
|
|
47
|
+
expect(httpUrlPattern.test(value)).toBe(false);
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const httpUrlPattern = /^https?:\/\//i;
|
|
2
|
+
|
|
3
|
+
export function parseDataUrl(
|
|
4
|
+
value: string,
|
|
5
|
+
): { mimeType: string; data: string } | null {
|
|
6
|
+
const match = value.match(/^data:([^;,]+)(?:;[^;,]+)*;base64,(.+)$/);
|
|
7
|
+
if (!match) return null;
|
|
8
|
+
return { mimeType: match[1]!, data: match[2]! };
|
|
9
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
type EventListener = (payload?: unknown) => void;
|
|
2
|
+
|
|
3
|
+
export const notifyEventListeners = (
|
|
4
|
+
listeners: Iterable<EventListener>,
|
|
5
|
+
payload: unknown,
|
|
6
|
+
errorContext: string,
|
|
7
|
+
) => {
|
|
8
|
+
const reportError = (error: unknown) => {
|
|
9
|
+
console.error(
|
|
10
|
+
`[assistant-ui] ${errorContext} listener threw an error`,
|
|
11
|
+
error,
|
|
12
|
+
);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
for (const listener of listeners) {
|
|
16
|
+
try {
|
|
17
|
+
const result = listener(payload) as unknown;
|
|
18
|
+
if (
|
|
19
|
+
result !== null &&
|
|
20
|
+
(typeof result === "object" || typeof result === "function") &&
|
|
21
|
+
"then" in result &&
|
|
22
|
+
typeof result.then === "function"
|
|
23
|
+
) {
|
|
24
|
+
void Promise.resolve(result).catch(reportError);
|
|
25
|
+
}
|
|
26
|
+
} catch (error) {
|
|
27
|
+
reportError(error);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|