@assistant-ui/core 0.3.17 → 0.3.18
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/attachment.js +5 -4
- package/dist/adapters/attachment.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js.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 +10 -4
- package/dist/model-context/frame/host.js.map +1 -1
- package/dist/model-context/frame/provider.d.ts +2 -0
- package/dist/model-context/frame/provider.d.ts.map +1 -1
- package/dist/model-context/frame/provider.js +30 -7
- package/dist/model-context/frame/provider.js.map +1 -1
- package/dist/model-context/interactable-composer-metadata.d.ts.map +1 -1
- package/dist/model-context/interactable-composer-metadata.js +16 -9
- package/dist/model-context/interactable-composer-metadata.js.map +1 -1
- package/dist/model-context/registry.d.ts.map +1 -1
- package/dist/model-context/registry.js +3 -5
- package/dist/model-context/registry.js.map +1 -1
- package/dist/model-context/types.d.ts.map +1 -1
- package/dist/model-context/types.js +5 -4
- package/dist/model-context/types.js.map +1 -1
- package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
- package/dist/react/client/InMemoryThreadList.js +62 -56
- package/dist/react/client/InMemoryThreadList.js.map +1 -1
- package/dist/react/client/Interactables.d.ts.map +1 -1
- package/dist/react/client/Interactables.js +24 -27
- package/dist/react/client/Interactables.js.map +1 -1
- package/dist/react/client/RemoteThreadList.d.ts +1 -0
- package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
- package/dist/react/client/RemoteThreadList.js +151 -141
- package/dist/react/client/RemoteThreadList.js.map +1 -1
- package/dist/react/client/Tools.d.ts.map +1 -1
- package/dist/react/client/Tools.js +18 -16
- package/dist/react/client/Tools.js.map +1 -1
- package/dist/react/interactables-legacy/Interactables.js +242 -121
- package/dist/react/interactables-legacy/Interactables.js.map +1 -1
- package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -1
- package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +11 -9
- package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -1
- package/dist/react/model-context/makeAssistantTool.d.ts +2 -2
- package/dist/react/model-context/makeAssistantTool.js +1 -1
- package/dist/react/model-context/makeAssistantTool.js.map +1 -1
- package/dist/react/model-context/useAssistantTool.d.ts +2 -2
- package/dist/react/model-context/useAssistantTool.js +1 -1
- package/dist/react/model-context/useAssistantTool.js.map +1 -1
- package/dist/react/model-context/useToolArgsStatus.d.ts.map +1 -1
- package/dist/react/model-context/useToolArgsStatus.js +2 -1
- package/dist/react/model-context/useToolArgsStatus.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +16 -8
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +5 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +103 -103
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadResource.d.ts +1 -0
- package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadResource.js +13 -11
- package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +4 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +401 -34
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
- package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js +2 -1
- package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js.map +1 -1
- package/dist/react/runtimes/useExternalStoreRuntime.js +75 -46
- package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
- package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
- package/dist/react/runtimes/useRemoteThreadListRuntime.js +63 -23
- package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
- package/dist/runtime/api/thread-list-item-runtime.d.ts +7 -3
- 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 +3 -0
- package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-list-runtime.js +18 -14
- package/dist/runtime/api/thread-list-runtime.js.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts +4 -0
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-runtime.js +1 -1
- package/dist/runtime/api/thread-runtime.js.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js +16 -12
- 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 +6 -5
- package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
- package/dist/runtime/interfaces/composer-runtime-core.d.ts +12 -5
- package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +4 -1
- package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtime/utils/external-message-conversion.js +1 -1
- package/dist/runtime/utils/external-message-conversion.js.map +1 -1
- package/dist/runtime/utils/message-repository.d.ts +1 -0
- package/dist/runtime/utils/message-repository.d.ts.map +1 -1
- package/dist/runtime/utils/message-repository.js +15 -6
- package/dist/runtime/utils/message-repository.js.map +1 -1
- package/dist/runtime/utils/thread-message-like.js +1 -1
- package/dist/runtime/utils/thread-message-like.js.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-list-runtime-core.js +21 -27
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
- package/dist/runtimes/internal.d.ts +2 -2
- package/dist/runtimes/internal.js +2 -2
- package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +18 -1
- package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/remote-thread-state.js +130 -42
- package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
- package/dist/runtimes/remote-thread-list/title-generation.d.ts +49 -0
- package/dist/runtimes/remote-thread-list/title-generation.d.ts.map +1 -0
- package/dist/runtimes/remote-thread-list/title-generation.js +197 -0
- package/dist/runtimes/remote-thread-list/title-generation.js.map +1 -0
- package/dist/runtimes/remote-thread-list/title.d.ts +3 -1
- package/dist/runtimes/remote-thread-list/title.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/title.js +9 -1
- package/dist/runtimes/remote-thread-list/title.js.map +1 -1
- package/dist/runtimes/remote-thread-list/types.d.ts +10 -0
- package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
- package/dist/store/clients/external-thread.d.ts.map +1 -1
- package/dist/store/clients/external-thread.js +19 -3
- package/dist/store/clients/external-thread.js.map +1 -1
- package/dist/store/clients/model-context-client.d.ts.map +1 -1
- package/dist/store/clients/model-context-client.js +2 -1
- package/dist/store/clients/model-context-client.js.map +1 -1
- package/dist/store/clients/single-thread-list.d.ts.map +1 -1
- package/dist/store/clients/single-thread-list.js +1 -0
- package/dist/store/clients/single-thread-list.js.map +1 -1
- package/dist/store/clients/suggestions.d.ts.map +1 -1
- package/dist/store/clients/suggestions.js +5 -4
- package/dist/store/clients/suggestions.js.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.d.ts +1 -0
- package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.js +97 -61
- package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/message-runtime-client.d.ts +1 -0
- package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/message-runtime-client.js +221 -144
- package/dist/store/runtime-clients/message-runtime-client.js.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-item-runtime-client.js +1 -1
- package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-list-runtime-client.js +54 -52
- 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 +193 -121
- package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
- package/dist/store/scopes/composer.d.ts +10 -2
- package/dist/store/scopes/composer.d.ts.map +1 -1
- package/dist/store/scopes/message.d.ts +30 -1
- package/dist/store/scopes/message.d.ts.map +1 -1
- package/dist/store/scopes/thread-list-item.d.ts +2 -2
- package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
- package/dist/store/scopes/thread.d.ts +8 -0
- package/dist/store/scopes/thread.d.ts.map +1 -1
- package/dist/store/scopes/threads.d.ts +1 -0
- package/dist/store/scopes/threads.d.ts.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 +2 -1
- package/dist/types/attachment.js.map +1 -1
- package/dist/utils/abortable-iterable.d.ts +19 -0
- package/dist/utils/abortable-iterable.d.ts.map +1 -0
- package/dist/utils/abortable-iterable.js +72 -0
- package/dist/utils/abortable-iterable.js.map +1 -0
- package/dist/utils/composite-context-provider.d.ts.map +1 -1
- package/dist/utils/composite-context-provider.js +20 -2
- package/dist/utils/composite-context-provider.js.map +1 -1
- package/dist/utils/record.d.ts +11 -0
- package/dist/utils/record.d.ts.map +1 -0
- package/dist/utils/record.js +12 -0
- package/dist/utils/record.js.map +1 -0
- package/package.json +10 -10
- package/src/adapters/attachment.test.ts +50 -1
- package/src/adapters/attachment.ts +7 -4
- package/src/index.ts +1 -0
- package/src/internal.ts +4 -0
- package/src/model-context/frame/host.test.ts +24 -0
- package/src/model-context/frame/host.ts +10 -4
- package/src/model-context/frame/provider.test.ts +71 -0
- package/src/model-context/frame/provider.ts +37 -7
- package/src/model-context/interactable-composer-metadata.test.ts +28 -0
- package/src/model-context/interactable-composer-metadata.ts +9 -3
- package/src/model-context/registry.test.ts +12 -0
- package/src/model-context/registry.ts +3 -2
- package/src/model-context/types.test.ts +25 -0
- package/src/model-context/types.ts +9 -4
- package/src/react/RuntimeAdapter.test.ts +34 -0
- package/src/react/client/InMemoryThreadList.ts +1 -0
- package/src/react/client/Interactables.test.ts +28 -0
- package/src/react/client/Interactables.ts +15 -13
- package/src/react/client/RemoteThreadList.background.test.ts +148 -6
- package/src/react/client/RemoteThreadList.concurrent.test.tsx +42 -1
- package/src/react/client/RemoteThreadList.test.ts +345 -3
- package/src/react/client/RemoteThreadList.ts +166 -110
- package/src/react/client/Tools.ts +22 -11
- package/src/react/interactables-legacy/Interactables.ts +16 -16
- package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +9 -10
- package/src/react/model-context/makeAssistantTool.ts +2 -2
- package/src/react/model-context/useAssistantTool.ts +2 -2
- package/src/react/model-context/useToolArgsStatus.test.tsx +30 -0
- package/src/react/model-context/useToolArgsStatus.ts +2 -1
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.destroy.test.tsx +172 -0
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +33 -0
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +7 -1
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +28 -5
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.load-error.test.ts +132 -0
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.switch-delete.test.ts +105 -0
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +136 -111
- package/src/react/runtimes/RemoteThreadResource.test.ts +9 -0
- package/src/react/runtimes/RemoteThreadResource.ts +19 -12
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +483 -6
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +504 -68
- package/src/react/runtimes/cloud/createCloudThreadListAdapter.test.ts +15 -0
- package/src/react/runtimes/cloud/createCloudThreadListAdapter.ts +1 -0
- package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +41 -0
- package/src/react/runtimes/useExternalStoreRuntime.ts +10 -4
- package/src/react/runtimes/useLocalRuntime.test.tsx +20 -0
- package/src/react/runtimes/useRemoteThreadListRuntime.load-error.test.tsx +108 -0
- package/src/react/runtimes/useRemoteThreadListRuntime.ts +33 -0
- package/src/runtime/api/message-runtime.test.ts +75 -0
- package/src/runtime/api/thread-list-item-runtime.ts +10 -3
- package/src/runtime/api/thread-list-runtime.ts +21 -11
- package/src/runtime/api/thread-runtime.test.ts +78 -0
- package/src/runtime/api/thread-runtime.ts +8 -1
- package/src/runtime/base/base-composer-runtime-core.ts +36 -8
- package/src/runtime/base/base-thread-runtime-core.test.ts +55 -1
- package/src/runtime/base/base-thread-runtime-core.ts +6 -4
- package/src/runtime/interfaces/composer-runtime-core.ts +10 -5
- package/src/runtime/interfaces/thread-list-runtime-core.ts +5 -1
- package/src/runtime/utils/external-message-conversion.test.ts +68 -0
- package/src/runtime/utils/external-message-conversion.ts +1 -1
- package/src/runtime/utils/message-repository.ts +29 -17
- package/src/runtime/utils/thread-message-like.ts +1 -1
- package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +18 -13
- package/src/runtimes/internal.ts +1 -0
- package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +128 -2
- package/src/runtimes/remote-thread-list/remote-thread-state.ts +182 -42
- package/src/runtimes/remote-thread-list/title-generation.test.ts +602 -0
- package/src/runtimes/remote-thread-list/title-generation.ts +329 -0
- package/src/runtimes/remote-thread-list/title.test.ts +62 -0
- package/src/runtimes/remote-thread-list/title.ts +13 -0
- package/src/runtimes/remote-thread-list/types.ts +10 -0
- package/src/store/clients/external-thread.ts +48 -3
- package/src/store/clients/model-context-client.ts +5 -1
- package/src/store/clients/single-thread-list.ts +1 -0
- package/src/store/clients/suggestions.test.ts +40 -0
- package/src/store/clients/suggestions.ts +13 -10
- package/src/store/runtime-clients/composer-runtime-client.ts +42 -9
- package/src/store/runtime-clients/message-runtime-client.ts +54 -6
- package/src/store/runtime-clients/thread-list-item-runtime-client.ts +4 -2
- package/src/store/runtime-clients/thread-list-runtime-client.ts +1 -0
- package/src/store/runtime-clients/thread-runtime-client.ts +58 -10
- package/src/store/scopes/composer.ts +16 -4
- package/src/store/scopes/message.ts +14 -0
- package/src/store/scopes/thread-list-item.ts +5 -2
- package/src/store/scopes/thread.ts +4 -0
- package/src/store/scopes/threads.ts +1 -0
- package/src/tests/MessageRepository.test.ts +116 -0
- package/src/tests/OptimisticState-list-race.test.ts +1 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-initialize-adapter-change.test.ts +38 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +47 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +89 -0
- package/src/tests/attachment-adapters.test.ts +14 -0
- package/src/tests/base-composer-runtime-core.test.ts +39 -0
- package/src/tests/external-store-thread-list-runtime-core.test.ts +83 -0
- package/src/tests/external-store-thread-runtime-core-adapter.test.ts +27 -18
- package/src/tests/external-thread-attachments.test.tsx +139 -0
- package/src/tests/external-thread-parity.test.tsx +23 -0
- package/src/tests/remote-thread-list-isLoading.test.ts +1 -0
- package/src/tests/thread-list-runtime-getLoadThreadsPromise.test.ts +1 -0
- package/src/tests/thread-switch-events.test.tsx +78 -0
- package/src/tests/tools-scope-migration.test.tsx +28 -0
- package/src/types/attachment.ts +4 -0
- package/src/utils/abortable-iterable.test.ts +213 -0
- package/src/utils/abortable-iterable.ts +93 -0
- package/src/utils/composite-context-provider.test.ts +25 -0
- package/src/utils/composite-context-provider.ts +23 -2
- package/src/utils/record.test.ts +48 -0
- package/src/utils/record.ts +11 -0
|
@@ -5,6 +5,21 @@ import { renderHook } from "@testing-library/react";
|
|
|
5
5
|
import type { AssistantCloud } from "assistant-cloud";
|
|
6
6
|
import { createCloudThreadListAdapter } from "./createCloudThreadListAdapter";
|
|
7
7
|
|
|
8
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => ({
|
|
9
|
+
...(await importOriginal<typeof import("@assistant-ui/store")>()),
|
|
10
|
+
useAui: () => ({
|
|
11
|
+
threads: {
|
|
12
|
+
getState: () => ({ mainThreadId: "local-1", threadItems: [] }),
|
|
13
|
+
},
|
|
14
|
+
thread: { getState: () => ({ isEmpty: true, suggestions: [] }) },
|
|
15
|
+
threadListItem: {
|
|
16
|
+
getState: () => ({ id: "local-1", remoteId: "remote-1" }),
|
|
17
|
+
},
|
|
18
|
+
on: () => () => {},
|
|
19
|
+
subscribe: () => () => {},
|
|
20
|
+
}),
|
|
21
|
+
}));
|
|
22
|
+
|
|
8
23
|
const makeCloud = () =>
|
|
9
24
|
({
|
|
10
25
|
threads: {
|
|
@@ -5,8 +5,49 @@ import { act, render } from "@testing-library/react";
|
|
|
5
5
|
import { describe, expect, it, vi } from "vitest";
|
|
6
6
|
import { useExternalStoreRuntime } from "./useExternalStoreRuntime";
|
|
7
7
|
import type { AssistantRuntime } from "../../runtime/api/assistant-runtime";
|
|
8
|
+
import type { ThreadMessage } from "../../types/message";
|
|
9
|
+
import { RuntimeAdapterProvider } from "./RuntimeAdapterProvider";
|
|
10
|
+
|
|
11
|
+
const userMessage: ThreadMessage = {
|
|
12
|
+
id: "user-1",
|
|
13
|
+
role: "user",
|
|
14
|
+
content: [{ type: "text", text: "hello" }],
|
|
15
|
+
attachments: [],
|
|
16
|
+
createdAt: new Date(0),
|
|
17
|
+
metadata: { custom: {} },
|
|
18
|
+
};
|
|
8
19
|
|
|
9
20
|
describe("useExternalStoreRuntime lifecycle", () => {
|
|
21
|
+
it("uses feedback supplied by the per-thread adapter context", () => {
|
|
22
|
+
const submit = vi.fn();
|
|
23
|
+
const capture: { runtime: AssistantRuntime | null } = { runtime: null };
|
|
24
|
+
const App = () => {
|
|
25
|
+
const runtime = useExternalStoreRuntime({
|
|
26
|
+
messages: [userMessage],
|
|
27
|
+
onNew: async () => {},
|
|
28
|
+
});
|
|
29
|
+
capture.runtime = runtime;
|
|
30
|
+
return null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
render(
|
|
34
|
+
<RuntimeAdapterProvider adapters={{ feedback: { submit } }}>
|
|
35
|
+
<App />
|
|
36
|
+
</RuntimeAdapterProvider>,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(capture.runtime!.thread.getState().capabilities.feedback).toBe(true);
|
|
40
|
+
act(() => {
|
|
41
|
+
capture
|
|
42
|
+
.runtime!.thread.getMessageById("user-1")
|
|
43
|
+
.submitFeedback({ type: "positive" });
|
|
44
|
+
});
|
|
45
|
+
expect(submit).toHaveBeenCalledWith({
|
|
46
|
+
message: expect.objectContaining({ id: "user-1" }),
|
|
47
|
+
type: "positive",
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
10
51
|
it("keeps dispatching appends after StrictMode's simulated remount", async () => {
|
|
11
52
|
const onNew = vi.fn(async () => {});
|
|
12
53
|
const capture: { runtime: AssistantRuntime | null } = { runtime: null };
|
|
@@ -11,7 +11,15 @@ import { useRuntimeAdapters } from "./RuntimeAdapterProvider";
|
|
|
11
11
|
export const useExternalStoreRuntime = <T>(
|
|
12
12
|
store: ExternalStoreAdapter<T>,
|
|
13
13
|
): AssistantRuntime => {
|
|
14
|
-
const
|
|
14
|
+
const { modelContext, feedback } = useRuntimeAdapters() ?? {};
|
|
15
|
+
const adaptedStore = useMemo(() => {
|
|
16
|
+
if (!feedback || store.adapters?.feedback) return store;
|
|
17
|
+
return {
|
|
18
|
+
...store,
|
|
19
|
+
adapters: { ...store.adapters, feedback },
|
|
20
|
+
};
|
|
21
|
+
}, [feedback, store]);
|
|
22
|
+
const [runtime] = useState(() => new ExternalStoreRuntimeCore(adaptedStore));
|
|
15
23
|
|
|
16
24
|
useEffect(() => {
|
|
17
25
|
return () => {
|
|
@@ -20,11 +28,9 @@ export const useExternalStoreRuntime = <T>(
|
|
|
20
28
|
}, [runtime]);
|
|
21
29
|
|
|
22
30
|
useEffect(() => {
|
|
23
|
-
runtime.setAdapter(
|
|
31
|
+
runtime.setAdapter(adaptedStore);
|
|
24
32
|
});
|
|
25
33
|
|
|
26
|
-
const { modelContext } = useRuntimeAdapters() ?? {};
|
|
27
|
-
|
|
28
34
|
useEffect(() => {
|
|
29
35
|
if (!modelContext) return undefined;
|
|
30
36
|
return runtime.registerModelContextProvider(modelContext);
|
|
@@ -32,6 +32,26 @@ afterEach(() => {
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
describe("useLocalRuntime", () => {
|
|
35
|
+
it("enables feedback for Cloud threads", async () => {
|
|
36
|
+
const cloud = makeCloud();
|
|
37
|
+
let runtime: ReturnType<typeof useLocalRuntime> | null = null;
|
|
38
|
+
const App = () => {
|
|
39
|
+
runtime = useLocalRuntime(chatModel, { cloud });
|
|
40
|
+
return (
|
|
41
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
42
|
+
<div />
|
|
43
|
+
</AssistantRuntimeProvider>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
render(<App />);
|
|
48
|
+
|
|
49
|
+
await waitFor(() => {
|
|
50
|
+
expect(cloud.threads.list).toHaveBeenCalledTimes(2);
|
|
51
|
+
expect(runtime!.thread.getState().capabilities.feedback).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
35
55
|
it("surfaces the live thread after mount without user input", async () => {
|
|
36
56
|
const auiRef: { current: ReturnType<typeof useAui> | null } = {
|
|
37
57
|
current: null,
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { act, cleanup, render, waitFor } from "@testing-library/react";
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import type { AssistantRuntime } from "../../runtime/api/assistant-runtime";
|
|
6
|
+
import { makeAdapter } from "../../tests/remote-thread-list-test-helpers";
|
|
7
|
+
import { AssistantRuntimeProvider } from "../AssistantRuntimeProvider";
|
|
8
|
+
import { useExternalStoreRuntime } from "./useExternalStoreRuntime";
|
|
9
|
+
import { useRemoteThreadListRuntime } from "./useRemoteThreadListRuntime";
|
|
10
|
+
|
|
11
|
+
const EMPTY_MESSAGES: readonly never[] = [];
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
cleanup();
|
|
15
|
+
vi.restoreAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("useRemoteThreadListRuntime load recovery", () => {
|
|
19
|
+
it("reloads once when the browser comes online after a failed load", async () => {
|
|
20
|
+
const error = new Error("offline");
|
|
21
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
22
|
+
const list = vi
|
|
23
|
+
.fn()
|
|
24
|
+
.mockRejectedValueOnce(error)
|
|
25
|
+
.mockResolvedValueOnce({ threads: [] });
|
|
26
|
+
const adapter = makeAdapter({ list });
|
|
27
|
+
const runtimeRef: { current: AssistantRuntime | null } = { current: null };
|
|
28
|
+
const useThreadRuntime = () =>
|
|
29
|
+
useExternalStoreRuntime({
|
|
30
|
+
messages: EMPTY_MESSAGES,
|
|
31
|
+
onNew: async () => {},
|
|
32
|
+
} as never);
|
|
33
|
+
|
|
34
|
+
const App = () => {
|
|
35
|
+
const runtime = useRemoteThreadListRuntime({
|
|
36
|
+
adapter,
|
|
37
|
+
runtimeHook: useThreadRuntime,
|
|
38
|
+
});
|
|
39
|
+
runtimeRef.current = runtime;
|
|
40
|
+
return (
|
|
41
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
42
|
+
{null}
|
|
43
|
+
</AssistantRuntimeProvider>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
render(<App />);
|
|
48
|
+
await waitFor(() => expect(list).toHaveBeenCalledTimes(1));
|
|
49
|
+
await waitFor(() =>
|
|
50
|
+
expect(runtimeRef.current!.threads.getState().loadError).toBe(error),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
act(() => window.dispatchEvent(new Event("online")));
|
|
54
|
+
|
|
55
|
+
await waitFor(() => expect(list).toHaveBeenCalledTimes(2));
|
|
56
|
+
await act(async () => {});
|
|
57
|
+
expect(list).toHaveBeenCalledTimes(2);
|
|
58
|
+
expect(runtimeRef.current!.threads.getState().loadError).toBeUndefined();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("reloads once when the page becomes visible after a failed load", async () => {
|
|
62
|
+
const error = new Error("offline");
|
|
63
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
64
|
+
vi.spyOn(document, "visibilityState", "get").mockReturnValue("visible");
|
|
65
|
+
const list = vi
|
|
66
|
+
.fn()
|
|
67
|
+
.mockRejectedValueOnce(error)
|
|
68
|
+
.mockResolvedValueOnce({ threads: [] });
|
|
69
|
+
const adapter = makeAdapter({ list });
|
|
70
|
+
const runtimeRef: { current: AssistantRuntime | null } = { current: null };
|
|
71
|
+
const useThreadRuntime = () =>
|
|
72
|
+
useExternalStoreRuntime({
|
|
73
|
+
messages: EMPTY_MESSAGES,
|
|
74
|
+
onNew: async () => {},
|
|
75
|
+
} as never);
|
|
76
|
+
|
|
77
|
+
const App = () => {
|
|
78
|
+
const runtime = useRemoteThreadListRuntime({
|
|
79
|
+
adapter,
|
|
80
|
+
runtimeHook: useThreadRuntime,
|
|
81
|
+
});
|
|
82
|
+
runtimeRef.current = runtime;
|
|
83
|
+
return (
|
|
84
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
85
|
+
{null}
|
|
86
|
+
</AssistantRuntimeProvider>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
render(<App />);
|
|
91
|
+
await waitFor(() => expect(list).toHaveBeenCalledTimes(1));
|
|
92
|
+
await waitFor(() =>
|
|
93
|
+
expect(runtimeRef.current!.threads.getState().loadError).toBe(error),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
act(() => document.dispatchEvent(new Event("visibilitychange")));
|
|
97
|
+
|
|
98
|
+
await waitFor(() => expect(list).toHaveBeenCalledTimes(2));
|
|
99
|
+
await waitFor(() =>
|
|
100
|
+
expect(runtimeRef.current!.threads.getState().loadError).toBeUndefined(),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
act(() => document.dispatchEvent(new Event("visibilitychange")));
|
|
104
|
+
await act(async () => {});
|
|
105
|
+
|
|
106
|
+
expect(list).toHaveBeenCalledTimes(2);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useState,
|
|
3
3
|
useEffect,
|
|
4
|
+
useInsertionEffect,
|
|
4
5
|
useMemo,
|
|
5
6
|
useRef,
|
|
6
7
|
useCallback,
|
|
@@ -39,11 +40,43 @@ const useRemoteThreadListRuntimeImpl = (
|
|
|
39
40
|
options: RemoteThreadListOptions,
|
|
40
41
|
): AssistantRuntime => {
|
|
41
42
|
const [runtime] = useState(() => new RemoteThreadListRuntimeCore(options));
|
|
43
|
+
|
|
44
|
+
// Insertion-effect cleanup runs only when React deletes the fiber, so a
|
|
45
|
+
// hidden <Activity> or a re-suspended boundary keeps the threads alive; the
|
|
46
|
+
// disposal is deferred to a microtask because it notifies subscribers and
|
|
47
|
+
// React forbids scheduling updates from an insertion effect.
|
|
48
|
+
useInsertionEffect(
|
|
49
|
+
() => () => queueMicrotask(() => runtime.threads.__internal_dispose()),
|
|
50
|
+
[runtime],
|
|
51
|
+
);
|
|
52
|
+
|
|
42
53
|
useEffect(() => {
|
|
43
54
|
runtime.threads.__internal_setOptions(options);
|
|
44
55
|
runtime.threads.__internal_load();
|
|
45
56
|
}, [runtime, options]);
|
|
46
57
|
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const reloadAfterError = () => {
|
|
64
|
+
if (runtime.threads.loadError !== undefined) {
|
|
65
|
+
void runtime.threads.reload();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const reloadAfterVisible = () => {
|
|
69
|
+
if (document.visibilityState === "visible") reloadAfterError();
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
window.addEventListener("online", reloadAfterError);
|
|
73
|
+
document.addEventListener("visibilitychange", reloadAfterVisible);
|
|
74
|
+
return () => {
|
|
75
|
+
window.removeEventListener("online", reloadAfterError);
|
|
76
|
+
document.removeEventListener("visibilitychange", reloadAfterVisible);
|
|
77
|
+
};
|
|
78
|
+
}, [runtime]);
|
|
79
|
+
|
|
47
80
|
return useMemo(() => new AssistantRuntimeImpl(runtime), [runtime]);
|
|
48
81
|
};
|
|
49
82
|
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type MessageStateBinding,
|
|
12
12
|
} from "./message-runtime";
|
|
13
13
|
import { toMessagePartStatus } from "../../utils/normalizePartStatus";
|
|
14
|
+
import { convertExternalMessageChunk } from "../utils/external-message-conversion";
|
|
14
15
|
|
|
15
16
|
const messagePath = {
|
|
16
17
|
ref: "threads.main.messages[0]",
|
|
@@ -256,6 +257,80 @@ describe("toMessagePartStatus", () => {
|
|
|
256
257
|
});
|
|
257
258
|
|
|
258
259
|
describe("MessageRuntimeImpl paths", () => {
|
|
260
|
+
it.each([undefined, ""])(
|
|
261
|
+
"looks up separate tool calls with ID %s without breaking provider result matching",
|
|
262
|
+
(toolCallId) => {
|
|
263
|
+
const id = toolCallId === undefined ? {} : { toolCallId };
|
|
264
|
+
const converted = convertExternalMessageChunk(
|
|
265
|
+
{
|
|
266
|
+
inputs: [{}],
|
|
267
|
+
outputs: [
|
|
268
|
+
{
|
|
269
|
+
role: "assistant",
|
|
270
|
+
content: [
|
|
271
|
+
{ type: "tool-call", ...id, toolName: "weather", args: {} },
|
|
272
|
+
{ type: "tool-call", ...id, toolName: "search", args: {} },
|
|
273
|
+
{
|
|
274
|
+
type: "tool-call",
|
|
275
|
+
toolCallId: "provider-call",
|
|
276
|
+
toolName: "calendar",
|
|
277
|
+
args: { day: "Monday" },
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
role: "assistant",
|
|
283
|
+
content: [
|
|
284
|
+
{
|
|
285
|
+
type: "tool-call",
|
|
286
|
+
toolCallId: "provider-call",
|
|
287
|
+
toolName: "calendar",
|
|
288
|
+
args: { day: "Tuesday" },
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
role: "tool",
|
|
294
|
+
toolCallId: "provider-call",
|
|
295
|
+
toolName: "calendar",
|
|
296
|
+
result: "Meeting at noon",
|
|
297
|
+
},
|
|
298
|
+
],
|
|
299
|
+
},
|
|
300
|
+
0,
|
|
301
|
+
1,
|
|
302
|
+
false,
|
|
303
|
+
undefined,
|
|
304
|
+
);
|
|
305
|
+
const runtime = new MessageRuntimeImpl(
|
|
306
|
+
{
|
|
307
|
+
...messageBinding,
|
|
308
|
+
getState: () => ({ ...message, ...converted }),
|
|
309
|
+
},
|
|
310
|
+
threadBinding,
|
|
311
|
+
);
|
|
312
|
+
const calls = converted.content.filter(
|
|
313
|
+
(part) => part.type === "tool-call",
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
expect(
|
|
317
|
+
calls.map((call) =>
|
|
318
|
+
runtime.getMessagePartByToolCallId(call.toolCallId).getState(),
|
|
319
|
+
),
|
|
320
|
+
).toMatchObject([
|
|
321
|
+
{ type: "tool-call", toolName: "weather" },
|
|
322
|
+
{ type: "tool-call", toolName: "search" },
|
|
323
|
+
{
|
|
324
|
+
type: "tool-call",
|
|
325
|
+
toolCallId: "provider-call",
|
|
326
|
+
toolName: "calendar",
|
|
327
|
+
args: { day: "Tuesday" },
|
|
328
|
+
result: "Meeting at noon",
|
|
329
|
+
},
|
|
330
|
+
]);
|
|
331
|
+
},
|
|
332
|
+
);
|
|
333
|
+
|
|
259
334
|
it("appends nested selectors to the message path", () => {
|
|
260
335
|
const runtime = new MessageRuntimeImpl(messageBinding, threadBinding);
|
|
261
336
|
|
|
@@ -30,12 +30,17 @@ import type { ThreadListItemStatus } from "../interfaces/thread-list-runtime-cor
|
|
|
30
30
|
|
|
31
31
|
export type { ThreadListItemState, ThreadListItemStatus };
|
|
32
32
|
|
|
33
|
+
export type ThreadListItemGenerateTitleOptions = {
|
|
34
|
+
/** Marks a generation started by the automatic title trigger. */
|
|
35
|
+
automatic?: boolean;
|
|
36
|
+
};
|
|
37
|
+
|
|
33
38
|
export type ThreadListItemRuntime = {
|
|
34
39
|
readonly path: ThreadListItemRuntimePath;
|
|
35
40
|
getState(): ThreadListItemState;
|
|
36
41
|
|
|
37
42
|
initialize(): Promise<{ remoteId: string; externalId: string | undefined }>;
|
|
38
|
-
generateTitle(): Promise<void>;
|
|
43
|
+
generateTitle(options?: ThreadListItemGenerateTitleOptions): Promise<void>;
|
|
39
44
|
|
|
40
45
|
switchTo(options?: { unarchive?: boolean }): Promise<void>;
|
|
41
46
|
rename(newTitle: string): Promise<void>;
|
|
@@ -147,9 +152,11 @@ export class ThreadListItemRuntimeImpl implements ThreadListItemRuntime {
|
|
|
147
152
|
return this._threadListBinding.initialize(state.id);
|
|
148
153
|
}
|
|
149
154
|
|
|
150
|
-
public generateTitle(
|
|
155
|
+
public generateTitle(
|
|
156
|
+
options?: ThreadListItemGenerateTitleOptions,
|
|
157
|
+
): Promise<void> {
|
|
151
158
|
const state = this._core.getState();
|
|
152
|
-
return this._threadListBinding.generateTitle(state.id);
|
|
159
|
+
return this._threadListBinding.generateTitle(state.id, options);
|
|
153
160
|
}
|
|
154
161
|
|
|
155
162
|
public unstable_on<E extends ThreadListItemEventType>(
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type ThreadListItemRuntime,
|
|
16
16
|
ThreadListItemRuntimeImpl,
|
|
17
17
|
type ThreadListItemState,
|
|
18
|
+
type ThreadListItemStateBinding,
|
|
18
19
|
} from "./thread-list-item-runtime";
|
|
19
20
|
import {
|
|
20
21
|
type ThreadListItemRuntimeBinding,
|
|
@@ -32,6 +33,8 @@ export type ThreadListState = {
|
|
|
32
33
|
readonly threadIds: readonly string[];
|
|
33
34
|
readonly archivedThreadIds: readonly string[];
|
|
34
35
|
readonly isLoading: boolean;
|
|
36
|
+
/** The error thrown by the most recent thread list load that failed, cleared when a later load starts. */
|
|
37
|
+
readonly loadError: unknown;
|
|
35
38
|
readonly isLoadingMore: boolean;
|
|
36
39
|
readonly hasMore: boolean;
|
|
37
40
|
readonly threadItems: Readonly<
|
|
@@ -97,6 +100,7 @@ const getThreadListState = (
|
|
|
97
100
|
threadIds: threadList.threadIds,
|
|
98
101
|
archivedThreadIds: threadList.archivedThreadIds,
|
|
99
102
|
isLoading: threadList.isLoading,
|
|
103
|
+
loadError: threadList.loadError,
|
|
100
104
|
isLoadingMore: threadList.isLoadingMore ?? false,
|
|
101
105
|
hasMore: threadList.hasMore ?? false,
|
|
102
106
|
threadItems: threadList.threadItems,
|
|
@@ -250,6 +254,21 @@ export class ThreadListRuntimeImpl implements ThreadListRuntime {
|
|
|
250
254
|
return this._mainThreadListItemRuntime;
|
|
251
255
|
}
|
|
252
256
|
|
|
257
|
+
private _createItemStateBinding(
|
|
258
|
+
threadId: string,
|
|
259
|
+
): ThreadListItemStateBinding {
|
|
260
|
+
return new ShallowMemoizeSubject({
|
|
261
|
+
path: {
|
|
262
|
+
ref: `threadItems[threadId=${threadId}]`,
|
|
263
|
+
threadSelector: { type: "threadId", threadId },
|
|
264
|
+
},
|
|
265
|
+
getState: () => {
|
|
266
|
+
return getThreadListItemState(this._core, threadId);
|
|
267
|
+
},
|
|
268
|
+
subscribe: (callback) => this._core.subscribe(callback),
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
253
272
|
public getById(threadId: string) {
|
|
254
273
|
return new this._runtimeFactory(
|
|
255
274
|
new NestedSubscriptionSubject({
|
|
@@ -260,7 +279,7 @@ export class ThreadListRuntimeImpl implements ThreadListRuntime {
|
|
|
260
279
|
getState: () => this._core.getThreadRuntimeCore(threadId),
|
|
261
280
|
subscribe: (callback) => this._core.subscribe(callback),
|
|
262
281
|
}),
|
|
263
|
-
this.
|
|
282
|
+
this._createItemStateBinding(threadId),
|
|
264
283
|
);
|
|
265
284
|
}
|
|
266
285
|
|
|
@@ -301,16 +320,7 @@ export class ThreadListRuntimeImpl implements ThreadListRuntime {
|
|
|
301
320
|
|
|
302
321
|
public getItemById(threadId: string) {
|
|
303
322
|
return new ThreadListItemRuntimeImpl(
|
|
304
|
-
|
|
305
|
-
path: {
|
|
306
|
-
ref: `threadItems[threadId=${threadId}]`,
|
|
307
|
-
threadSelector: { type: "threadId", threadId },
|
|
308
|
-
},
|
|
309
|
-
getState: () => {
|
|
310
|
-
return getThreadListItemState(this._core, threadId);
|
|
311
|
-
},
|
|
312
|
-
subscribe: (callback) => this._core.subscribe(callback),
|
|
313
|
-
}),
|
|
323
|
+
this._createItemStateBinding(threadId),
|
|
314
324
|
this._core,
|
|
315
325
|
);
|
|
316
326
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { LocalRuntimeCore } from "../../runtimes/local/local-runtime-core";
|
|
3
|
+
import { ExternalStoreRuntimeCore } from "../../runtimes/external-store/external-store-runtime-core";
|
|
4
|
+
import { AssistantRuntimeImpl } from "./assistant-runtime";
|
|
5
|
+
|
|
6
|
+
describe("ThreadRuntime.append", () => {
|
|
7
|
+
it.each([
|
|
8
|
+
{ parent: { parentId: null }, expectedParent: null, visible: ["new"] },
|
|
9
|
+
{
|
|
10
|
+
parent: {},
|
|
11
|
+
expectedParent: "tail",
|
|
12
|
+
visible: ["root", "tail", "new"],
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
parent: { parentId: "root" },
|
|
16
|
+
expectedParent: "root",
|
|
17
|
+
visible: ["root", "new"],
|
|
18
|
+
},
|
|
19
|
+
])(
|
|
20
|
+
"selects the branch under $expectedParent",
|
|
21
|
+
({ parent, expectedParent, visible }) => {
|
|
22
|
+
const core = new LocalRuntimeCore(
|
|
23
|
+
{ adapters: { chatModel: { run: async () => ({ content: [] }) } } },
|
|
24
|
+
[
|
|
25
|
+
{ id: "root", role: "user", content: "root" },
|
|
26
|
+
{ id: "tail", role: "assistant", content: "tail" },
|
|
27
|
+
],
|
|
28
|
+
);
|
|
29
|
+
const thread = new AssistantRuntimeImpl(core).thread;
|
|
30
|
+
|
|
31
|
+
thread.append({
|
|
32
|
+
...parent,
|
|
33
|
+
content: [{ type: "text", text: "new" }],
|
|
34
|
+
startRun: false,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(
|
|
38
|
+
thread.getState().messages.map((message) => message.content),
|
|
39
|
+
).toEqual(visible.map((text) => [{ type: "text", text }]));
|
|
40
|
+
expect(thread.export().messages.at(-1)?.parentId).toBe(expectedParent);
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("ThreadRuntime.append with an external store", () => {
|
|
46
|
+
it("routes an explicit root parent to onEdit instead of onNew", async () => {
|
|
47
|
+
const onNew = vi.fn(async () => {});
|
|
48
|
+
const onEdit = vi.fn(async () => {});
|
|
49
|
+
const core = new ExternalStoreRuntimeCore({
|
|
50
|
+
messages: [
|
|
51
|
+
{
|
|
52
|
+
id: "old",
|
|
53
|
+
role: "user",
|
|
54
|
+
content: [{ type: "text", text: "old" }],
|
|
55
|
+
createdAt: new Date(0),
|
|
56
|
+
attachments: [],
|
|
57
|
+
metadata: { custom: {} },
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
onNew,
|
|
61
|
+
onEdit,
|
|
62
|
+
});
|
|
63
|
+
const thread = new AssistantRuntimeImpl(core).thread;
|
|
64
|
+
|
|
65
|
+
thread.append({
|
|
66
|
+
parentId: null,
|
|
67
|
+
content: [{ type: "text", text: "new root" }],
|
|
68
|
+
startRun: false,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
await vi.waitFor(() =>
|
|
72
|
+
expect(onEdit).toHaveBeenCalledExactlyOnceWith(
|
|
73
|
+
expect.objectContaining({ parentId: null }),
|
|
74
|
+
),
|
|
75
|
+
);
|
|
76
|
+
expect(onNew).not.toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -77,6 +77,10 @@ const toStartRunConfig = (message: CreateStartRunConfig): StartRunConfig => {
|
|
|
77
77
|
export type CreateAppendMessage =
|
|
78
78
|
| string
|
|
79
79
|
| {
|
|
80
|
+
/**
|
|
81
|
+
* An omitted value or `undefined` selects the current tail.
|
|
82
|
+
* `null` selects a root branch.
|
|
83
|
+
*/
|
|
80
84
|
parentId?: string | null | undefined;
|
|
81
85
|
sourceId?: string | null | undefined;
|
|
82
86
|
role?: AppendMessage["role"] | undefined;
|
|
@@ -107,7 +111,10 @@ const toAppendMessage = (
|
|
|
107
111
|
|
|
108
112
|
return {
|
|
109
113
|
createdAt: message.createdAt ?? new Date(),
|
|
110
|
-
parentId:
|
|
114
|
+
parentId:
|
|
115
|
+
message.parentId === undefined
|
|
116
|
+
? (messages.at(-1)?.id ?? null)
|
|
117
|
+
: message.parentId,
|
|
111
118
|
sourceId: message.sourceId ?? null,
|
|
112
119
|
role: message.role ?? "user",
|
|
113
120
|
content: message.content,
|