@assistant-ui/core 0.1.17 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/react/client/Tools.d.ts +6 -1
- package/dist/react/client/Tools.d.ts.map +1 -1
- package/dist/react/client/Tools.js +16 -19
- package/dist/react/client/Tools.js.map +1 -1
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useComposerSend.js +2 -3
- package/dist/react/primitive-hooks/useComposerSend.js.map +1 -1
- package/dist/react/primitive-hooks/useThreadListLoadMore.d.ts +5 -0
- package/dist/react/primitive-hooks/useThreadListLoadMore.d.ts.map +1 -0
- package/dist/react/primitive-hooks/useThreadListLoadMore.js +11 -0
- package/dist/react/primitive-hooks/useThreadListLoadMore.js.map +1 -0
- package/dist/react/primitives/message/MessageAttachments.js +1 -1
- package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
- package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageParts.js +14 -10
- package/dist/react/primitives/message/MessageParts.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +2 -2
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +4 -3
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +8 -4
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +86 -38
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/cloud/auiV0.d.ts +10 -1
- package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/auiV0.js +21 -3
- package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
- package/dist/react/runtimes/useLocalRuntime.d.ts +1 -1
- package/dist/react/runtimes/useToolInvocations.d.ts +2 -1
- package/dist/react/runtimes/useToolInvocations.d.ts.map +1 -1
- package/dist/react/runtimes/useToolInvocations.js +16 -1
- package/dist/react/runtimes/useToolInvocations.js.map +1 -1
- package/dist/react/types/scopes/tools.d.ts +4 -0
- package/dist/react/types/scopes/tools.d.ts.map +1 -1
- package/dist/runtime/api/assistant-runtime.d.ts +0 -33
- package/dist/runtime/api/assistant-runtime.d.ts.map +1 -1
- package/dist/runtime/api/assistant-runtime.js +0 -23
- package/dist/runtime/api/assistant-runtime.js.map +1 -1
- package/dist/runtime/api/bindings.d.ts +1 -3
- package/dist/runtime/api/bindings.d.ts.map +1 -1
- package/dist/runtime/api/composer-runtime.d.ts +1 -0
- package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
- package/dist/runtime/api/composer-runtime.js +2 -0
- package/dist/runtime/api/composer-runtime.js.map +1 -1
- package/dist/runtime/api/message-runtime.d.ts +1 -6
- package/dist/runtime/api/message-runtime.d.ts.map +1 -1
- package/dist/runtime/api/message-runtime.js.map +1 -1
- package/dist/runtime/api/thread-list-runtime.d.ts +4 -0
- package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-list-runtime.js +6 -0
- package/dist/runtime/api/thread-list-runtime.js.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts +3 -24
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-runtime.js +1 -20
- package/dist/runtime/api/thread-runtime.js.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -0
- package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js +1 -1
- package/dist/runtime/base/base-composer-runtime-core.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/default-edit-composer-runtime-core.d.ts +1 -0
- package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.js +3 -0
- 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 -0
- package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.js +12 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
- package/dist/runtime/interfaces/composer-runtime-core.d.ts +1 -0
- package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +3 -0
- package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts +6 -4
- package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/utils/chat-model-adapter.d.ts +0 -4
- package/dist/runtime/utils/chat-model-adapter.d.ts.map +1 -1
- package/dist/runtime/utils/external-store-message.d.ts +0 -4
- package/dist/runtime/utils/external-store-message.d.ts.map +1 -1
- package/dist/runtime/utils/external-store-message.js +0 -7
- package/dist/runtime/utils/external-store-message.js.map +1 -1
- package/dist/runtimes/assistant-transport/utils.d.ts +0 -9
- package/dist/runtimes/assistant-transport/utils.d.ts.map +1 -1
- package/dist/runtimes/assistant-transport/utils.js +0 -13
- package/dist/runtimes/assistant-transport/utils.js.map +1 -1
- package/dist/runtimes/external-store/external-store-adapter.d.ts +15 -0
- package/dist/runtimes/external-store/external-store-adapter.d.ts.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 +14 -12
- 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 +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 +2 -3
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +1 -4
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +2 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +2 -3
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.js +2 -3
- package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
- package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +12 -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 +34 -0
- package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
- package/dist/runtimes/remote-thread-list/types.d.ts +5 -1
- package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
- package/dist/store/clients/no-op-composer-client.d.ts.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/clients/thread-message-client.d.ts.map +1 -1
- package/dist/store/clients/thread-message-client.js +0 -1
- package/dist/store/clients/thread-message-client.js.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.js +1 -0
- package/dist/store/runtime-clients/composer-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 +3 -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 +0 -1
- package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
- package/dist/store/scopes/composer.d.ts +9 -0
- package/dist/store/scopes/composer.d.ts.map +1 -1
- package/dist/store/scopes/message.d.ts +1 -3
- package/dist/store/scopes/message.d.ts.map +1 -1
- package/dist/store/scopes/thread.d.ts +0 -4
- package/dist/store/scopes/thread.d.ts.map +1 -1
- package/dist/store/scopes/threads.d.ts +3 -0
- package/dist/store/scopes/threads.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/message.d.ts +28 -1
- package/dist/types/message.d.ts.map +1 -1
- package/dist/types/message.js +2 -1
- package/dist/types/message.js.map +1 -1
- package/package.json +11 -12
- package/src/index.ts +8 -6
- package/src/react/client/Tools.ts +46 -22
- package/src/react/index.ts +2 -1
- package/src/react/primitive-hooks/useComposerSend.ts +2 -3
- package/src/react/primitive-hooks/useThreadListLoadMore.ts +15 -0
- package/src/react/primitives/message/MessageAttachments.test.tsx +50 -0
- package/src/react/primitives/message/MessageAttachments.tsx +1 -1
- package/src/react/primitives/message/MessageParts.tsx +20 -9
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +7 -6
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +96 -43
- package/src/react/runtimes/cloud/auiV0.ts +37 -4
- package/src/react/runtimes/useToolInvocations.ts +21 -1
- package/src/react/types/scopes/tools.ts +5 -0
- package/src/runtime/api/assistant-runtime.ts +0 -62
- package/src/runtime/api/bindings.ts +1 -6
- package/src/runtime/api/composer-runtime.ts +3 -0
- package/src/runtime/api/message-runtime.ts +1 -8
- package/src/runtime/api/thread-list-runtime.ts +10 -0
- package/src/runtime/api/thread-runtime.ts +1 -46
- package/src/runtime/base/base-composer-runtime-core.ts +2 -1
- package/src/runtime/base/base-thread-runtime-core.ts +1 -1
- package/src/runtime/base/default-edit-composer-runtime-core.ts +4 -0
- package/src/runtime/base/default-thread-composer-runtime-core.ts +12 -1
- package/src/runtime/interfaces/composer-runtime-core.ts +1 -0
- package/src/runtime/interfaces/thread-list-runtime-core.ts +3 -0
- package/src/runtime/interfaces/thread-runtime-core.ts +6 -5
- package/src/runtime/utils/chat-model-adapter.ts +0 -5
- package/src/runtime/utils/external-store-message.ts +0 -8
- package/src/runtimes/assistant-transport/utils.ts +0 -28
- package/src/runtimes/external-store/external-store-adapter.ts +15 -0
- package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +15 -9
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +2 -4
- package/src/runtimes/local/local-thread-runtime-core.ts +1 -5
- package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +2 -4
- package/src/runtimes/remote-thread-list/empty-thread-core.ts +2 -4
- package/src/runtimes/remote-thread-list/remote-thread-state.ts +54 -1
- package/src/runtimes/remote-thread-list/types.ts +6 -1
- package/src/store/clients/no-op-composer-client.ts +1 -0
- package/src/store/clients/thread-message-client.ts +0 -1
- package/src/store/runtime-clients/composer-runtime-client.ts +1 -0
- package/src/store/runtime-clients/thread-list-runtime-client.ts +3 -0
- package/src/store/runtime-clients/thread-runtime-client.ts +0 -1
- package/src/store/scopes/composer.ts +9 -0
- package/src/store/scopes/message.ts +1 -6
- package/src/store/scopes/thread.ts +0 -5
- package/src/store/scopes/threads.ts +3 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-loadMore.test.ts +448 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-reload.test.ts +6 -1
- package/src/tests/auiV0Encode.test.ts +55 -0
- package/src/tests/composer-can-send.test.ts +112 -0
- package/src/tests/external-store-thread-list-runtime-core.test.ts +34 -0
- package/src/types/index.ts +2 -0
- package/src/types/message.ts +44 -7
|
@@ -26,6 +26,15 @@ export type ComposerState = {
|
|
|
26
26
|
readonly runConfig: RunConfig;
|
|
27
27
|
readonly isEditing: boolean;
|
|
28
28
|
readonly canCancel: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Whether the composer is currently willing to send. `true` when the
|
|
31
|
+
* composer is in editing mode and has non-empty content; for thread
|
|
32
|
+
* composers also requires the thread's `isSendDisabled` flag to be unset.
|
|
33
|
+
* Edit composers (saving message edits) ignore `isSendDisabled` since it
|
|
34
|
+
* is a thread-scoped gate. Cross-thread gating (running, queue capability)
|
|
35
|
+
* is layered on top by `useComposerSend`.
|
|
36
|
+
*/
|
|
37
|
+
readonly canSend: boolean;
|
|
29
38
|
readonly attachmentAccept: string;
|
|
30
39
|
readonly isEmpty: boolean;
|
|
31
40
|
readonly type: "thread" | "edit";
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import type { ThreadMessage } from "../../types/message";
|
|
2
2
|
import type { RunConfig } from "../../types/message";
|
|
3
|
-
import type {
|
|
4
|
-
SpeechState,
|
|
5
|
-
SubmittedFeedback,
|
|
6
|
-
} from "../../runtime/interfaces/thread-runtime-core";
|
|
3
|
+
import type { SpeechState } from "../../runtime/interfaces/thread-runtime-core";
|
|
7
4
|
import type { MessageRuntime } from "../../runtime/api/message-runtime";
|
|
8
5
|
import type { ComposerMethods, ComposerState } from "./composer";
|
|
9
6
|
import type { PartMethods, PartState } from "./part";
|
|
@@ -31,8 +28,6 @@ export type MessageState = ThreadMessage & {
|
|
|
31
28
|
* });
|
|
32
29
|
*/
|
|
33
30
|
readonly speech: SpeechState | undefined;
|
|
34
|
-
/** @deprecated Use `message.metadata.submittedFeedback` instead. This will be removed in 0.12.0. */
|
|
35
|
-
readonly submittedFeedback: SubmittedFeedback | undefined;
|
|
36
31
|
readonly composer: ComposerState;
|
|
37
32
|
readonly parts: readonly PartState[];
|
|
38
33
|
readonly isCopied: boolean;
|
|
@@ -98,11 +98,6 @@ export type ThreadMethods = {
|
|
|
98
98
|
* @param config The configuration for resuming the run
|
|
99
99
|
*/
|
|
100
100
|
resumeRun(config: CreateResumeRunConfig): void;
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* @deprecated Use `resumeRun` instead.
|
|
104
|
-
*/
|
|
105
|
-
unstable_resumeRun(config: CreateResumeRunConfig): void;
|
|
106
101
|
cancelRun(): void;
|
|
107
102
|
getModelContext(): ModelContext;
|
|
108
103
|
export(): ExportedMessageRepository;
|
|
@@ -9,6 +9,8 @@ export type ThreadsState = {
|
|
|
9
9
|
readonly mainThreadId: string;
|
|
10
10
|
readonly newThreadId: string | null;
|
|
11
11
|
readonly isLoading: boolean;
|
|
12
|
+
readonly isLoadingMore: boolean;
|
|
13
|
+
readonly hasMore: boolean;
|
|
12
14
|
readonly threadIds: readonly string[];
|
|
13
15
|
readonly archivedThreadIds: readonly string[];
|
|
14
16
|
readonly threadItems: readonly ThreadListItemState[];
|
|
@@ -28,6 +30,7 @@ export type ThreadsMethods = {
|
|
|
28
30
|
thread(selector: "main"): ThreadMethods;
|
|
29
31
|
getLoadThreadsPromise(): Promise<void>;
|
|
30
32
|
reload(): Promise<void>;
|
|
33
|
+
loadMore(): Promise<void>;
|
|
31
34
|
__internal_getAssistantRuntime?(): AssistantRuntime;
|
|
32
35
|
};
|
|
33
36
|
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import { afterEach, describe, it, expect, vi } from "vitest";
|
|
2
|
+
import type {
|
|
3
|
+
RemoteThreadListPageOptions,
|
|
4
|
+
RemoteThreadListResponse,
|
|
5
|
+
} from "../runtimes/remote-thread-list/types";
|
|
6
|
+
import {
|
|
7
|
+
createCore,
|
|
8
|
+
deferred,
|
|
9
|
+
makeAdapter,
|
|
10
|
+
} from "./remote-thread-list-test-helpers";
|
|
11
|
+
|
|
12
|
+
type ListFn = (
|
|
13
|
+
params?: RemoteThreadListPageOptions,
|
|
14
|
+
) => Promise<RemoteThreadListResponse>;
|
|
15
|
+
|
|
16
|
+
describe("RemoteThreadListThreadListRuntimeCore.loadMore", () => {
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
vi.restoreAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("initial list response with nextCursor sets hasMore=true", async () => {
|
|
22
|
+
const adapter = makeAdapter({
|
|
23
|
+
list: vi.fn(async () => ({
|
|
24
|
+
threads: [
|
|
25
|
+
{
|
|
26
|
+
status: "regular",
|
|
27
|
+
remoteId: "t-1",
|
|
28
|
+
externalId: "t-1",
|
|
29
|
+
title: "First",
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
nextCursor: "cursor-1",
|
|
33
|
+
})),
|
|
34
|
+
});
|
|
35
|
+
const core = createCore(adapter);
|
|
36
|
+
|
|
37
|
+
await core.getLoadThreadsPromise();
|
|
38
|
+
expect(core.threadIds).toEqual(["t-1"]);
|
|
39
|
+
expect(core.hasMore).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("absent nextCursor leaves hasMore=false", async () => {
|
|
43
|
+
const adapter = makeAdapter({
|
|
44
|
+
list: vi.fn(async () => ({
|
|
45
|
+
threads: [
|
|
46
|
+
{
|
|
47
|
+
status: "regular",
|
|
48
|
+
remoteId: "t-1",
|
|
49
|
+
externalId: "t-1",
|
|
50
|
+
title: "Only",
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
})),
|
|
54
|
+
});
|
|
55
|
+
const core = createCore(adapter);
|
|
56
|
+
|
|
57
|
+
await core.getLoadThreadsPromise();
|
|
58
|
+
expect(core.hasMore).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("loadMore appends the next page and advances the cursor", async () => {
|
|
62
|
+
const listFn = vi
|
|
63
|
+
.fn<ListFn>()
|
|
64
|
+
.mockResolvedValueOnce({
|
|
65
|
+
threads: [
|
|
66
|
+
{ status: "regular", remoteId: "p1-a", externalId: "p1-a" },
|
|
67
|
+
{ status: "regular", remoteId: "p1-b", externalId: "p1-b" },
|
|
68
|
+
],
|
|
69
|
+
nextCursor: "c1",
|
|
70
|
+
})
|
|
71
|
+
.mockResolvedValueOnce({
|
|
72
|
+
threads: [
|
|
73
|
+
{ status: "regular", remoteId: "p2-a", externalId: "p2-a" },
|
|
74
|
+
{ status: "regular", remoteId: "p2-b", externalId: "p2-b" },
|
|
75
|
+
],
|
|
76
|
+
nextCursor: "c2",
|
|
77
|
+
});
|
|
78
|
+
const adapter = makeAdapter({ list: listFn });
|
|
79
|
+
const core = createCore(adapter);
|
|
80
|
+
|
|
81
|
+
await core.getLoadThreadsPromise();
|
|
82
|
+
expect(core.threadIds).toEqual(["p1-a", "p1-b"]);
|
|
83
|
+
expect(core.hasMore).toBe(true);
|
|
84
|
+
|
|
85
|
+
await core.loadMore();
|
|
86
|
+
|
|
87
|
+
expect(listFn).toHaveBeenNthCalledWith(2, { after: "c1" });
|
|
88
|
+
expect(core.threadIds).toEqual(["p1-a", "p1-b", "p2-a", "p2-b"]);
|
|
89
|
+
expect(core.hasMore).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("loadMore without nextCursor flips hasMore to false", async () => {
|
|
93
|
+
const listFn = vi
|
|
94
|
+
.fn<ListFn>()
|
|
95
|
+
.mockResolvedValueOnce({
|
|
96
|
+
threads: [{ status: "regular", remoteId: "a", externalId: "a" }],
|
|
97
|
+
nextCursor: "c1",
|
|
98
|
+
})
|
|
99
|
+
.mockResolvedValueOnce({
|
|
100
|
+
threads: [{ status: "regular", remoteId: "b", externalId: "b" }],
|
|
101
|
+
});
|
|
102
|
+
const adapter = makeAdapter({ list: listFn });
|
|
103
|
+
const core = createCore(adapter);
|
|
104
|
+
|
|
105
|
+
await core.getLoadThreadsPromise();
|
|
106
|
+
await core.loadMore();
|
|
107
|
+
|
|
108
|
+
expect(core.hasMore).toBe(false);
|
|
109
|
+
expect(core.threadIds).toEqual(["a", "b"]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("loadMore is a no-op when hasMore is false", async () => {
|
|
113
|
+
const listFn = vi.fn<ListFn>().mockResolvedValueOnce({
|
|
114
|
+
threads: [{ status: "regular", remoteId: "a", externalId: "a" }],
|
|
115
|
+
});
|
|
116
|
+
const adapter = makeAdapter({ list: listFn });
|
|
117
|
+
const core = createCore(adapter);
|
|
118
|
+
|
|
119
|
+
await core.getLoadThreadsPromise();
|
|
120
|
+
expect(core.hasMore).toBe(false);
|
|
121
|
+
|
|
122
|
+
await core.loadMore();
|
|
123
|
+
expect(listFn).toHaveBeenCalledTimes(1);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("loadMore is a no-op while the initial list is in flight", async () => {
|
|
127
|
+
const first = deferred<RemoteThreadListResponse>();
|
|
128
|
+
const listFn = vi.fn<ListFn>().mockReturnValueOnce(first.promise);
|
|
129
|
+
const adapter = makeAdapter({ list: listFn });
|
|
130
|
+
const core = createCore(adapter);
|
|
131
|
+
|
|
132
|
+
core.getLoadThreadsPromise();
|
|
133
|
+
await core.loadMore();
|
|
134
|
+
|
|
135
|
+
expect(listFn).toHaveBeenCalledTimes(1);
|
|
136
|
+
first.resolve({ threads: [] });
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("concurrent loadMore calls dedupe to a single in-flight request", async () => {
|
|
140
|
+
const first = deferred<RemoteThreadListResponse>();
|
|
141
|
+
const listFn = vi
|
|
142
|
+
.fn<ListFn>()
|
|
143
|
+
.mockResolvedValueOnce({
|
|
144
|
+
threads: [{ status: "regular", remoteId: "a", externalId: "a" }],
|
|
145
|
+
nextCursor: "c1",
|
|
146
|
+
})
|
|
147
|
+
.mockReturnValueOnce(first.promise);
|
|
148
|
+
const adapter = makeAdapter({ list: listFn });
|
|
149
|
+
const core = createCore(adapter);
|
|
150
|
+
|
|
151
|
+
await core.getLoadThreadsPromise();
|
|
152
|
+
|
|
153
|
+
const p1 = core.loadMore();
|
|
154
|
+
const p2 = core.loadMore();
|
|
155
|
+
expect(listFn).toHaveBeenCalledTimes(2);
|
|
156
|
+
|
|
157
|
+
first.resolve({
|
|
158
|
+
threads: [{ status: "regular", remoteId: "b", externalId: "b" }],
|
|
159
|
+
});
|
|
160
|
+
await Promise.all([p1, p2]);
|
|
161
|
+
|
|
162
|
+
expect(listFn).toHaveBeenCalledTimes(2);
|
|
163
|
+
expect(core.threadIds).toEqual(["a", "b"]);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("drops a stale loadMore when reload bumps the generation mid-flight", async () => {
|
|
167
|
+
const loadMoreCall = deferred<RemoteThreadListResponse>();
|
|
168
|
+
const listFn = vi
|
|
169
|
+
.fn<ListFn>()
|
|
170
|
+
.mockResolvedValueOnce({
|
|
171
|
+
threads: [{ status: "regular", remoteId: "a", externalId: "a" }],
|
|
172
|
+
nextCursor: "c1",
|
|
173
|
+
})
|
|
174
|
+
.mockReturnValueOnce(loadMoreCall.promise)
|
|
175
|
+
.mockResolvedValueOnce({
|
|
176
|
+
threads: [
|
|
177
|
+
{ status: "regular", remoteId: "fresh", externalId: "fresh" },
|
|
178
|
+
],
|
|
179
|
+
});
|
|
180
|
+
const adapter = makeAdapter({ list: listFn });
|
|
181
|
+
const core = createCore(adapter);
|
|
182
|
+
|
|
183
|
+
await core.getLoadThreadsPromise();
|
|
184
|
+
|
|
185
|
+
const stale = core.loadMore();
|
|
186
|
+
const reloaded = core.reload();
|
|
187
|
+
|
|
188
|
+
loadMoreCall.resolve({
|
|
189
|
+
threads: [{ status: "regular", remoteId: "stale", externalId: "stale" }],
|
|
190
|
+
nextCursor: "stale-cursor",
|
|
191
|
+
});
|
|
192
|
+
await Promise.all([stale, reloaded]);
|
|
193
|
+
|
|
194
|
+
expect(core.threadIds).toEqual(["fresh"]);
|
|
195
|
+
expect(core.hasMore).toBe(false);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("releases the dedup handle after a rejection so retries can proceed", async () => {
|
|
199
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
200
|
+
const listFn = vi
|
|
201
|
+
.fn<ListFn>()
|
|
202
|
+
.mockResolvedValueOnce({
|
|
203
|
+
threads: [{ status: "regular", remoteId: "a", externalId: "a" }],
|
|
204
|
+
nextCursor: "c1",
|
|
205
|
+
})
|
|
206
|
+
.mockRejectedValueOnce(new Error("network"))
|
|
207
|
+
.mockResolvedValueOnce({
|
|
208
|
+
threads: [{ status: "regular", remoteId: "b", externalId: "b" }],
|
|
209
|
+
});
|
|
210
|
+
const adapter = makeAdapter({ list: listFn });
|
|
211
|
+
const core = createCore(adapter);
|
|
212
|
+
|
|
213
|
+
await core.getLoadThreadsPromise();
|
|
214
|
+
await core.loadMore();
|
|
215
|
+
expect(core.threadIds).toEqual(["a"]);
|
|
216
|
+
expect(core.isLoadingMore).toBe(false);
|
|
217
|
+
|
|
218
|
+
await core.loadMore();
|
|
219
|
+
expect(core.threadIds).toEqual(["a", "b"]);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("dedupes thread ids that appear on more than one page", async () => {
|
|
223
|
+
const listFn = vi
|
|
224
|
+
.fn<ListFn>()
|
|
225
|
+
.mockResolvedValueOnce({
|
|
226
|
+
threads: [{ status: "regular", remoteId: "a", externalId: "a" }],
|
|
227
|
+
nextCursor: "c1",
|
|
228
|
+
})
|
|
229
|
+
.mockResolvedValueOnce({
|
|
230
|
+
threads: [
|
|
231
|
+
{ status: "regular", remoteId: "a", externalId: "a" },
|
|
232
|
+
{ status: "regular", remoteId: "b", externalId: "b" },
|
|
233
|
+
],
|
|
234
|
+
});
|
|
235
|
+
const adapter = makeAdapter({ list: listFn });
|
|
236
|
+
const core = createCore(adapter);
|
|
237
|
+
|
|
238
|
+
await core.getLoadThreadsPromise();
|
|
239
|
+
await core.loadMore();
|
|
240
|
+
|
|
241
|
+
expect(core.threadIds).toEqual(["a", "b"]);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("__internal_setOptions clears cursor and dedup handles on adapter swap, then refetches via the new adapter", async () => {
|
|
245
|
+
const firstAdapter = makeAdapter({
|
|
246
|
+
list: vi.fn(async () => ({
|
|
247
|
+
threads: [{ status: "regular", remoteId: "old", externalId: "old" }],
|
|
248
|
+
nextCursor: "old-cursor",
|
|
249
|
+
})),
|
|
250
|
+
});
|
|
251
|
+
const core = createCore(firstAdapter);
|
|
252
|
+
await core.getLoadThreadsPromise();
|
|
253
|
+
expect(core.threadIds).toEqual(["old"]);
|
|
254
|
+
expect(core.hasMore).toBe(true);
|
|
255
|
+
|
|
256
|
+
const secondList = vi.fn(async () => ({
|
|
257
|
+
threads: [{ status: "regular", remoteId: "new", externalId: "new" }],
|
|
258
|
+
}));
|
|
259
|
+
const secondAdapter = makeAdapter({ list: secondList });
|
|
260
|
+
core.__internal_setOptions({
|
|
261
|
+
adapter: secondAdapter,
|
|
262
|
+
runtimeHook: () => ({}) as never,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
expect(core.hasMore).toBe(false);
|
|
266
|
+
expect(core.threadIds).toEqual(["old"]);
|
|
267
|
+
|
|
268
|
+
await core.getLoadThreadsPromise();
|
|
269
|
+
expect(secondList).toHaveBeenCalledTimes(1);
|
|
270
|
+
expect(core.threadIds).toEqual(["new"]);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("ignores an in-flight loadMore response when the adapter swaps mid-flight", async () => {
|
|
274
|
+
const slow = deferred<RemoteThreadListResponse>();
|
|
275
|
+
const firstList = vi
|
|
276
|
+
.fn<
|
|
277
|
+
(
|
|
278
|
+
params?: RemoteThreadListPageOptions,
|
|
279
|
+
) => Promise<RemoteThreadListResponse>
|
|
280
|
+
>()
|
|
281
|
+
.mockResolvedValueOnce({
|
|
282
|
+
threads: [{ status: "regular", remoteId: "p1", externalId: "p1" }],
|
|
283
|
+
nextCursor: "c1",
|
|
284
|
+
})
|
|
285
|
+
.mockReturnValueOnce(slow.promise);
|
|
286
|
+
const firstAdapter = makeAdapter({ list: firstList });
|
|
287
|
+
const core = createCore(firstAdapter);
|
|
288
|
+
|
|
289
|
+
await core.getLoadThreadsPromise();
|
|
290
|
+
const stale = core.loadMore();
|
|
291
|
+
|
|
292
|
+
const secondAdapter = makeAdapter({
|
|
293
|
+
list: vi.fn(async () => ({
|
|
294
|
+
threads: [{ status: "regular", remoteId: "ignored", externalId: "x" }],
|
|
295
|
+
})),
|
|
296
|
+
});
|
|
297
|
+
core.__internal_setOptions({
|
|
298
|
+
adapter: secondAdapter,
|
|
299
|
+
runtimeHook: () => ({}) as never,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
slow.resolve({
|
|
303
|
+
threads: [{ status: "regular", remoteId: "stale", externalId: "stale" }],
|
|
304
|
+
nextCursor: "stale-cursor",
|
|
305
|
+
});
|
|
306
|
+
await stale;
|
|
307
|
+
|
|
308
|
+
expect(core.threadIds).toEqual(["p1"]);
|
|
309
|
+
expect(core.hasMore).toBe(false);
|
|
310
|
+
expect(core.isLoadingMore).toBe(false);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("drops the in-flight initial list when the adapter swaps mid-flight", async () => {
|
|
314
|
+
const slow = deferred<RemoteThreadListResponse>();
|
|
315
|
+
const firstList = vi.fn<ListFn>().mockReturnValueOnce(slow.promise);
|
|
316
|
+
const firstAdapter = makeAdapter({ list: firstList });
|
|
317
|
+
const core = createCore(firstAdapter);
|
|
318
|
+
|
|
319
|
+
core.getLoadThreadsPromise();
|
|
320
|
+
|
|
321
|
+
const secondList = vi.fn<ListFn>().mockResolvedValueOnce({
|
|
322
|
+
threads: [{ status: "regular", remoteId: "fresh", externalId: "fresh" }],
|
|
323
|
+
});
|
|
324
|
+
const secondAdapter = makeAdapter({ list: secondList });
|
|
325
|
+
core.__internal_setOptions({
|
|
326
|
+
adapter: secondAdapter,
|
|
327
|
+
runtimeHook: () => ({}) as never,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
slow.resolve({
|
|
331
|
+
threads: [{ status: "regular", remoteId: "stale", externalId: "stale" }],
|
|
332
|
+
nextCursor: "stale-cursor",
|
|
333
|
+
});
|
|
334
|
+
await Promise.resolve();
|
|
335
|
+
await Promise.resolve();
|
|
336
|
+
|
|
337
|
+
expect(core.threadIds).toEqual([]);
|
|
338
|
+
expect(core.hasMore).toBe(false);
|
|
339
|
+
|
|
340
|
+
await core.getLoadThreadsPromise();
|
|
341
|
+
expect(secondList).toHaveBeenCalledTimes(1);
|
|
342
|
+
expect(core.threadIds).toEqual(["fresh"]);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it("dedupes thread ids that appear twice within a single page", async () => {
|
|
346
|
+
const listFn = vi
|
|
347
|
+
.fn<ListFn>()
|
|
348
|
+
.mockResolvedValueOnce({
|
|
349
|
+
threads: [{ status: "regular", remoteId: "a", externalId: "a" }],
|
|
350
|
+
nextCursor: "c1",
|
|
351
|
+
})
|
|
352
|
+
.mockResolvedValueOnce({
|
|
353
|
+
threads: [
|
|
354
|
+
{ status: "regular", remoteId: "b", externalId: "b" },
|
|
355
|
+
{ status: "regular", remoteId: "b", externalId: "b" },
|
|
356
|
+
],
|
|
357
|
+
});
|
|
358
|
+
const adapter = makeAdapter({ list: listFn });
|
|
359
|
+
const core = createCore(adapter);
|
|
360
|
+
|
|
361
|
+
await core.getLoadThreadsPromise();
|
|
362
|
+
await core.loadMore();
|
|
363
|
+
|
|
364
|
+
expect(core.threadIds).toEqual(["a", "b"]);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it("treats an empty-string nextCursor as no more pages", async () => {
|
|
368
|
+
const listFn = vi.fn<ListFn>().mockResolvedValueOnce({
|
|
369
|
+
threads: [{ status: "regular", remoteId: "a", externalId: "a" }],
|
|
370
|
+
nextCursor: "",
|
|
371
|
+
});
|
|
372
|
+
const adapter = makeAdapter({ list: listFn });
|
|
373
|
+
const core = createCore(adapter);
|
|
374
|
+
|
|
375
|
+
await core.getLoadThreadsPromise();
|
|
376
|
+
expect(core.hasMore).toBe(false);
|
|
377
|
+
|
|
378
|
+
await core.loadMore();
|
|
379
|
+
expect(listFn).toHaveBeenCalledTimes(1);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it("does not advance the cursor when the reducer rejects an unknown thread status", async () => {
|
|
383
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
384
|
+
const listFn = vi
|
|
385
|
+
.fn<ListFn>()
|
|
386
|
+
.mockResolvedValueOnce({
|
|
387
|
+
threads: [{ status: "regular", remoteId: "a", externalId: "a" }],
|
|
388
|
+
nextCursor: "c1",
|
|
389
|
+
})
|
|
390
|
+
.mockResolvedValueOnce({
|
|
391
|
+
threads: [
|
|
392
|
+
{
|
|
393
|
+
status: "weird" as unknown as "regular",
|
|
394
|
+
remoteId: "bad",
|
|
395
|
+
externalId: "bad",
|
|
396
|
+
},
|
|
397
|
+
],
|
|
398
|
+
nextCursor: "c2",
|
|
399
|
+
})
|
|
400
|
+
.mockResolvedValueOnce({
|
|
401
|
+
threads: [
|
|
402
|
+
{ status: "regular", remoteId: "retry", externalId: "retry" },
|
|
403
|
+
],
|
|
404
|
+
});
|
|
405
|
+
const adapter = makeAdapter({ list: listFn });
|
|
406
|
+
const core = createCore(adapter);
|
|
407
|
+
|
|
408
|
+
await core.getLoadThreadsPromise();
|
|
409
|
+
expect(core.hasMore).toBe(true);
|
|
410
|
+
|
|
411
|
+
await core.loadMore();
|
|
412
|
+
|
|
413
|
+
expect(core.threadIds).toEqual(["a"]);
|
|
414
|
+
expect(core.hasMore).toBe(true);
|
|
415
|
+
expect(core.isLoadingMore).toBe(false);
|
|
416
|
+
|
|
417
|
+
await core.loadMore();
|
|
418
|
+
expect(listFn).toHaveBeenNthCalledWith(2, { after: "c1" });
|
|
419
|
+
expect(listFn).toHaveBeenNthCalledWith(3, { after: "c1" });
|
|
420
|
+
expect(core.threadIds).toEqual(["a", "retry"]);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it("reload resets cursor and hasMore so loadMore becomes a no-op", async () => {
|
|
424
|
+
const listFn = vi
|
|
425
|
+
.fn<ListFn>()
|
|
426
|
+
.mockResolvedValueOnce({
|
|
427
|
+
threads: [{ status: "regular", remoteId: "a", externalId: "a" }],
|
|
428
|
+
nextCursor: "c1",
|
|
429
|
+
})
|
|
430
|
+
.mockResolvedValueOnce({
|
|
431
|
+
threads: [
|
|
432
|
+
{ status: "regular", remoteId: "fresh", externalId: "fresh" },
|
|
433
|
+
],
|
|
434
|
+
});
|
|
435
|
+
const adapter = makeAdapter({ list: listFn });
|
|
436
|
+
const core = createCore(adapter);
|
|
437
|
+
|
|
438
|
+
await core.getLoadThreadsPromise();
|
|
439
|
+
expect(core.hasMore).toBe(true);
|
|
440
|
+
|
|
441
|
+
await core.reload();
|
|
442
|
+
expect(core.hasMore).toBe(false);
|
|
443
|
+
expect(core.threadIds).toEqual(["fresh"]);
|
|
444
|
+
|
|
445
|
+
await core.loadMore();
|
|
446
|
+
expect(listFn).toHaveBeenCalledTimes(2);
|
|
447
|
+
});
|
|
448
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
1
|
+
import { afterEach, describe, it, expect, vi } from "vitest";
|
|
2
2
|
import type { RemoteThreadListResponse } from "../runtimes/remote-thread-list/types";
|
|
3
3
|
import {
|
|
4
4
|
createCore,
|
|
@@ -7,6 +7,10 @@ import {
|
|
|
7
7
|
} from "./remote-thread-list-test-helpers";
|
|
8
8
|
|
|
9
9
|
describe("RemoteThreadListThreadListRuntimeCore.reload", () => {
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
vi.restoreAllMocks();
|
|
12
|
+
});
|
|
13
|
+
|
|
10
14
|
it("refetches list() after a successful empty load", async () => {
|
|
11
15
|
const listFn = vi
|
|
12
16
|
.fn<() => Promise<RemoteThreadListResponse>>()
|
|
@@ -92,6 +96,7 @@ describe("RemoteThreadListThreadListRuntimeCore.reload", () => {
|
|
|
92
96
|
});
|
|
93
97
|
|
|
94
98
|
it("recovers after a failed initial load", async () => {
|
|
99
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
95
100
|
const listFn = vi
|
|
96
101
|
.fn<() => Promise<RemoteThreadListResponse>>()
|
|
97
102
|
.mockRejectedValueOnce(new Error("401"))
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { auiV0Encode } from "../react/runtimes/cloud/auiV0";
|
|
3
|
+
|
|
4
|
+
describe("auiV0Encode", () => {
|
|
5
|
+
it("preserves document source parts in the core cloud encoder", () => {
|
|
6
|
+
const encoded = auiV0Encode({
|
|
7
|
+
id: "m1",
|
|
8
|
+
createdAt: new Date("2026-03-15T00:00:00.000Z"),
|
|
9
|
+
role: "assistant",
|
|
10
|
+
status: { type: "complete", reason: "stop" },
|
|
11
|
+
metadata: {
|
|
12
|
+
unstable_state: undefined,
|
|
13
|
+
unstable_annotations: [],
|
|
14
|
+
unstable_data: [],
|
|
15
|
+
steps: [],
|
|
16
|
+
custom: {},
|
|
17
|
+
},
|
|
18
|
+
content: [
|
|
19
|
+
{
|
|
20
|
+
type: "source",
|
|
21
|
+
sourceType: "document",
|
|
22
|
+
id: "doc_123",
|
|
23
|
+
title: "proposal.pdf",
|
|
24
|
+
mediaType: "application/pdf",
|
|
25
|
+
filename: "proposal.pdf",
|
|
26
|
+
providerMetadata: {
|
|
27
|
+
openai: {
|
|
28
|
+
type: "file_citation",
|
|
29
|
+
fileId: "file_123",
|
|
30
|
+
index: 0,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(encoded.content).toEqual([
|
|
38
|
+
{
|
|
39
|
+
type: "source",
|
|
40
|
+
sourceType: "document",
|
|
41
|
+
id: "doc_123",
|
|
42
|
+
title: "proposal.pdf",
|
|
43
|
+
mediaType: "application/pdf",
|
|
44
|
+
filename: "proposal.pdf",
|
|
45
|
+
providerMetadata: {
|
|
46
|
+
openai: {
|
|
47
|
+
type: "file_citation",
|
|
48
|
+
fileId: "file_123",
|
|
49
|
+
index: 0,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
});
|