@assistant-ui/core 0.3.5 → 0.3.7
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/index.d.ts +4 -4
- package/dist/index.js.map +1 -1
- package/dist/internal.js +1 -1
- package/dist/react/AssistantProvider.d.ts +3 -2
- package/dist/react/AssistantProvider.d.ts.map +1 -1
- package/dist/react/AssistantProvider.js +19 -26
- package/dist/react/AssistantProvider.js.map +1 -1
- package/dist/react/AssistantRuntimeProvider.d.ts +7 -2
- package/dist/react/AssistantRuntimeProvider.d.ts.map +1 -1
- package/dist/react/AssistantRuntimeProvider.js +9 -7
- package/dist/react/AssistantRuntimeProvider.js.map +1 -1
- package/dist/react/RuntimeAdapter.js +1 -1
- package/dist/react/RuntimeAdapter.js.map +1 -1
- package/dist/react/client/Interactables.js +1 -1
- package/dist/react/client/Interactables.js.map +1 -1
- package/dist/react/client/Tools.d.ts +1 -1
- package/dist/react/client/Tools.js +1 -1
- package/dist/react/client/Tools.js.map +1 -1
- package/dist/react/index.d.ts +9 -9
- package/dist/react/index.js +4 -4
- package/dist/react/model-context/interactableTool.d.ts +1 -1
- package/dist/react/model-context/useAssistantTool.d.ts +1 -1
- package/dist/react/providers/AttachmentByIndexProvider.d.ts.map +1 -1
- package/dist/react/providers/AttachmentByIndexProvider.js +33 -39
- package/dist/react/providers/AttachmentByIndexProvider.js.map +1 -1
- package/dist/react/providers/ChainOfThoughtByIndicesProvider.d.ts.map +1 -1
- package/dist/react/providers/ChainOfThoughtByIndicesProvider.js +15 -20
- package/dist/react/providers/ChainOfThoughtByIndicesProvider.js.map +1 -1
- package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js +17 -20
- package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js.map +1 -1
- package/dist/react/providers/MessageByIdProvider.js +28 -38
- package/dist/react/providers/MessageByIdProvider.js.map +1 -1
- package/dist/react/providers/MessageByIndexProvider.js +28 -38
- package/dist/react/providers/MessageByIndexProvider.js.map +1 -1
- package/dist/react/providers/PartByIndexProvider.js +17 -20
- package/dist/react/providers/PartByIndexProvider.js.map +1 -1
- package/dist/react/providers/QueueItemByIndexProvider.js +21 -21
- package/dist/react/providers/QueueItemByIndexProvider.js.map +1 -1
- package/dist/react/providers/ReadonlyThreadProvider.js +24 -32
- package/dist/react/providers/ReadonlyThreadProvider.js.map +1 -1
- package/dist/react/providers/SuggestionByIndexProvider.js +17 -20
- package/dist/react/providers/SuggestionByIndexProvider.js.map +1 -1
- package/dist/react/providers/TextMessagePartProvider.d.ts.map +1 -1
- package/dist/react/providers/TextMessagePartProvider.js +16 -19
- package/dist/react/providers/TextMessagePartProvider.js.map +1 -1
- package/dist/react/providers/ThreadListItemByIndexProvider.js +17 -20
- package/dist/react/providers/ThreadListItemByIndexProvider.js.map +1 -1
- package/dist/react/providers/ThreadListItemRuntimeProvider.js +15 -18
- package/dist/react/providers/ThreadListItemRuntimeProvider.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +7 -3
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +5 -2
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +3 -2
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/createMessageConverter.d.ts +1 -1
- package/dist/react/runtimes/createRuntimeExtras.d.ts +4 -1
- package/dist/react/runtimes/createRuntimeExtras.d.ts.map +1 -1
- package/dist/react/runtimes/createRuntimeExtras.js +6 -17
- package/dist/react/runtimes/createRuntimeExtras.js.map +1 -1
- package/dist/react/runtimes/useLocalRuntime.d.ts +3 -1
- package/dist/react/runtimes/useLocalRuntime.d.ts.map +1 -1
- package/dist/react/runtimes/useLocalRuntime.js +4 -2
- package/dist/react/runtimes/useLocalRuntime.js.map +1 -1
- package/dist/react/types/scopes/dataRenderers.d.ts +1 -1
- package/dist/react/types/scopes/interactables.d.ts +1 -1
- package/dist/react/types/scopes/tools.d.ts +1 -1
- package/dist/runtime/api/composer-runtime.d.ts +5 -1
- package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
- package/dist/runtime/api/composer-runtime.js +8 -1
- package/dist/runtime/api/composer-runtime.js.map +1 -1
- package/dist/runtime/api/message-runtime.js +1 -1
- package/dist/runtime/api/message-runtime.js.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts +4 -2
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts +2 -1
- 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/default-thread-composer-runtime-core.d.ts +3 -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 +14 -3
- package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
- package/dist/runtime/interfaces/composer-runtime-core.d.ts +2 -1
- package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts +3 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/queue/external-thread-queue-adapter.d.ts +19 -6
- package/dist/runtime/queue/external-thread-queue-adapter.d.ts.map +1 -1
- package/dist/runtime/queue/message-queue.d.ts +10 -1
- package/dist/runtime/queue/message-queue.d.ts.map +1 -1
- package/dist/runtime/queue/message-queue.js +138 -36
- package/dist/runtime/queue/message-queue.js.map +1 -1
- package/dist/runtime/utils/runtime-extras-brand.d.ts +21 -0
- package/dist/runtime/utils/runtime-extras-brand.d.ts.map +1 -0
- package/dist/runtime/utils/runtime-extras-brand.js +37 -0
- package/dist/runtime/utils/runtime-extras-brand.js.map +1 -0
- package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
- package/dist/runtime/utils/thread-message-like.js +2 -1
- package/dist/runtime/utils/thread-message-like.js.map +1 -1
- package/dist/runtimes/external-store/external-store-adapter.d.ts +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +3 -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 +7 -6
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/local/local-runtime-options.d.ts +13 -0
- package/dist/runtimes/local/local-runtime-options.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts +5 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +30 -14
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.js +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
- package/dist/runtimes/remote-thread-list/types.d.ts +6 -3
- package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
- package/dist/store/clients/runtime-adapter.d.ts.map +1 -1
- package/dist/store/clients/runtime-adapter.js +1 -1
- package/dist/store/clients/runtime-adapter.js.map +1 -1
- package/dist/store/clients/suggestions.js +1 -1
- package/dist/store/clients/suggestions.js.map +1 -1
- package/dist/store/clients/thread-message-client.js +1 -1
- package/dist/store/clients/thread-message-client.js.map +1 -1
- package/dist/store/index.d.ts +10 -1
- package/dist/store/index.js +7 -1
- package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.js +12 -5
- package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/message-runtime-client.js +1 -1
- 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 +1 -1
- package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.js +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
- package/dist/store/scopes/composer.d.ts +7 -3
- package/dist/store/scopes/composer.d.ts.map +1 -1
- package/dist/store/scopes/queue-item.d.ts +10 -0
- package/dist/store/scopes/queue-item.d.ts.map +1 -1
- package/dist/store/scopes/queue-item.js.map +1 -1
- package/package.json +15 -15
- package/src/index.ts +5 -1
- package/src/react/AssistantProvider.tsx +20 -18
- package/src/react/AssistantRuntimeProvider.test.tsx +55 -1
- package/src/react/AssistantRuntimeProvider.tsx +12 -2
- package/src/react/RuntimeAdapter.test.ts +85 -0
- package/src/react/RuntimeAdapter.ts +1 -1
- package/src/react/client/Interactables.test.ts +3 -2
- package/src/react/client/Interactables.ts +4 -4
- package/src/react/client/Tools.ts +4 -4
- package/src/react/index.ts +1 -0
- package/src/react/providers/AttachmentByIndexProvider.tsx +15 -7
- package/src/react/providers/ChainOfThoughtByIndicesProvider.tsx +12 -4
- package/src/react/providers/ChainOfThoughtPartByIndexProvider.tsx +8 -4
- package/src/react/providers/MessageByIdProvider.tsx +8 -4
- package/src/react/providers/MessageByIndexProvider.tsx +8 -4
- package/src/react/providers/PartByIndexProvider.tsx +8 -4
- package/src/react/providers/QueueItemByIndexProvider.tsx +9 -5
- package/src/react/providers/ReadonlyThreadProvider.tsx +9 -4
- package/src/react/providers/SuggestionByIndexProvider.tsx +8 -4
- package/src/react/providers/TextMessagePartProvider.tsx +13 -4
- package/src/react/providers/ThreadListItemByIndexProvider.tsx +8 -4
- package/src/react/providers/ThreadListItemRuntimeProvider.tsx +8 -4
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +19 -4
- package/src/react/runtimes/createRuntimeExtras.test.ts +14 -1
- package/src/react/runtimes/createRuntimeExtras.ts +15 -26
- package/src/react/runtimes/useLocalRuntime.ts +4 -0
- package/src/runtime/api/composer-runtime.ts +11 -2
- package/src/runtime/api/message-runtime.test.ts +25 -0
- package/src/runtime/api/message-runtime.ts +1 -1
- package/src/runtime/base/base-composer-runtime-core.ts +5 -1
- package/src/runtime/base/default-thread-composer-runtime-core.ts +27 -3
- package/src/runtime/interfaces/composer-runtime-core.ts +2 -1
- package/src/runtime/interfaces/thread-runtime-core.ts +3 -1
- package/src/runtime/queue/external-thread-queue-adapter.ts +19 -3
- package/src/runtime/queue/message-queue.ts +200 -38
- package/src/runtime/utils/runtime-extras-brand.ts +47 -0
- package/src/runtime/utils/thread-message-like.ts +2 -4
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +10 -8
- package/src/runtimes/local/local-runtime-options.ts +15 -0
- package/src/runtimes/local/local-thread-runtime-core.test.ts +43 -0
- package/src/runtimes/local/local-thread-runtime-core.ts +48 -13
- package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +1 -1
- package/src/runtimes/remote-thread-list/empty-thread-core.ts +1 -1
- package/src/runtimes/remote-thread-list/types.ts +9 -2
- package/src/store/clients/runtime-adapter.ts +2 -5
- package/src/store/clients/suggestions.ts +1 -1
- package/src/store/clients/thread-message-client.ts +1 -1
- package/src/store/index.ts +31 -0
- package/src/store/runtime-clients/composer-runtime-client.ts +15 -10
- package/src/store/runtime-clients/message-runtime-client.ts +2 -5
- package/src/store/runtime-clients/thread-list-item-runtime-client.ts +2 -1
- package/src/store/runtime-clients/thread-list-runtime-client.ts +2 -5
- package/src/store/runtime-clients/thread-runtime-client.ts +2 -2
- package/src/store/scopes/composer.ts +6 -4
- package/src/store/scopes/queue-item.ts +8 -1
- package/src/tests/external-store-thread-runtime-core.test.ts +56 -13
- package/src/tests/framework-neutral-boundary.test.ts +257 -0
- package/src/tests/message-queue.test.ts +494 -34
- package/src/tests/thread-message-like.test.ts +66 -0
- package/LICENSE +0 -21
|
@@ -14,18 +14,21 @@ const msg = (text: string, extra?: Partial<AppendMessage>): AppendMessage => ({
|
|
|
14
14
|
...extra,
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
+
const prompts = (items: readonly { prompt: string }[]) =>
|
|
18
|
+
items.map((i) => i.prompt);
|
|
19
|
+
|
|
17
20
|
describe("createMessageQueue", () => {
|
|
18
21
|
it("runs immediately when idle and holds while running", () => {
|
|
19
22
|
const run = vi.fn();
|
|
20
23
|
const { adapter, notifyIdle } = createMessageQueue({ run });
|
|
21
24
|
|
|
22
|
-
adapter.enqueue(msg("first")
|
|
25
|
+
adapter.enqueue(msg("first"));
|
|
23
26
|
expect(run).toHaveBeenCalledTimes(1);
|
|
24
27
|
expect(adapter.items).toHaveLength(0);
|
|
25
28
|
|
|
26
|
-
adapter.enqueue(msg("second")
|
|
29
|
+
adapter.enqueue(msg("second"));
|
|
27
30
|
expect(run).toHaveBeenCalledTimes(1);
|
|
28
|
-
expect(adapter.items
|
|
31
|
+
expect(prompts(adapter.items)).toEqual(["second"]);
|
|
29
32
|
|
|
30
33
|
notifyIdle();
|
|
31
34
|
expect(run).toHaveBeenCalledTimes(2);
|
|
@@ -39,10 +42,10 @@ describe("createMessageQueue", () => {
|
|
|
39
42
|
);
|
|
40
43
|
const { adapter, notifyIdle } = createMessageQueue({ run });
|
|
41
44
|
|
|
42
|
-
adapter.enqueue(msg("a")
|
|
43
|
-
adapter.enqueue(msg("b")
|
|
44
|
-
adapter.enqueue(msg("c")
|
|
45
|
-
expect(adapter.items
|
|
45
|
+
adapter.enqueue(msg("a")); // runs now
|
|
46
|
+
adapter.enqueue(msg("b"));
|
|
47
|
+
adapter.enqueue(msg("c"));
|
|
48
|
+
expect(prompts(adapter.items)).toEqual(["b", "c"]);
|
|
46
49
|
|
|
47
50
|
notifyIdle();
|
|
48
51
|
notifyIdle();
|
|
@@ -55,10 +58,8 @@ describe("createMessageQueue", () => {
|
|
|
55
58
|
const attachments = [{ id: "x" }] as never;
|
|
56
59
|
const runConfig = { custom: { k: 1 } };
|
|
57
60
|
|
|
58
|
-
adapter.enqueue(msg("busy")
|
|
59
|
-
adapter.enqueue(msg("queued", { attachments, runConfig })
|
|
60
|
-
steer: false,
|
|
61
|
-
});
|
|
61
|
+
adapter.enqueue(msg("busy"));
|
|
62
|
+
adapter.enqueue(msg("queued", { attachments, runConfig }));
|
|
62
63
|
notifyIdle();
|
|
63
64
|
|
|
64
65
|
expect(run).toHaveBeenLastCalledWith(
|
|
@@ -67,16 +68,101 @@ describe("createMessageQueue", () => {
|
|
|
67
68
|
);
|
|
68
69
|
});
|
|
69
70
|
|
|
71
|
+
it("projects text parts onto queue items", () => {
|
|
72
|
+
const run = vi.fn();
|
|
73
|
+
const { adapter, notifyBusy } = createMessageQueue({ run });
|
|
74
|
+
notifyBusy();
|
|
75
|
+
|
|
76
|
+
adapter.enqueue(msg("hello"));
|
|
77
|
+
expect(adapter.items[0]!.parts).toEqual([{ type: "text", text: "hello" }]);
|
|
78
|
+
expect(adapter.items[0]!.prompt).toBe("hello");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("excludes attachment-derived text parts from the projection", () => {
|
|
82
|
+
const run = vi.fn();
|
|
83
|
+
const { adapter, notifyBusy } = createMessageQueue({ run });
|
|
84
|
+
notifyBusy();
|
|
85
|
+
|
|
86
|
+
adapter.enqueue(
|
|
87
|
+
msg("caption", {
|
|
88
|
+
attachments: [
|
|
89
|
+
{
|
|
90
|
+
id: "att1",
|
|
91
|
+
type: "document",
|
|
92
|
+
name: "notes.md",
|
|
93
|
+
contentType: "text/markdown",
|
|
94
|
+
status: { type: "complete" },
|
|
95
|
+
content: [
|
|
96
|
+
{
|
|
97
|
+
type: "text",
|
|
98
|
+
text: "<attachment>entire file body</attachment>",
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
}),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
expect(adapter.items[0]!.parts).toEqual([
|
|
107
|
+
{ type: "text", text: "caption" },
|
|
108
|
+
]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("projects parts in source order, converting image parts to file parts", () => {
|
|
112
|
+
const run = vi.fn();
|
|
113
|
+
const { adapter, notifyBusy } = createMessageQueue({ run });
|
|
114
|
+
notifyBusy();
|
|
115
|
+
|
|
116
|
+
adapter.enqueue(
|
|
117
|
+
msg("caption", {
|
|
118
|
+
content: [
|
|
119
|
+
{ type: "text", text: "caption" },
|
|
120
|
+
{ type: "image", image: "https://example.com/cat.png" },
|
|
121
|
+
],
|
|
122
|
+
attachments: [
|
|
123
|
+
{
|
|
124
|
+
id: "att1",
|
|
125
|
+
type: "file",
|
|
126
|
+
name: "doc.pdf",
|
|
127
|
+
contentType: "application/pdf",
|
|
128
|
+
status: { type: "complete" },
|
|
129
|
+
content: [
|
|
130
|
+
{
|
|
131
|
+
type: "file",
|
|
132
|
+
data: "data:application/pdf;base64,QQ==",
|
|
133
|
+
mimeType: "application/pdf",
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
expect(adapter.items[0]!.parts).toEqual([
|
|
142
|
+
{ type: "text", text: "caption" },
|
|
143
|
+
{
|
|
144
|
+
type: "file",
|
|
145
|
+
data: "https://example.com/cat.png",
|
|
146
|
+
mimeType: "image/*",
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
type: "file",
|
|
150
|
+
data: "data:application/pdf;base64,QQ==",
|
|
151
|
+
mimeType: "application/pdf",
|
|
152
|
+
},
|
|
153
|
+
]);
|
|
154
|
+
});
|
|
155
|
+
|
|
70
156
|
it("removes a queued message before it runs", () => {
|
|
71
157
|
const run = vi.fn();
|
|
72
158
|
const { adapter, notifyIdle } = createMessageQueue({ run });
|
|
73
159
|
|
|
74
|
-
adapter.enqueue(msg("a")
|
|
75
|
-
adapter.enqueue(msg("b")
|
|
76
|
-
adapter.enqueue(msg("c")
|
|
160
|
+
adapter.enqueue(msg("a")); // runs now
|
|
161
|
+
adapter.enqueue(msg("b"));
|
|
162
|
+
adapter.enqueue(msg("c"));
|
|
77
163
|
|
|
78
164
|
adapter.remove(adapter.items[0]!.id); // remove "b"
|
|
79
|
-
expect(adapter.items
|
|
165
|
+
expect(prompts(adapter.items)).toEqual(["c"]);
|
|
80
166
|
|
|
81
167
|
notifyIdle();
|
|
82
168
|
expect(run).toHaveBeenLastCalledWith(
|
|
@@ -85,23 +171,35 @@ describe("createMessageQueue", () => {
|
|
|
85
171
|
);
|
|
86
172
|
});
|
|
87
173
|
|
|
88
|
-
it("
|
|
174
|
+
it("edits a queued message in place, keeping id and position", () => {
|
|
89
175
|
const run = vi.fn();
|
|
90
|
-
const { adapter } = createMessageQueue({ run });
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
adapter.
|
|
94
|
-
|
|
176
|
+
const { adapter, notifyBusy } = createMessageQueue({ run });
|
|
177
|
+
notifyBusy();
|
|
178
|
+
|
|
179
|
+
adapter.enqueue(msg("a"));
|
|
180
|
+
adapter.enqueue(msg("b"));
|
|
181
|
+
const id = adapter.items[0]!.id;
|
|
182
|
+
|
|
183
|
+
adapter.edit(id, msg("a2"));
|
|
184
|
+
expect(prompts(adapter.items)).toEqual(["a2", "b"]);
|
|
185
|
+
expect(adapter.items[0]!.id).toBe(id);
|
|
95
186
|
});
|
|
96
187
|
|
|
97
|
-
it("
|
|
188
|
+
it("edit throws on an unknown queue item", () => {
|
|
189
|
+
const { adapter } = createMessageQueue({ run: vi.fn() });
|
|
190
|
+
expect(() => adapter.edit("nope", msg("x"))).toThrow(
|
|
191
|
+
'Unknown queue item "nope"',
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("steer interrupts via cancel and suppresses the cancelled run's idle", () => {
|
|
98
196
|
const run = vi.fn();
|
|
99
197
|
const cancel = vi.fn();
|
|
100
198
|
const { adapter, notifyIdle } = createMessageQueue({ run, cancel });
|
|
101
199
|
|
|
102
|
-
adapter.enqueue(msg("a")
|
|
103
|
-
adapter.enqueue(msg("b")
|
|
104
|
-
adapter.
|
|
200
|
+
adapter.enqueue(msg("a")); // running
|
|
201
|
+
adapter.enqueue(msg("b")); // queued
|
|
202
|
+
adapter.steer(msg("steer-me"));
|
|
105
203
|
|
|
106
204
|
expect(cancel).toHaveBeenCalledTimes(1);
|
|
107
205
|
expect(run).toHaveBeenLastCalledWith(
|
|
@@ -124,16 +222,16 @@ describe("createMessageQueue", () => {
|
|
|
124
222
|
);
|
|
125
223
|
});
|
|
126
224
|
|
|
127
|
-
it("
|
|
225
|
+
it("steer without cancel dispatches next, before the queue lane", () => {
|
|
128
226
|
const run = vi.fn();
|
|
129
227
|
const { adapter, notifyIdle } = createMessageQueue({ run }); // no cancel
|
|
130
228
|
|
|
131
|
-
adapter.enqueue(msg("a")
|
|
132
|
-
adapter.enqueue(msg("b")
|
|
133
|
-
adapter.
|
|
229
|
+
adapter.enqueue(msg("a")); // running
|
|
230
|
+
adapter.enqueue(msg("b"));
|
|
231
|
+
adapter.steer(msg("urgent"));
|
|
134
232
|
|
|
135
|
-
|
|
136
|
-
expect(adapter.items
|
|
233
|
+
expect(prompts(adapter.steerItems)).toEqual(["urgent"]);
|
|
234
|
+
expect(prompts(adapter.items)).toEqual(["b"]);
|
|
137
235
|
|
|
138
236
|
notifyIdle();
|
|
139
237
|
expect(run).toHaveBeenLastCalledWith(
|
|
@@ -142,13 +240,375 @@ describe("createMessageQueue", () => {
|
|
|
142
240
|
);
|
|
143
241
|
});
|
|
144
242
|
|
|
243
|
+
it("advance pops the steer lane before the queue lane", () => {
|
|
244
|
+
const order: string[] = [];
|
|
245
|
+
const run = vi.fn((m: AppendMessage) =>
|
|
246
|
+
order.push((m.content[0] as { text: string }).text),
|
|
247
|
+
);
|
|
248
|
+
const { adapter, notifyBusy, notifyIdle } = createMessageQueue({ run });
|
|
249
|
+
|
|
250
|
+
notifyBusy();
|
|
251
|
+
adapter.enqueue(msg("q1"));
|
|
252
|
+
adapter.steer(msg("s1"));
|
|
253
|
+
adapter.steer(msg("s2"));
|
|
254
|
+
|
|
255
|
+
notifyIdle();
|
|
256
|
+
notifyIdle();
|
|
257
|
+
notifyIdle();
|
|
258
|
+
expect(order).toEqual(["s1", "s2", "q1"]);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe("move", () => {
|
|
262
|
+
const setup = () => {
|
|
263
|
+
const run = vi.fn();
|
|
264
|
+
const queue = createMessageQueue({ run });
|
|
265
|
+
queue.notifyBusy();
|
|
266
|
+
queue.adapter.enqueue(msg("a"));
|
|
267
|
+
queue.adapter.enqueue(msg("b"));
|
|
268
|
+
queue.adapter.enqueue(msg("c"));
|
|
269
|
+
queue.adapter.steer(msg("s"));
|
|
270
|
+
const id = (prompt: string) =>
|
|
271
|
+
[...queue.adapter.steerItems, ...queue.adapter.items].find(
|
|
272
|
+
(i) => i.prompt === prompt,
|
|
273
|
+
)!.id;
|
|
274
|
+
return { ...queue, id, run };
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
it("keeps position within the same lane when no placement is given", () => {
|
|
278
|
+
const { adapter, id } = setup();
|
|
279
|
+
adapter.move(id("b"), {});
|
|
280
|
+
expect(prompts(adapter.items)).toEqual(["a", "b", "c"]);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("moves to the destination lane tail on lane change without placement", () => {
|
|
284
|
+
const { adapter, id } = setup();
|
|
285
|
+
adapter.move(id("s"), { lane: "queue" });
|
|
286
|
+
expect(prompts(adapter.steerItems)).toEqual([]);
|
|
287
|
+
expect(prompts(adapter.items)).toEqual(["a", "b", "c", "s"]);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("moves into the steer lane without interrupting when no cancel is available", () => {
|
|
291
|
+
const { adapter, id } = setup();
|
|
292
|
+
adapter.move(id("b"), { lane: "steer" });
|
|
293
|
+
expect(prompts(adapter.steerItems)).toEqual(["s", "b"]);
|
|
294
|
+
expect(prompts(adapter.items)).toEqual(["a", "c"]);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it("reorders within the steer lane", () => {
|
|
298
|
+
const { adapter, id } = setup();
|
|
299
|
+
adapter.steer(msg("s2"));
|
|
300
|
+
adapter.move(id("s2"), { insertBefore: id("s") });
|
|
301
|
+
expect(prompts(adapter.steerItems)).toEqual(["s2", "s"]);
|
|
302
|
+
expect(prompts(adapter.items)).toEqual(["a", "b", "c"]);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("insertAfter: null moves to the front of the lane", () => {
|
|
306
|
+
const { adapter, id } = setup();
|
|
307
|
+
adapter.move(id("c"), { insertAfter: null });
|
|
308
|
+
expect(prompts(adapter.items)).toEqual(["c", "a", "b"]);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("insertBefore: null moves to the end of the lane", () => {
|
|
312
|
+
const { adapter, id } = setup();
|
|
313
|
+
adapter.move(id("a"), { insertBefore: null });
|
|
314
|
+
expect(prompts(adapter.items)).toEqual(["b", "c", "a"]);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("insertAfter an anchor id places the item right after it", () => {
|
|
318
|
+
const { adapter, id } = setup();
|
|
319
|
+
adapter.move(id("a"), { insertAfter: id("b") });
|
|
320
|
+
expect(prompts(adapter.items)).toEqual(["b", "a", "c"]);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it("insertBefore an anchor id places the item right before it", () => {
|
|
324
|
+
const { adapter, id } = setup();
|
|
325
|
+
adapter.move(id("c"), { insertBefore: id("b") });
|
|
326
|
+
expect(prompts(adapter.items)).toEqual(["a", "c", "b"]);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("accepts an adjacent insertAfter/insertBefore pair", () => {
|
|
330
|
+
const { adapter, id } = setup();
|
|
331
|
+
adapter.move(id("c"), { insertAfter: id("a"), insertBefore: id("b") });
|
|
332
|
+
expect(prompts(adapter.items)).toEqual(["a", "c", "b"]);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it("throws when the insertAfter/insertBefore pair is not adjacent", () => {
|
|
336
|
+
const { adapter, id } = setup();
|
|
337
|
+
expect(() =>
|
|
338
|
+
adapter.move(id("b"), { insertAfter: id("c"), insertBefore: id("a") }),
|
|
339
|
+
).toThrow("not adjacent");
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it("throws on an unknown queue item", () => {
|
|
343
|
+
const { adapter } = setup();
|
|
344
|
+
expect(() => adapter.move("nope", {})).toThrow(
|
|
345
|
+
'Unknown queue item "nope"',
|
|
346
|
+
);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("throws on an unknown anchor", () => {
|
|
350
|
+
const { adapter, id } = setup();
|
|
351
|
+
expect(() => adapter.move(id("a"), { insertAfter: "nope" })).toThrow(
|
|
352
|
+
'Unknown anchor "nope"',
|
|
353
|
+
);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("throws on a self-anchor", () => {
|
|
357
|
+
const { adapter, id } = setup();
|
|
358
|
+
expect(() => adapter.move(id("a"), { insertAfter: id("a") })).toThrow(
|
|
359
|
+
"cannot anchor itself",
|
|
360
|
+
);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("throws when the anchor lives in a different lane", () => {
|
|
364
|
+
const { adapter, id } = setup();
|
|
365
|
+
expect(() =>
|
|
366
|
+
adapter.move(id("s"), { lane: "queue", insertAfter: id("s") }),
|
|
367
|
+
).toThrow("cannot anchor itself");
|
|
368
|
+
expect(() => adapter.move(id("a"), { insertAfter: id("s") })).toThrow(
|
|
369
|
+
"Unknown anchor",
|
|
370
|
+
);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it("interrupts via cancel when moving into the steer lane mid-run", () => {
|
|
374
|
+
const run = vi.fn();
|
|
375
|
+
const cancel = vi.fn();
|
|
376
|
+
const { adapter, notifyIdle } = createMessageQueue({ run, cancel });
|
|
377
|
+
|
|
378
|
+
adapter.enqueue(msg("a")); // running
|
|
379
|
+
adapter.enqueue(msg("b"));
|
|
380
|
+
adapter.enqueue(msg("c"));
|
|
381
|
+
|
|
382
|
+
adapter.move(adapter.items[1]!.id, { lane: "steer" }); // "c"
|
|
383
|
+
expect(cancel).toHaveBeenCalledTimes(1);
|
|
384
|
+
expect(run).toHaveBeenLastCalledWith(
|
|
385
|
+
expect.objectContaining({ content: [{ type: "text", text: "c" }] }),
|
|
386
|
+
{ steer: true },
|
|
387
|
+
);
|
|
388
|
+
expect(prompts(adapter.items)).toEqual(["b"]);
|
|
389
|
+
|
|
390
|
+
// cancelled run's settle is swallowed
|
|
391
|
+
notifyIdle();
|
|
392
|
+
expect(run).toHaveBeenCalledTimes(2);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it("validates anchors mid-run instead of interrupting past them", () => {
|
|
396
|
+
const run = vi.fn();
|
|
397
|
+
const cancel = vi.fn();
|
|
398
|
+
const { adapter } = createMessageQueue({ run, cancel });
|
|
399
|
+
|
|
400
|
+
adapter.enqueue(msg("a")); // running
|
|
401
|
+
adapter.enqueue(msg("b"));
|
|
402
|
+
const bId = adapter.items[0]!.id;
|
|
403
|
+
|
|
404
|
+
expect(() =>
|
|
405
|
+
adapter.move(bId, { lane: "steer", insertAfter: "nope" }),
|
|
406
|
+
).toThrow('Unknown anchor "nope"');
|
|
407
|
+
expect(() =>
|
|
408
|
+
adapter.move(bId, { lane: "steer", insertAfter: bId }),
|
|
409
|
+
).toThrow("cannot anchor itself");
|
|
410
|
+
expect(cancel).not.toHaveBeenCalled();
|
|
411
|
+
expect(run).toHaveBeenCalledTimes(1);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it("places an anchored move into the steer lane mid-run without interrupting", () => {
|
|
415
|
+
const run = vi.fn();
|
|
416
|
+
const cancel = vi.fn();
|
|
417
|
+
const { adapter, notifyIdle } = createMessageQueue({ run, cancel });
|
|
418
|
+
|
|
419
|
+
adapter.enqueue(msg("a")); // running
|
|
420
|
+
adapter.enqueue(msg("b"));
|
|
421
|
+
adapter.enqueue(msg("c"));
|
|
422
|
+
|
|
423
|
+
adapter.move(adapter.items[1]!.id, { lane: "steer", insertAfter: null }); // "c"
|
|
424
|
+
expect(cancel).not.toHaveBeenCalled();
|
|
425
|
+
expect(run).toHaveBeenCalledTimes(1);
|
|
426
|
+
expect(prompts(adapter.steerItems)).toEqual(["c"]);
|
|
427
|
+
expect(prompts(adapter.items)).toEqual(["b"]);
|
|
428
|
+
|
|
429
|
+
// the placed item dispatches first once the live run settles
|
|
430
|
+
notifyIdle();
|
|
431
|
+
expect(run).toHaveBeenLastCalledWith(
|
|
432
|
+
expect.objectContaining({ content: [{ type: "text", text: "c" }] }),
|
|
433
|
+
{ steer: false },
|
|
434
|
+
);
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it("notifyCancelled keeps items and pauses advance until the next send", () => {
|
|
439
|
+
const run = vi.fn();
|
|
440
|
+
const { adapter, notifyIdle, notifyCancelled } = createMessageQueue({
|
|
441
|
+
run,
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
adapter.enqueue(msg("a")); // running
|
|
445
|
+
adapter.enqueue(msg("b"));
|
|
446
|
+
adapter.enqueue(msg("c"));
|
|
447
|
+
|
|
448
|
+
notifyCancelled();
|
|
449
|
+
notifyIdle(); // the cancelled run settles
|
|
450
|
+
expect(run).toHaveBeenCalledTimes(1);
|
|
451
|
+
expect(prompts(adapter.items)).toEqual(["b", "c"]);
|
|
452
|
+
|
|
453
|
+
// the next explicit send re-arms draining, head first
|
|
454
|
+
adapter.enqueue(msg("d"));
|
|
455
|
+
expect(run).toHaveBeenLastCalledWith(
|
|
456
|
+
expect.objectContaining({ content: [{ type: "text", text: "b" }] }),
|
|
457
|
+
{ steer: false },
|
|
458
|
+
);
|
|
459
|
+
expect(prompts(adapter.items)).toEqual(["c", "d"]);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it("a run started after a cancel re-arms draining", () => {
|
|
463
|
+
const run = vi.fn();
|
|
464
|
+
const { adapter, notifyBusy, notifyIdle, notifyCancelled } =
|
|
465
|
+
createMessageQueue({ run });
|
|
466
|
+
|
|
467
|
+
adapter.enqueue(msg("a")); // running
|
|
468
|
+
adapter.enqueue(msg("b"));
|
|
469
|
+
|
|
470
|
+
notifyCancelled();
|
|
471
|
+
notifyIdle(); // the cancelled run settles
|
|
472
|
+
expect(run).toHaveBeenCalledTimes(1);
|
|
473
|
+
|
|
474
|
+
notifyBusy(); // regenerate
|
|
475
|
+
notifyIdle();
|
|
476
|
+
expect(run).toHaveBeenCalledTimes(2);
|
|
477
|
+
expect(run).toHaveBeenLastCalledWith(
|
|
478
|
+
expect.objectContaining({ content: [{ type: "text", text: "b" }] }),
|
|
479
|
+
{ steer: false },
|
|
480
|
+
);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it("swallows the cancelled run's settle when a replacement run starts first", () => {
|
|
484
|
+
const run = vi.fn();
|
|
485
|
+
const { adapter, notifyBusy, notifyIdle, notifyCancelled } =
|
|
486
|
+
createMessageQueue({ run });
|
|
487
|
+
|
|
488
|
+
adapter.enqueue(msg("a")); // running
|
|
489
|
+
adapter.enqueue(msg("b"));
|
|
490
|
+
|
|
491
|
+
notifyCancelled();
|
|
492
|
+
notifyBusy(); // a regenerate starts before the cancelled run settles
|
|
493
|
+
notifyIdle(); // the cancelled run's late settle
|
|
494
|
+
expect(run).toHaveBeenCalledTimes(1); // nothing dispatches mid-run
|
|
495
|
+
|
|
496
|
+
notifyIdle(); // the replacement run settles
|
|
497
|
+
expect(run).toHaveBeenCalledTimes(2);
|
|
498
|
+
expect(run).toHaveBeenLastCalledWith(
|
|
499
|
+
expect.objectContaining({ content: [{ type: "text", text: "b" }] }),
|
|
500
|
+
{ steer: false },
|
|
501
|
+
);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it("a send during the cancellation window drains once the cancelled run settles", () => {
|
|
505
|
+
const run = vi.fn();
|
|
506
|
+
const { adapter, notifyIdle, notifyCancelled } = createMessageQueue({
|
|
507
|
+
run,
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
adapter.enqueue(msg("a")); // running
|
|
511
|
+
adapter.enqueue(msg("b"));
|
|
512
|
+
|
|
513
|
+
notifyCancelled();
|
|
514
|
+
adapter.enqueue(msg("c")); // re-arms before the cancelled run settles
|
|
515
|
+
expect(run).toHaveBeenCalledTimes(1);
|
|
516
|
+
|
|
517
|
+
notifyIdle(); // the cancelled run settles
|
|
518
|
+
expect(run).toHaveBeenCalledTimes(2);
|
|
519
|
+
expect(run).toHaveBeenLastCalledWith(
|
|
520
|
+
expect.objectContaining({ content: [{ type: "text", text: "b" }] }),
|
|
521
|
+
{ steer: false },
|
|
522
|
+
);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it("steering during the cancellation window swallows only the cancelled settle", () => {
|
|
526
|
+
const run = vi.fn();
|
|
527
|
+
const cancel = vi.fn();
|
|
528
|
+
const { adapter, notifyIdle, notifyCancelled } = createMessageQueue({
|
|
529
|
+
run,
|
|
530
|
+
cancel,
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
adapter.enqueue(msg("a")); // running
|
|
534
|
+
adapter.enqueue(msg("b"));
|
|
535
|
+
|
|
536
|
+
notifyCancelled();
|
|
537
|
+
adapter.steer(msg("s")); // interrupts before the cancelled run settles
|
|
538
|
+
expect(run).toHaveBeenCalledTimes(2);
|
|
539
|
+
|
|
540
|
+
notifyIdle(); // the cancelled run's settle is swallowed
|
|
541
|
+
expect(run).toHaveBeenCalledTimes(2);
|
|
542
|
+
|
|
543
|
+
notifyIdle(); // the steered run settles and draining resumes
|
|
544
|
+
expect(run).toHaveBeenCalledTimes(3);
|
|
545
|
+
expect(run).toHaveBeenLastCalledWith(
|
|
546
|
+
expect.objectContaining({ content: [{ type: "text", text: "b" }] }),
|
|
547
|
+
{ steer: false },
|
|
548
|
+
);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it("a repeated cancel counts a single settle", () => {
|
|
552
|
+
const run = vi.fn();
|
|
553
|
+
const { adapter, notifyBusy, notifyIdle, notifyCancelled } =
|
|
554
|
+
createMessageQueue({ run });
|
|
555
|
+
|
|
556
|
+
adapter.enqueue(msg("a")); // running
|
|
557
|
+
adapter.enqueue(msg("b"));
|
|
558
|
+
|
|
559
|
+
notifyCancelled();
|
|
560
|
+
notifyCancelled();
|
|
561
|
+
notifyBusy(); // a replacement run starts inside the cancellation window
|
|
562
|
+
|
|
563
|
+
notifyIdle(); // the cancelled run's settle is swallowed exactly once
|
|
564
|
+
expect(run).toHaveBeenCalledTimes(1);
|
|
565
|
+
|
|
566
|
+
notifyIdle(); // the replacement run settles and draining resumes
|
|
567
|
+
expect(run).toHaveBeenCalledTimes(2);
|
|
568
|
+
expect(run).toHaveBeenLastCalledWith(
|
|
569
|
+
expect.objectContaining({ content: [{ type: "text", text: "b" }] }),
|
|
570
|
+
{ steer: false },
|
|
571
|
+
);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it("clear empties both lanes without dispatching", () => {
|
|
575
|
+
const run = vi.fn();
|
|
576
|
+
const { adapter, notifyBusy, clear } = createMessageQueue({ run });
|
|
577
|
+
|
|
578
|
+
notifyBusy();
|
|
579
|
+
adapter.enqueue(msg("a"));
|
|
580
|
+
adapter.steer(msg("s"));
|
|
581
|
+
clear();
|
|
582
|
+
|
|
583
|
+
expect(adapter.items).toHaveLength(0);
|
|
584
|
+
expect(adapter.steerItems).toHaveLength(0);
|
|
585
|
+
expect(run).not.toHaveBeenCalled();
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
it("dispatches sends normally after clear", () => {
|
|
589
|
+
const run = vi.fn();
|
|
590
|
+
const { adapter, notifyIdle, clear } = createMessageQueue({ run });
|
|
591
|
+
|
|
592
|
+
adapter.enqueue(msg("a")); // running
|
|
593
|
+
adapter.enqueue(msg("b"));
|
|
594
|
+
clear();
|
|
595
|
+
notifyIdle();
|
|
596
|
+
expect(run).toHaveBeenCalledTimes(1);
|
|
597
|
+
|
|
598
|
+
adapter.enqueue(msg("c"));
|
|
599
|
+
expect(run).toHaveBeenLastCalledWith(
|
|
600
|
+
expect.objectContaining({ content: [{ type: "text", text: "c" }] }),
|
|
601
|
+
{ steer: false },
|
|
602
|
+
);
|
|
603
|
+
});
|
|
604
|
+
|
|
145
605
|
it("notifies subscribers on item changes", () => {
|
|
146
606
|
const run = vi.fn();
|
|
147
607
|
const { adapter, subscribe } = createMessageQueue({ run });
|
|
148
608
|
const cb = vi.fn();
|
|
149
609
|
subscribe(cb);
|
|
150
610
|
|
|
151
|
-
adapter.enqueue(msg("a")
|
|
611
|
+
adapter.enqueue(msg("a")); // runs (1 setLanes push + 1 pop)
|
|
152
612
|
expect(cb).toHaveBeenCalled();
|
|
153
613
|
});
|
|
154
614
|
|
|
@@ -157,9 +617,9 @@ describe("createMessageQueue", () => {
|
|
|
157
617
|
const { adapter, notifyBusy, notifyIdle } = createMessageQueue({ run });
|
|
158
618
|
|
|
159
619
|
notifyBusy(); // e.g. a regenerate started without going through the queue
|
|
160
|
-
adapter.enqueue(msg("a")
|
|
620
|
+
adapter.enqueue(msg("a"));
|
|
161
621
|
expect(run).not.toHaveBeenCalled();
|
|
162
|
-
expect(adapter.items
|
|
622
|
+
expect(prompts(adapter.items)).toEqual(["a"]);
|
|
163
623
|
|
|
164
624
|
notifyIdle(); // that run settled
|
|
165
625
|
expect(run).toHaveBeenCalledTimes(1);
|
|
@@ -174,6 +174,72 @@ describe("fromThreadMessageLike", () => {
|
|
|
174
174
|
{ type: "image", image: "DATA:IMAGE/PNG;base64,AAAA" },
|
|
175
175
|
]);
|
|
176
176
|
});
|
|
177
|
+
|
|
178
|
+
it.each([
|
|
179
|
+
"data:image/avif;base64,AAAA",
|
|
180
|
+
"data:image/bmp;base64,AAAA",
|
|
181
|
+
"data:image/heic;base64,AAAA",
|
|
182
|
+
"data:image/heif;base64,AAAA",
|
|
183
|
+
"data:image/tiff;base64,AAAA",
|
|
184
|
+
])("keeps an image part with a %s data URL", (image) => {
|
|
185
|
+
const result = fromThreadMessageLike(
|
|
186
|
+
{ role: "assistant", content: [{ type: "image", image }] },
|
|
187
|
+
fallbackId,
|
|
188
|
+
fallbackStatus,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
expect(result.content).toEqual([{ type: "image", image }]);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("keeps an image part with a parameterized data URL", () => {
|
|
195
|
+
const image = "data:image/png;charset=utf-8;base64,AAAA";
|
|
196
|
+
const result = fromThreadMessageLike(
|
|
197
|
+
{ role: "assistant", content: [{ type: "image", image }] },
|
|
198
|
+
fallbackId,
|
|
199
|
+
fallbackStatus,
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
expect(result.content).toEqual([{ type: "image", image }]);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("drops an image part with a non-image data URL", () => {
|
|
206
|
+
const result = fromThreadMessageLike(
|
|
207
|
+
{
|
|
208
|
+
role: "assistant",
|
|
209
|
+
content: [{ type: "image", image: "data:text/plain;base64,AAAA" }],
|
|
210
|
+
},
|
|
211
|
+
fallbackId,
|
|
212
|
+
fallbackStatus,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
expect(result.content).toEqual([]);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("drops an image part with a non-base64 data URL", () => {
|
|
219
|
+
const result = fromThreadMessageLike(
|
|
220
|
+
{
|
|
221
|
+
role: "assistant",
|
|
222
|
+
content: [{ type: "image", image: "data:image/png,rawpayload" }],
|
|
223
|
+
},
|
|
224
|
+
fallbackId,
|
|
225
|
+
fallbackStatus,
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
expect(result.content).toEqual([]);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("drops an image part with a non-URL string", () => {
|
|
232
|
+
const result = fromThreadMessageLike(
|
|
233
|
+
{
|
|
234
|
+
role: "assistant",
|
|
235
|
+
content: [{ type: "image", image: "not-an-image" }],
|
|
236
|
+
},
|
|
237
|
+
fallbackId,
|
|
238
|
+
fallbackStatus,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
expect(result.content).toEqual([]);
|
|
242
|
+
});
|
|
177
243
|
});
|
|
178
244
|
|
|
179
245
|
describe("providerMetadata passthrough", () => {
|