@assistant-ui/core 0.2.22 → 0.2.23
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/thread-history.d.ts +8 -0
- package/dist/adapters/thread-history.d.ts.map +1 -1
- package/dist/internal.d.ts +3 -2
- package/dist/internal.js +3 -2
- package/dist/model-context/frame/host.d.ts.map +1 -1
- package/dist/model-context/frame/host.js +36 -21
- package/dist/model-context/frame/host.js.map +1 -1
- package/dist/model-context/types.d.ts.map +1 -1
- package/dist/model-context/types.js +13 -4
- package/dist/model-context/types.js.map +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.js +1 -0
- package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
- package/dist/react/client/interactable-model-context.js +1 -0
- package/dist/react/client/interactable-model-context.js.map +1 -1
- package/dist/react/interactables-legacy/useInteractableState.js +1 -0
- package/dist/react/interactables-legacy/useInteractableState.js.map +1 -1
- package/dist/react/model-context/useInteractableState.js +1 -0
- package/dist/react/model-context/useInteractableState.js.map +1 -1
- package/dist/react/model-context/useInteractableVersions.js +1 -0
- package/dist/react/model-context/useInteractableVersions.js.map +1 -1
- package/dist/react/model-context/useToolArgsStatus.js +1 -0
- package/dist/react/model-context/useToolArgsStatus.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarCopy.js +1 -0
- package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarEdit.js +1 -0
- package/dist/react/primitive-hooks/useActionBarEdit.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarFeedback.js +1 -0
- package/dist/react/primitive-hooks/useActionBarFeedback.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarReload.js +1 -0
- package/dist/react/primitive-hooks/useActionBarReload.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarSpeak.js +1 -0
- package/dist/react/primitive-hooks/useActionBarSpeak.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarStopSpeaking.js +1 -0
- package/dist/react/primitive-hooks/useActionBarStopSpeaking.js.map +1 -1
- package/dist/react/primitive-hooks/useBranchPickerNext.js +1 -0
- package/dist/react/primitive-hooks/useBranchPickerNext.js.map +1 -1
- package/dist/react/primitive-hooks/useBranchPickerPrevious.js +1 -0
- package/dist/react/primitive-hooks/useBranchPickerPrevious.js.map +1 -1
- package/dist/react/primitive-hooks/useComposerAddAttachment.js +1 -0
- package/dist/react/primitive-hooks/useComposerAddAttachment.js.map +1 -1
- package/dist/react/primitive-hooks/useComposerCancel.js +1 -0
- package/dist/react/primitive-hooks/useComposerCancel.js.map +1 -1
- package/dist/react/primitive-hooks/useComposerDictate.js +1 -0
- package/dist/react/primitive-hooks/useComposerDictate.js.map +1 -1
- package/dist/react/primitive-hooks/useComposerSend.js +1 -0
- package/dist/react/primitive-hooks/useComposerSend.js.map +1 -1
- package/dist/react/primitive-hooks/useEditComposerCancel.js +1 -0
- package/dist/react/primitive-hooks/useEditComposerCancel.js.map +1 -1
- package/dist/react/primitive-hooks/useEditComposerSend.js +1 -0
- package/dist/react/primitive-hooks/useEditComposerSend.js.map +1 -1
- package/dist/react/primitive-hooks/useMessageBranching.js +1 -0
- package/dist/react/primitive-hooks/useMessageBranching.js.map +1 -1
- package/dist/react/primitive-hooks/useMessageReload.js +1 -0
- package/dist/react/primitive-hooks/useMessageReload.js.map +1 -1
- package/dist/react/primitive-hooks/useSuggestionTrigger.js +1 -0
- package/dist/react/primitive-hooks/useSuggestionTrigger.js.map +1 -1
- package/dist/react/primitive-hooks/useThreadListItemArchive.js +1 -0
- package/dist/react/primitive-hooks/useThreadListItemArchive.js.map +1 -1
- package/dist/react/primitive-hooks/useThreadListItemDelete.js +1 -0
- package/dist/react/primitive-hooks/useThreadListItemDelete.js.map +1 -1
- package/dist/react/primitive-hooks/useThreadListItemTrigger.js +1 -0
- package/dist/react/primitive-hooks/useThreadListItemTrigger.js.map +1 -1
- package/dist/react/primitive-hooks/useThreadListItemUnarchive.js +1 -0
- package/dist/react/primitive-hooks/useThreadListItemUnarchive.js.map +1 -1
- package/dist/react/primitive-hooks/useThreadListLoadMore.js +1 -0
- package/dist/react/primitive-hooks/useThreadListLoadMore.js.map +1 -1
- package/dist/react/primitive-hooks/useThreadListNew.js +1 -0
- package/dist/react/primitive-hooks/useThreadListNew.js.map +1 -1
- package/dist/react/providers/TextMessagePartProvider.js +1 -0
- package/dist/react/providers/TextMessagePartProvider.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +6 -4
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +31 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +11 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts +1 -1
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js +1 -1
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js.map +1 -1
- package/dist/react/runtimes/cloud/auiV0.d.ts +12 -1
- package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/auiV0.js +2 -1
- package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +75 -73
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.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/useExternalStoreSharedOptions.js +1 -0
- package/dist/react/runtimes/useExternalStoreSharedOptions.js.map +1 -1
- package/dist/react/runtimes/useLocalRuntime.d.ts +0 -1
- package/dist/react/runtimes/useLocalRuntime.d.ts.map +1 -1
- package/dist/runtime/api/assistant-runtime.d.ts +1 -1
- package/dist/runtime/api/assistant-runtime.d.ts.map +1 -1
- package/dist/runtime/api/assistant-runtime.js +1 -1
- package/dist/runtime/api/assistant-runtime.js.map +1 -1
- package/dist/runtime/api/attachment-runtime.d.ts +1 -1
- package/dist/runtime/api/attachment-runtime.d.ts.map +1 -1
- package/dist/runtime/api/attachment-runtime.js +1 -1
- package/dist/runtime/api/attachment-runtime.js.map +1 -1
- package/dist/runtime/api/composer-runtime.d.ts +2 -2
- package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
- package/dist/runtime/api/composer-runtime.js +2 -2
- package/dist/runtime/api/composer-runtime.js.map +1 -1
- package/dist/runtime/api/message-part-runtime.d.ts +4 -4
- package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
- package/dist/runtime/api/message-part-runtime.js +3 -3
- package/dist/runtime/api/message-part-runtime.js.map +1 -1
- package/dist/runtime/api/message-runtime.d.ts +1 -1
- package/dist/runtime/api/message-runtime.d.ts.map +1 -1
- package/dist/runtime/api/message-runtime.js +2 -2
- package/dist/runtime/api/message-runtime.js.map +1 -1
- package/dist/runtime/api/thread-list-item-runtime.d.ts +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 -2
- package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
- package/dist/runtime/api/thread-list-runtime.d.ts +1 -1
- package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-list-runtime.js +1 -1
- package/dist/runtime/api/thread-list-runtime.js.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts +1 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.js +1 -1
- package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.d.ts +2 -2
- package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.js +2 -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 +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 +1 -1
- 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/message-repository.js +4 -2
- package/dist/runtime/utils/message-repository.js.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +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-list-runtime-core.js +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.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 +12 -0
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/internal.d.ts +2 -1
- package/dist/runtimes/internal.js +2 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts +3 -0
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +44 -13
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +32 -27
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +2 -0
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
- package/dist/store/clients/no-op-composer-client.js +1 -0
- package/dist/store/clients/no-op-composer-client.js.map +1 -1
- package/dist/store/runtime-clients/thread-list-runtime-client.js +1 -0
- package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.js +17 -15
- package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
- package/dist/store/scopes/thread.d.ts +1 -0
- package/dist/store/scopes/thread.d.ts.map +1 -1
- package/dist/subscribable/subscribable.d.ts +3 -3
- package/dist/subscribable/subscribable.d.ts.map +1 -1
- package/dist/subscribable/subscribable.js +3 -3
- package/dist/subscribable/subscribable.js.map +1 -1
- package/package.json +7 -7
- package/src/adapters/thread-history.ts +8 -0
- package/src/internal.ts +1 -1
- package/src/model-context/frame/host.test.ts +47 -0
- package/src/model-context/frame/host.ts +46 -19
- package/src/model-context/types.ts +16 -6
- package/src/react/adapters/LocalStorageThreadListAdapter.tsx +15 -5
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +20 -6
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.ts +34 -0
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +26 -5
- package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.ts +5 -1
- package/src/react/runtimes/cloud/auiV0.ts +14 -0
- package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +90 -88
- package/src/react/runtimes/external-message-converter.test.ts +346 -0
- package/src/react/runtimes/external-message-converter.ts +1 -1
- package/src/react/runtimes/useLocalRuntime.test.tsx +43 -0
- package/src/runtime/api/assistant-runtime.ts +4 -1
- package/src/runtime/api/attachment-runtime.ts +8 -2
- package/src/runtime/api/composer-runtime.ts +9 -5
- package/src/runtime/api/message-part-runtime.ts +10 -3
- package/src/runtime/api/message-runtime.ts +7 -2
- package/src/runtime/api/thread-list-item-runtime.ts +7 -2
- package/src/runtime/api/thread-list-runtime.ts +10 -2
- package/src/runtime/base/base-thread-runtime-core.ts +5 -1
- package/src/runtime/base/default-edit-composer-runtime-core.ts +14 -2
- package/src/runtime/base/default-thread-composer-runtime-core.ts +11 -1
- package/src/runtime/utils/message-repository.ts +19 -13
- package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +4 -1
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +15 -0
- package/src/runtimes/internal.ts +3 -0
- package/src/runtimes/local/local-thread-runtime-core.test.ts +596 -0
- package/src/runtimes/local/local-thread-runtime-core.ts +85 -21
- package/src/runtimes/local/should-continue.test.ts +122 -0
- package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +9 -10
- package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +38 -32
- package/src/store/clients/model-context-client.test.ts +21 -1
- package/src/store/runtime-clients/thread-runtime-client.ts +2 -1
- package/src/store/scopes/thread.ts +1 -0
- package/src/subscribable/subscribable.ts +16 -8
- package/src/tests/MessageRepository.test.ts +12 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-switchToThread-position.test.ts +121 -0
- package/src/tests/auiV0Encode.test.ts +74 -0
- package/src/tests/base-composer-runtime-core.test.ts +422 -0
- package/src/tests/external-store-thread-list-runtime-core.test.ts +6 -6
- package/src/tests/external-store-thread-runtime-core-adapter.test.ts +575 -0
- package/src/tests/external-store-thread-runtime-core.test.ts +55 -1
- package/src/tests/OptimisticState-delete-crash.test.ts +0 -208
|
@@ -76,94 +76,96 @@ export const useCloudThreadListAdapter = (
|
|
|
76
76
|
);
|
|
77
77
|
|
|
78
78
|
const cloud = adapter.cloud ?? autoCloud;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return inMemory;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
list: async () => {
|
|
91
|
-
const { threads } = await cloud.threads.list();
|
|
92
|
-
return {
|
|
93
|
-
threads: threads.map((t) => ({
|
|
94
|
-
status: t.is_archived ? "archived" : "regular",
|
|
95
|
-
remoteId: t.id,
|
|
96
|
-
title: t.title,
|
|
97
|
-
lastMessageAt: t.last_message_at
|
|
98
|
-
? new Date(t.last_message_at)
|
|
99
|
-
: undefined,
|
|
100
|
-
externalId: t.external_id ?? undefined,
|
|
101
|
-
custom: toCustom(t.metadata),
|
|
102
|
-
})),
|
|
79
|
+
return useMemo<RemoteThreadListAdapter>(() => {
|
|
80
|
+
if (!cloud) {
|
|
81
|
+
const ref = adapterRef;
|
|
82
|
+
const inMemory = new InMemoryThreadListAdapter();
|
|
83
|
+
inMemory.initialize = async (threadId: string) => {
|
|
84
|
+
const result = await ref.current.create?.();
|
|
85
|
+
return { remoteId: threadId, externalId: result?.externalId };
|
|
103
86
|
};
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
87
|
+
return inMemory;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
list: async () => {
|
|
92
|
+
const { threads } = await cloud.threads.list();
|
|
93
|
+
return {
|
|
94
|
+
threads: threads.map((t) => ({
|
|
95
|
+
status: t.is_archived ? "archived" : "regular",
|
|
96
|
+
remoteId: t.id,
|
|
97
|
+
title: t.title,
|
|
98
|
+
lastMessageAt: t.last_message_at
|
|
99
|
+
? new Date(t.last_message_at)
|
|
100
|
+
: undefined,
|
|
101
|
+
externalId: t.external_id ?? undefined,
|
|
102
|
+
custom: toCustom(t.metadata),
|
|
103
|
+
})),
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
initialize: async () => {
|
|
108
|
+
const createTask = adapterRef.current.create?.() ?? Promise.resolve();
|
|
109
|
+
const t = await createTask;
|
|
110
|
+
const external_id = t ? t.externalId : undefined;
|
|
111
|
+
const { thread_id: remoteId } = await cloud.threads.create({
|
|
112
|
+
last_message_at: new Date(),
|
|
113
|
+
external_id,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return { externalId: external_id, remoteId: remoteId };
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
rename: async (threadId, newTitle) => {
|
|
120
|
+
return cloud.threads.update(threadId, { title: newTitle });
|
|
121
|
+
},
|
|
122
|
+
updateCustom: async (threadId, custom) => {
|
|
123
|
+
return cloud.threads.update(threadId, { metadata: custom ?? null });
|
|
124
|
+
},
|
|
125
|
+
archive: async (threadId) => {
|
|
126
|
+
return cloud.threads.update(threadId, { is_archived: true });
|
|
127
|
+
},
|
|
128
|
+
unarchive: async (threadId) => {
|
|
129
|
+
return cloud.threads.update(threadId, { is_archived: false });
|
|
130
|
+
},
|
|
131
|
+
delete: async (threadId) => {
|
|
132
|
+
await adapterRef.current.delete?.(threadId);
|
|
133
|
+
return cloud.threads.delete(threadId);
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
generateTitle: async (threadId, messages) => {
|
|
137
|
+
// Filter messages to only include content types the title generator understands
|
|
138
|
+
// (reasoning, source, etc. are not needed for title generation)
|
|
139
|
+
// TODO serialize these to a more efficient format
|
|
140
|
+
const filteredMessages = messages.map((msg) => ({
|
|
141
|
+
...msg,
|
|
142
|
+
content: msg.content.filter(
|
|
143
|
+
(part) => part.type === "text" || part.type === "tool-call",
|
|
144
|
+
),
|
|
145
|
+
}));
|
|
146
|
+
|
|
147
|
+
return cloud.runs.stream({
|
|
148
|
+
thread_id: threadId,
|
|
149
|
+
assistant_id: "system/thread_title",
|
|
150
|
+
messages: filteredMessages,
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
fetch: async (threadId: string) => {
|
|
155
|
+
const thread = await cloud.threads.get(threadId);
|
|
156
|
+
return {
|
|
157
|
+
status: thread.is_archived ? "archived" : "regular",
|
|
158
|
+
remoteId: thread.id,
|
|
159
|
+
title: thread.title,
|
|
160
|
+
lastMessageAt: thread.last_message_at
|
|
161
|
+
? new Date(thread.last_message_at)
|
|
162
|
+
: undefined,
|
|
163
|
+
externalId: thread.external_id ?? undefined,
|
|
164
|
+
custom: toCustom(thread.metadata),
|
|
165
|
+
};
|
|
166
|
+
},
|
|
166
167
|
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
unstable_Provider,
|
|
169
|
+
};
|
|
170
|
+
}, [cloud, unstable_Provider]);
|
|
169
171
|
};
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { convertExternalMessages } from "./external-message-converter";
|
|
3
|
+
import type { useExternalMessageConverter } from "./external-message-converter";
|
|
4
|
+
import { isErrorMessageId } from "../../utils/id";
|
|
5
|
+
|
|
6
|
+
describe("convertExternalMessages", () => {
|
|
7
|
+
describe("reasoning part merging", () => {
|
|
8
|
+
it("should merge reasoning parts with the same parentId", () => {
|
|
9
|
+
const messages = [
|
|
10
|
+
{
|
|
11
|
+
id: "msg1",
|
|
12
|
+
role: "assistant" as const,
|
|
13
|
+
content: [
|
|
14
|
+
{
|
|
15
|
+
type: "reasoning" as const,
|
|
16
|
+
text: "First reasoning",
|
|
17
|
+
parentId: "parent1",
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "msg2",
|
|
23
|
+
role: "assistant" as const,
|
|
24
|
+
content: [
|
|
25
|
+
{
|
|
26
|
+
type: "reasoning" as const,
|
|
27
|
+
text: "Second reasoning",
|
|
28
|
+
parentId: "parent1",
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const callback: useExternalMessageConverter.Callback<
|
|
35
|
+
(typeof messages)[number]
|
|
36
|
+
> = (msg) => msg;
|
|
37
|
+
|
|
38
|
+
const result = convertExternalMessages(messages, callback, false, {});
|
|
39
|
+
|
|
40
|
+
expect(result).toHaveLength(1);
|
|
41
|
+
expect(result[0]!.role).toBe("assistant");
|
|
42
|
+
|
|
43
|
+
const reasoningParts = result[0]!.content.filter(
|
|
44
|
+
(p) => p.type === "reasoning",
|
|
45
|
+
);
|
|
46
|
+
expect(reasoningParts).toHaveLength(1);
|
|
47
|
+
expect((reasoningParts[0] as any).text).toBe(
|
|
48
|
+
"First reasoning\n\nSecond reasoning",
|
|
49
|
+
);
|
|
50
|
+
expect((reasoningParts[0] as any).parentId).toBe("parent1");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should keep reasoning parts without parentId separate", () => {
|
|
54
|
+
const messages = [
|
|
55
|
+
{
|
|
56
|
+
id: "msg1",
|
|
57
|
+
role: "assistant" as const,
|
|
58
|
+
content: [{ type: "reasoning" as const, text: "First reasoning" }],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "msg2",
|
|
62
|
+
role: "assistant" as const,
|
|
63
|
+
content: [{ type: "reasoning" as const, text: "Second reasoning" }],
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
const callback: useExternalMessageConverter.Callback<
|
|
68
|
+
(typeof messages)[number]
|
|
69
|
+
> = (msg) => msg;
|
|
70
|
+
|
|
71
|
+
const result = convertExternalMessages(messages, callback, false, {});
|
|
72
|
+
|
|
73
|
+
expect(result).toHaveLength(1);
|
|
74
|
+
|
|
75
|
+
const reasoningParts = result[0]!.content.filter(
|
|
76
|
+
(p) => p.type === "reasoning",
|
|
77
|
+
);
|
|
78
|
+
expect(reasoningParts).toHaveLength(2);
|
|
79
|
+
expect((reasoningParts[0] as any).text).toBe("First reasoning");
|
|
80
|
+
expect((reasoningParts[1] as any).text).toBe("Second reasoning");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should keep reasoning parts with different parentIds separate", () => {
|
|
84
|
+
const messages = [
|
|
85
|
+
{
|
|
86
|
+
id: "msg1",
|
|
87
|
+
role: "assistant" as const,
|
|
88
|
+
content: [
|
|
89
|
+
{
|
|
90
|
+
type: "reasoning" as const,
|
|
91
|
+
text: "Reasoning for parent1",
|
|
92
|
+
parentId: "parent1",
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: "msg2",
|
|
98
|
+
role: "assistant" as const,
|
|
99
|
+
content: [
|
|
100
|
+
{
|
|
101
|
+
type: "reasoning" as const,
|
|
102
|
+
text: "Reasoning for parent2",
|
|
103
|
+
parentId: "parent2",
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
const callback: useExternalMessageConverter.Callback<
|
|
110
|
+
(typeof messages)[number]
|
|
111
|
+
> = (msg) => msg;
|
|
112
|
+
|
|
113
|
+
const result = convertExternalMessages(messages, callback, false, {});
|
|
114
|
+
|
|
115
|
+
expect(result).toHaveLength(1);
|
|
116
|
+
|
|
117
|
+
const reasoningParts = result[0]!.content.filter(
|
|
118
|
+
(p) => p.type === "reasoning",
|
|
119
|
+
);
|
|
120
|
+
expect(reasoningParts).toHaveLength(2);
|
|
121
|
+
expect((reasoningParts[0] as any).parentId).toBe("parent1");
|
|
122
|
+
expect((reasoningParts[1] as any).parentId).toBe("parent2");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("should still merge tool results with matching tool calls", () => {
|
|
126
|
+
const messages = [
|
|
127
|
+
{
|
|
128
|
+
id: "msg1",
|
|
129
|
+
role: "assistant" as const,
|
|
130
|
+
content: [
|
|
131
|
+
{
|
|
132
|
+
type: "tool-call" as const,
|
|
133
|
+
toolCallId: "tc1",
|
|
134
|
+
toolName: "search",
|
|
135
|
+
args: { query: "test" },
|
|
136
|
+
argsText: '{"query":"test"}',
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
role: "tool" as const,
|
|
142
|
+
toolCallId: "tc1",
|
|
143
|
+
result: { data: "result" },
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
const callback: useExternalMessageConverter.Callback<
|
|
148
|
+
(typeof messages)[number]
|
|
149
|
+
> = (msg) => msg;
|
|
150
|
+
|
|
151
|
+
const result = convertExternalMessages(messages, callback, false, {});
|
|
152
|
+
|
|
153
|
+
expect(result).toHaveLength(1);
|
|
154
|
+
|
|
155
|
+
const toolCallParts = result[0]!.content.filter(
|
|
156
|
+
(p) => p.type === "tool-call",
|
|
157
|
+
);
|
|
158
|
+
expect(toolCallParts).toHaveLength(1);
|
|
159
|
+
expect((toolCallParts[0] as any).result).toEqual({ data: "result" });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("should merge duplicate tool calls by toolCallId across assistant messages", () => {
|
|
163
|
+
const messages = [
|
|
164
|
+
{
|
|
165
|
+
id: "msg1",
|
|
166
|
+
role: "assistant" as const,
|
|
167
|
+
content: [
|
|
168
|
+
{
|
|
169
|
+
type: "tool-call" as const,
|
|
170
|
+
toolCallId: "tc1",
|
|
171
|
+
toolName: "search",
|
|
172
|
+
args: { query: "old" },
|
|
173
|
+
argsText: '{"query":"old"',
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: "msg2",
|
|
179
|
+
role: "assistant" as const,
|
|
180
|
+
content: [
|
|
181
|
+
{
|
|
182
|
+
type: "tool-call" as const,
|
|
183
|
+
toolCallId: "tc1",
|
|
184
|
+
toolName: "search",
|
|
185
|
+
args: { query: "new" },
|
|
186
|
+
argsText: '{"query":"new"}',
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
const callback: useExternalMessageConverter.Callback<
|
|
193
|
+
(typeof messages)[number]
|
|
194
|
+
> = (msg) => msg;
|
|
195
|
+
|
|
196
|
+
const result = convertExternalMessages(messages, callback, false, {});
|
|
197
|
+
|
|
198
|
+
expect(result).toHaveLength(1);
|
|
199
|
+
expect(result[0]!.role).toBe("assistant");
|
|
200
|
+
const toolCallParts = result[0]!.content.filter(
|
|
201
|
+
(p) => p.type === "tool-call",
|
|
202
|
+
);
|
|
203
|
+
expect(toolCallParts).toHaveLength(1);
|
|
204
|
+
expect((toolCallParts[0] as any).args).toEqual({ query: "new" });
|
|
205
|
+
expect((toolCallParts[0] as any).argsText).toBe('{"query":"new"}');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("should ignore orphaned tool results without throwing", () => {
|
|
209
|
+
const messages = [
|
|
210
|
+
{
|
|
211
|
+
id: "msg1",
|
|
212
|
+
role: "assistant" as const,
|
|
213
|
+
content: "First response",
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
role: "tool" as const,
|
|
217
|
+
toolCallId: "missing-tool-call",
|
|
218
|
+
toolName: "search",
|
|
219
|
+
result: { data: "orphan result" },
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: "msg2",
|
|
223
|
+
role: "assistant" as const,
|
|
224
|
+
content: "Second response",
|
|
225
|
+
},
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
const callback: useExternalMessageConverter.Callback<
|
|
229
|
+
(typeof messages)[number]
|
|
230
|
+
> = (msg) => msg;
|
|
231
|
+
|
|
232
|
+
const result = convertExternalMessages(messages, callback, false, {});
|
|
233
|
+
expect(result).toHaveLength(1);
|
|
234
|
+
expect(result[0]!.role).toBe("assistant");
|
|
235
|
+
|
|
236
|
+
const textParts = result[0]!.content.filter((p) => p.type === "text");
|
|
237
|
+
expect(textParts).toHaveLength(2);
|
|
238
|
+
expect((textParts[0] as any).text).toBe("First response");
|
|
239
|
+
expect((textParts[1] as any).text).toBe("Second response");
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe("synthetic error message", () => {
|
|
244
|
+
it("should create synthetic error message when error exists and no messages", () => {
|
|
245
|
+
const messages: never[] = [];
|
|
246
|
+
const callback: useExternalMessageConverter.Callback<never> = (msg) =>
|
|
247
|
+
msg;
|
|
248
|
+
|
|
249
|
+
const result = convertExternalMessages(messages, callback, false, {
|
|
250
|
+
error: "API key is missing",
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
expect(result).toHaveLength(1);
|
|
254
|
+
expect(result[0]!.role).toBe("assistant");
|
|
255
|
+
expect(result[0]!.content).toHaveLength(0);
|
|
256
|
+
expect(result[0]!.status).toEqual({
|
|
257
|
+
type: "incomplete",
|
|
258
|
+
reason: "error",
|
|
259
|
+
error: "API key is missing",
|
|
260
|
+
});
|
|
261
|
+
expect(isErrorMessageId(result[0]!.id)).toBe(true);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("should create synthetic error message when error exists and last message is user", () => {
|
|
265
|
+
const messages = [
|
|
266
|
+
{
|
|
267
|
+
id: "user1",
|
|
268
|
+
role: "user" as const,
|
|
269
|
+
content: "Hello",
|
|
270
|
+
},
|
|
271
|
+
];
|
|
272
|
+
|
|
273
|
+
const callback: useExternalMessageConverter.Callback<
|
|
274
|
+
(typeof messages)[number]
|
|
275
|
+
> = (msg) => msg;
|
|
276
|
+
|
|
277
|
+
const result = convertExternalMessages(messages, callback, false, {
|
|
278
|
+
error: { message: "Invalid API key" },
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
expect(result).toHaveLength(2);
|
|
282
|
+
expect(result[0]!.role).toBe("user");
|
|
283
|
+
expect(result[1]!.role).toBe("assistant");
|
|
284
|
+
expect(result[1]!.content).toHaveLength(0);
|
|
285
|
+
expect(result[1]!.status).toEqual({
|
|
286
|
+
type: "incomplete",
|
|
287
|
+
reason: "error",
|
|
288
|
+
error: { message: "Invalid API key" },
|
|
289
|
+
});
|
|
290
|
+
expect(isErrorMessageId(result[1]!.id)).toBe(true);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("should not create synthetic error message when last message is assistant", () => {
|
|
294
|
+
const messages = [
|
|
295
|
+
{
|
|
296
|
+
id: "user1",
|
|
297
|
+
role: "user" as const,
|
|
298
|
+
content: "Hello",
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
id: "assistant1",
|
|
302
|
+
role: "assistant" as const,
|
|
303
|
+
content: "Hi there",
|
|
304
|
+
},
|
|
305
|
+
];
|
|
306
|
+
|
|
307
|
+
const callback: useExternalMessageConverter.Callback<
|
|
308
|
+
(typeof messages)[number]
|
|
309
|
+
> = (msg) => msg;
|
|
310
|
+
|
|
311
|
+
const result = convertExternalMessages(messages, callback, false, {
|
|
312
|
+
error: "Connection error",
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
expect(result).toHaveLength(2);
|
|
316
|
+
expect(result[0]!.role).toBe("user");
|
|
317
|
+
expect(result[1]!.role).toBe("assistant");
|
|
318
|
+
expect(result[1]!.id).toBe("assistant1");
|
|
319
|
+
expect(result[1]!.status).toMatchObject({
|
|
320
|
+
type: "incomplete",
|
|
321
|
+
reason: "error",
|
|
322
|
+
error: "Connection error",
|
|
323
|
+
});
|
|
324
|
+
expect(isErrorMessageId(result[1]!.id)).toBe(false);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it("should not create synthetic message when no error", () => {
|
|
328
|
+
const messages = [
|
|
329
|
+
{
|
|
330
|
+
id: "user1",
|
|
331
|
+
role: "user" as const,
|
|
332
|
+
content: "Hello",
|
|
333
|
+
},
|
|
334
|
+
];
|
|
335
|
+
|
|
336
|
+
const callback: useExternalMessageConverter.Callback<
|
|
337
|
+
(typeof messages)[number]
|
|
338
|
+
> = (msg) => msg;
|
|
339
|
+
|
|
340
|
+
const result = convertExternalMessages(messages, callback, false, {});
|
|
341
|
+
|
|
342
|
+
expect(result).toHaveLength(1);
|
|
343
|
+
expect(result[0]!.role).toBe("user");
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
});
|
|
@@ -118,7 +118,7 @@ const joinExternalMessages = (
|
|
|
118
118
|
const toolCall = assistantMessage.content[
|
|
119
119
|
toolCallIdx
|
|
120
120
|
]! as ToolCallMessagePart;
|
|
121
|
-
if (output.toolName
|
|
121
|
+
if (output.toolName != null) {
|
|
122
122
|
if (toolCall.toolName !== output.toolName)
|
|
123
123
|
throw new Error(
|
|
124
124
|
`Tool call name ${output.toolCallId} ${output.toolName} does not match existing tool call ${toolCall.toolName}`,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { render, waitFor } from "@testing-library/react";
|
|
4
4
|
import { StrictMode } from "react";
|
|
5
5
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import type { AssistantCloud } from "assistant-cloud";
|
|
6
7
|
import type { ChatModelAdapter } from "../../runtime/utils/chat-model-adapter";
|
|
7
8
|
import { AssistantRuntimeProvider } from "../AssistantRuntimeProvider";
|
|
8
9
|
import { useLocalRuntime } from "./useLocalRuntime";
|
|
@@ -11,11 +12,53 @@ const chatModel: ChatModelAdapter = {
|
|
|
11
12
|
run: async () => ({ content: [] }),
|
|
12
13
|
};
|
|
13
14
|
|
|
15
|
+
const makeCloud = () =>
|
|
16
|
+
({
|
|
17
|
+
threads: {
|
|
18
|
+
list: vi.fn().mockResolvedValue({ threads: [] }),
|
|
19
|
+
},
|
|
20
|
+
}) as unknown as AssistantCloud;
|
|
21
|
+
|
|
14
22
|
afterEach(() => {
|
|
15
23
|
vi.restoreAllMocks();
|
|
16
24
|
});
|
|
17
25
|
|
|
18
26
|
describe("useLocalRuntime", () => {
|
|
27
|
+
it("keeps the Cloud thread list loaded across unrelated rerenders", async () => {
|
|
28
|
+
const firstCloud = makeCloud();
|
|
29
|
+
const secondCloud = makeCloud();
|
|
30
|
+
|
|
31
|
+
const App = ({
|
|
32
|
+
cloud,
|
|
33
|
+
label,
|
|
34
|
+
}: {
|
|
35
|
+
cloud: AssistantCloud;
|
|
36
|
+
label: string;
|
|
37
|
+
}) => {
|
|
38
|
+
const runtime = useLocalRuntime(chatModel, { cloud });
|
|
39
|
+
return (
|
|
40
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
41
|
+
<div>{label}</div>
|
|
42
|
+
</AssistantRuntimeProvider>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const { rerender } = render(<App cloud={firstCloud} label="first" />);
|
|
47
|
+
|
|
48
|
+
await waitFor(() => {
|
|
49
|
+
expect(firstCloud.threads.list).toHaveBeenCalledOnce();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
rerender(<App cloud={firstCloud} label="second" />);
|
|
53
|
+
expect(firstCloud.threads.list).toHaveBeenCalledOnce();
|
|
54
|
+
|
|
55
|
+
rerender(<App cloud={secondCloud} label="third" />);
|
|
56
|
+
await waitFor(() => {
|
|
57
|
+
expect(secondCloud.threads.list).toHaveBeenCalledOnce();
|
|
58
|
+
});
|
|
59
|
+
expect(firstCloud.threads.list).toHaveBeenCalledOnce();
|
|
60
|
+
});
|
|
61
|
+
|
|
19
62
|
it("handles rejected history loads", async () => {
|
|
20
63
|
const error = new Error("history unavailable");
|
|
21
64
|
const consoleError = vi
|
|
@@ -31,7 +31,10 @@ export class AssistantRuntimeImpl implements AssistantRuntime {
|
|
|
31
31
|
|
|
32
32
|
public readonly _thread: ThreadRuntime;
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
private readonly _core: AssistantRuntimeCore;
|
|
35
|
+
|
|
36
|
+
constructor(_core: AssistantRuntimeCore) {
|
|
37
|
+
this._core = _core;
|
|
35
38
|
this.threads = new ThreadListRuntimeImpl(_core.threads);
|
|
36
39
|
this._thread = this.threads.main;
|
|
37
40
|
|
|
@@ -49,7 +49,10 @@ export abstract class AttachmentRuntimeImpl<
|
|
|
49
49
|
|
|
50
50
|
public abstract get source(): Source;
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
private _core: AttachmentSnapshotBinding<Source>;
|
|
53
|
+
|
|
54
|
+
constructor(_core: AttachmentSnapshotBinding<Source>) {
|
|
55
|
+
this._core = _core;
|
|
53
56
|
this.__internal_bindMethods();
|
|
54
57
|
}
|
|
55
58
|
|
|
@@ -73,11 +76,14 @@ export abstract class AttachmentRuntimeImpl<
|
|
|
73
76
|
abstract class ComposerAttachmentRuntime<
|
|
74
77
|
Source extends "thread-composer" | "edit-composer",
|
|
75
78
|
> extends AttachmentRuntimeImpl<Source> {
|
|
79
|
+
private _composerApi: ComposerRuntimeCoreBinding;
|
|
80
|
+
|
|
76
81
|
constructor(
|
|
77
82
|
core: AttachmentSnapshotBinding<Source>,
|
|
78
|
-
|
|
83
|
+
_composerApi: ComposerRuntimeCoreBinding,
|
|
79
84
|
) {
|
|
80
85
|
super(core);
|
|
86
|
+
this._composerApi = _composerApi;
|
|
81
87
|
}
|
|
82
88
|
|
|
83
89
|
public remove() {
|
|
@@ -251,7 +251,11 @@ export abstract class ComposerRuntimeImpl implements ComposerRuntime {
|
|
|
251
251
|
|
|
252
252
|
public abstract get type(): "edit" | "thread";
|
|
253
253
|
|
|
254
|
-
|
|
254
|
+
protected _core: ComposerRuntimeCoreBinding;
|
|
255
|
+
|
|
256
|
+
constructor(_core: ComposerRuntimeCoreBinding) {
|
|
257
|
+
this._core = _core;
|
|
258
|
+
}
|
|
255
259
|
|
|
256
260
|
protected __internal_bindMethods() {
|
|
257
261
|
this.setText = this.setText.bind(this);
|
|
@@ -483,10 +487,9 @@ export class EditComposerRuntimeImpl
|
|
|
483
487
|
}
|
|
484
488
|
|
|
485
489
|
private _getState;
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
) {
|
|
490
|
+
private _beginEdit: () => void;
|
|
491
|
+
|
|
492
|
+
constructor(core: EditComposerRuntimeCoreBinding, _beginEdit: () => void) {
|
|
490
493
|
const stateBinding = new LazyMemoizeSubject({
|
|
491
494
|
path: core.path,
|
|
492
495
|
getState: () => getEditComposerState(core.getState()),
|
|
@@ -498,6 +501,7 @@ export class EditComposerRuntimeImpl
|
|
|
498
501
|
getState: () => core.getState(),
|
|
499
502
|
subscribe: (callback) => stateBinding.subscribe(callback),
|
|
500
503
|
});
|
|
504
|
+
this._beginEdit = _beginEdit;
|
|
501
505
|
|
|
502
506
|
this._getState = stateBinding.getState.bind(stateBinding);
|
|
503
507
|
|