@assistant-ui/react 0.14.29 → 0.15.0
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/client/ExternalThread.js +4 -3
- package/dist/client/ExternalThread.js.map +1 -1
- package/dist/client/InMemoryThreadList.js +3 -3
- package/dist/client/InMemoryThreadList.js.map +1 -1
- package/dist/context/index.d.ts +1 -8
- package/dist/context/index.js +1 -8
- package/dist/index.d.ts +1 -8
- package/dist/index.js +1 -8
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +3 -3
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
- package/dist/mcp-apps/McpAppRenderer.js +1 -1
- package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/model-context/makeAssistantVisible.js +26 -25
- package/dist/model-context/makeAssistantVisible.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js +3 -3
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
- package/dist/primitives/attachment/AttachmentRemove.js +3 -3
- package/dist/primitives/attachment/AttachmentRemove.js.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +3 -3
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js.map +1 -1
- package/dist/primitives/composer/ComposerAddAttachment.js +3 -3
- package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +13 -13
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerQuote.js +1 -1
- package/dist/primitives/composer/ComposerQuote.js.map +1 -1
- package/dist/primitives/composer/ComposerStopDictation.js +3 -3
- package/dist/primitives/composer/ComposerStopDictation.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.js +3 -3
- package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
- package/dist/primitives/message/MessagePartsGrouped.d.ts.map +1 -1
- package/dist/primitives/message/MessagePartsGrouped.js +67 -89
- package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
- package/dist/primitives/message/MessageRoot.js +3 -3
- package/dist/primitives/message/MessageRoot.js.map +1 -1
- package/dist/primitives/queueItem/QueueItemRemove.js +3 -3
- package/dist/primitives/queueItem/QueueItemRemove.js.map +1 -1
- package/dist/primitives/queueItem/QueueItemSteer.js +3 -3
- package/dist/primitives/queueItem/QueueItemSteer.js.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js.map +1 -1
- package/dist/primitives/suggestion/SuggestionTrigger.js +16 -15
- package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -1
- package/dist/unstable/useComposerInput.js +2 -2
- package/dist/unstable/useComposerInput.js.map +1 -1
- package/dist/unstable/useComposerInputHistory.js +6 -6
- package/dist/unstable/useComposerInputHistory.js.map +1 -1
- package/dist/unstable/useMentionAdapter.js +1 -1
- package/dist/unstable/useMentionAdapter.js.map +1 -1
- package/dist/utils/smooth/SmoothContext.js +3 -3
- package/dist/utils/smooth/SmoothContext.js.map +1 -1
- package/dist/utils/smooth/useSmooth.js +1 -1
- package/dist/utils/smooth/useSmooth.js.map +1 -1
- package/package.json +5 -5
- package/src/client/ExternalThread.ts +5 -3
- package/src/client/InMemoryThreadList.ts +3 -3
- package/src/context/index.ts +0 -44
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.test.tsx +5 -9
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +1 -1
- package/src/mcp-apps/McpAppRenderer.tsx +1 -1
- package/src/model-context/makeAssistantVisible.test.tsx +1 -1
- package/src/model-context/makeAssistantVisible.tsx +1 -1
- package/src/primitives/actionBar/ActionBarExportMarkdown.tsx +1 -1
- package/src/primitives/attachment/AttachmentRemove.ts +1 -1
- package/src/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.ts +1 -1
- package/src/primitives/composer/ComposerAddAttachment.test.tsx +2 -2
- package/src/primitives/composer/ComposerAddAttachment.ts +1 -1
- package/src/primitives/composer/ComposerAttachmentDropzone.test.tsx +2 -2
- package/src/primitives/composer/ComposerAttachmentDropzone.tsx +1 -1
- package/src/primitives/composer/ComposerInput.test.tsx +7 -7
- package/src/primitives/composer/ComposerInput.tsx +13 -13
- package/src/primitives/composer/ComposerQuote.tsx +1 -1
- package/src/primitives/composer/ComposerStopDictation.ts +1 -1
- package/src/primitives/composer/trigger/triggerSelectionResource.ts +7 -9
- package/src/primitives/message/MessagePartsGrouped.tsx +6 -8
- package/src/primitives/message/MessageRoot.tsx +1 -1
- package/src/primitives/queueItem/QueueItemRemove.ts +1 -1
- package/src/primitives/queueItem/QueueItemSteer.ts +1 -1
- package/src/primitives/selectionToolbar/SelectionToolbarQuote.tsx +1 -1
- package/src/primitives/suggestion/SuggestionTrigger.ts +9 -9
- package/src/tests/DataRenderers.test.tsx +1 -1
- package/src/tests/RemoteThreadListRuntime.deferredProvider.test.tsx +3 -3
- package/src/tests/external-thread-attachments.test.tsx +21 -23
- package/src/tests/external-thread-branches.test.tsx +10 -10
- package/src/tests/external-thread-parity.test.tsx +18 -20
- package/src/tests/local-runtime-queue.test.tsx +29 -34
- package/src/tests/messagePartSwitchRace.test.tsx +30 -13
- package/src/unstable/useComposerInput.test.tsx +2 -2
- package/src/unstable/useComposerInput.ts +2 -2
- package/src/unstable/useComposerInputHistory.test.tsx +3 -3
- package/src/unstable/useComposerInputHistory.ts +6 -6
- package/src/unstable/useMentionAdapter.ts +1 -1
- package/src/utils/smooth/SmoothContext.tsx +1 -1
- package/src/utils/smooth/useSmooth.test.tsx +1 -1
- package/src/utils/smooth/useSmooth.ts +1 -1
- package/dist/context/react/utils/createStateHookForRuntime.d.ts +0 -34
- package/dist/context/react/utils/createStateHookForRuntime.d.ts.map +0 -1
- package/dist/context/react/utils/createStateHookForRuntime.js +0 -21
- package/dist/context/react/utils/createStateHookForRuntime.js.map +0 -1
- package/dist/legacy-runtime/hooks/AssistantContext.d.ts +0 -74
- package/dist/legacy-runtime/hooks/AssistantContext.d.ts.map +0 -1
- package/dist/legacy-runtime/hooks/AssistantContext.js +0 -29
- package/dist/legacy-runtime/hooks/AssistantContext.js.map +0 -1
- package/dist/legacy-runtime/hooks/AttachmentContext.d.ts +0 -1007
- package/dist/legacy-runtime/hooks/AttachmentContext.d.ts.map +0 -1
- package/dist/legacy-runtime/hooks/AttachmentContext.js +0 -47
- package/dist/legacy-runtime/hooks/AttachmentContext.js.map +0 -1
- package/dist/legacy-runtime/hooks/ComposerContext.d.ts +0 -141
- package/dist/legacy-runtime/hooks/ComposerContext.d.ts.map +0 -1
- package/dist/legacy-runtime/hooks/ComposerContext.js +0 -65
- package/dist/legacy-runtime/hooks/ComposerContext.js.map +0 -1
- package/dist/legacy-runtime/hooks/MessageContext.d.ts +0 -155
- package/dist/legacy-runtime/hooks/MessageContext.d.ts.map +0 -1
- package/dist/legacy-runtime/hooks/MessageContext.js +0 -68
- package/dist/legacy-runtime/hooks/MessageContext.js.map +0 -1
- package/dist/legacy-runtime/hooks/MessagePartContext.d.ts +0 -44
- package/dist/legacy-runtime/hooks/MessagePartContext.d.ts.map +0 -1
- package/dist/legacy-runtime/hooks/MessagePartContext.js +0 -26
- package/dist/legacy-runtime/hooks/MessagePartContext.js.map +0 -1
- package/dist/legacy-runtime/hooks/ThreadContext.d.ts +0 -139
- package/dist/legacy-runtime/hooks/ThreadContext.d.ts.map +0 -1
- package/dist/legacy-runtime/hooks/ThreadContext.js +0 -84
- package/dist/legacy-runtime/hooks/ThreadContext.js.map +0 -1
- package/dist/legacy-runtime/hooks/ThreadListItemContext.d.ts +0 -44
- package/dist/legacy-runtime/hooks/ThreadListItemContext.d.ts.map +0 -1
- package/dist/legacy-runtime/hooks/ThreadListItemContext.js +0 -26
- package/dist/legacy-runtime/hooks/ThreadListItemContext.js.map +0 -1
- package/src/context/react/utils/createStateHookForRuntime.ts +0 -82
- package/src/legacy-runtime/hooks/AssistantContext.ts +0 -67
- package/src/legacy-runtime/hooks/AttachmentContext.ts +0 -99
- package/src/legacy-runtime/hooks/ComposerContext.ts +0 -131
- package/src/legacy-runtime/hooks/MessageContext.ts +0 -127
- package/src/legacy-runtime/hooks/MessagePartContext.ts +0 -32
- package/src/legacy-runtime/hooks/ThreadContext.ts +0 -120
- package/src/legacy-runtime/hooks/ThreadListItemContext.ts +0 -36
|
@@ -72,7 +72,7 @@ const useTriggerSelectionResource = ({
|
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
const currentText = aui.composer
|
|
75
|
+
const currentText = aui.composer.getState().text;
|
|
76
76
|
const before = currentText.slice(0, trigger.offset);
|
|
77
77
|
const after = currentText.slice(
|
|
78
78
|
trigger.offset + triggerChar.length + trigger.query.length,
|
|
@@ -80,11 +80,9 @@ const useTriggerSelectionResource = ({
|
|
|
80
80
|
|
|
81
81
|
const insertDirective = () => {
|
|
82
82
|
const directive = behavior.formatter.serialize(item);
|
|
83
|
-
aui
|
|
84
|
-
.
|
|
85
|
-
|
|
86
|
-
before + directive + (after.startsWith(" ") ? after : ` ${after}`),
|
|
87
|
-
);
|
|
83
|
+
aui.composer.setText(
|
|
84
|
+
before + directive + (after.startsWith(" ") ? after : ` ${after}`),
|
|
85
|
+
);
|
|
88
86
|
};
|
|
89
87
|
|
|
90
88
|
if (behavior.kind === "directive") {
|
|
@@ -92,9 +90,9 @@ const useTriggerSelectionResource = ({
|
|
|
92
90
|
behavior.onInserted?.(item);
|
|
93
91
|
} else {
|
|
94
92
|
if (behavior.removeOnExecute) {
|
|
95
|
-
aui
|
|
96
|
-
.
|
|
97
|
-
|
|
93
|
+
aui.composer.setText(
|
|
94
|
+
before + (after.startsWith(" ") ? after.slice(1) : after),
|
|
95
|
+
);
|
|
98
96
|
} else {
|
|
99
97
|
// Leave directive chip in the composer as an audit trail
|
|
100
98
|
insertDirective();
|
|
@@ -230,11 +230,9 @@ const ToolUIDisplay = ({
|
|
|
230
230
|
}: {
|
|
231
231
|
Fallback: ToolCallMessagePartComponent | undefined;
|
|
232
232
|
} & ToolCallMessagePartProps) => {
|
|
233
|
-
const Render = useAuiState(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return Render;
|
|
237
|
-
});
|
|
233
|
+
const Render = useAuiState(
|
|
234
|
+
(s) => s.tools.toolUIs[props.toolName]?.[0]?.render ?? Fallback,
|
|
235
|
+
);
|
|
238
236
|
if (!Render) return null;
|
|
239
237
|
return <Render {...props} />;
|
|
240
238
|
};
|
|
@@ -292,9 +290,9 @@ const MessagePartComponent: FC<MessagePartComponentProps> = ({
|
|
|
292
290
|
|
|
293
291
|
const type = part.type;
|
|
294
292
|
if (type === "tool-call") {
|
|
295
|
-
const addResult = aui.part
|
|
296
|
-
const resume = aui.part
|
|
297
|
-
const respondToApproval = aui.part
|
|
293
|
+
const addResult = aui.part.addToolResult;
|
|
294
|
+
const resume = aui.part.resumeToolCall;
|
|
295
|
+
const respondToApproval = aui.part.respondToToolApproval;
|
|
298
296
|
if ("Override" in tools)
|
|
299
297
|
return (
|
|
300
298
|
<tools.Override
|
|
@@ -23,7 +23,7 @@ type ThreadViewportStore = NonNullable<
|
|
|
23
23
|
|
|
24
24
|
const useIsHoveringRef = () => {
|
|
25
25
|
const aui = useAui();
|
|
26
|
-
const message = useAuiState(() => aui.message
|
|
26
|
+
const message = useAuiState(() => aui.message);
|
|
27
27
|
|
|
28
28
|
const callbackRef = useCallback(
|
|
29
29
|
(el: HTMLElement) => {
|
|
@@ -34,24 +34,24 @@ const useSuggestionTrigger = ({
|
|
|
34
34
|
const resolvedSend = send ?? false;
|
|
35
35
|
|
|
36
36
|
const callback = useCallback(() => {
|
|
37
|
-
const isRunning = aui.thread
|
|
37
|
+
const isRunning = aui.thread.getState().isRunning;
|
|
38
38
|
|
|
39
39
|
if (resolvedSend && !isRunning) {
|
|
40
|
-
aui.thread
|
|
40
|
+
aui.thread.append({
|
|
41
41
|
content: [{ type: "text", text: prompt }],
|
|
42
|
-
runConfig: aui.composer
|
|
42
|
+
runConfig: aui.composer.getState().runConfig,
|
|
43
43
|
});
|
|
44
44
|
if (clearComposer) {
|
|
45
|
-
aui.composer
|
|
45
|
+
aui.composer.setText("");
|
|
46
46
|
}
|
|
47
47
|
} else {
|
|
48
48
|
if (clearComposer) {
|
|
49
|
-
aui.composer
|
|
49
|
+
aui.composer.setText(prompt);
|
|
50
50
|
} else {
|
|
51
|
-
const currentText = aui.composer
|
|
52
|
-
aui
|
|
53
|
-
.
|
|
54
|
-
|
|
51
|
+
const currentText = aui.composer.getState().text;
|
|
52
|
+
aui.composer.setText(
|
|
53
|
+
currentText.trim() ? `${currentText} ${prompt}` : prompt,
|
|
54
|
+
);
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
}, [aui, resolvedSend, clearComposer, prompt]);
|
|
@@ -66,7 +66,7 @@ const GlobalFallbackDataUI: FC<{ render: DataMessagePartComponent }> = ({
|
|
|
66
66
|
render,
|
|
67
67
|
}) => {
|
|
68
68
|
const aui = useAui();
|
|
69
|
-
useEffect(() => aui.dataRenderers
|
|
69
|
+
useEffect(() => aui.dataRenderers.setFallbackDataUI(render), [aui, render]);
|
|
70
70
|
return null;
|
|
71
71
|
};
|
|
72
72
|
|
|
@@ -6,9 +6,9 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
6
6
|
import { useRemoteThreadListRuntime } from "@assistant-ui/core/react";
|
|
7
7
|
import { makeAdapter } from "./remote-thread-list-test-helpers";
|
|
8
8
|
import type { AssistantRuntime } from "@assistant-ui/core";
|
|
9
|
+
import { useAui } from "@assistant-ui/store";
|
|
9
10
|
import { AssistantRuntimeProvider } from "../context";
|
|
10
11
|
import { useLocalRuntime } from "../legacy-runtime/runtime-cores/local/useLocalRuntime";
|
|
11
|
-
import { useAssistantRuntime } from "../legacy-runtime/hooks/AssistantContext";
|
|
12
12
|
import type { ChatModelAdapter, RemoteThreadListAdapter } from "../index";
|
|
13
13
|
|
|
14
14
|
const noOpAdapter: ChatModelAdapter = {
|
|
@@ -34,8 +34,8 @@ const RuntimeProvider: FC<
|
|
|
34
34
|
const RuntimeCapture: FC<{
|
|
35
35
|
runtimeRef: { current: AssistantRuntime | null };
|
|
36
36
|
}> = ({ runtimeRef }) => {
|
|
37
|
-
const
|
|
38
|
-
runtimeRef.current =
|
|
37
|
+
const aui = useAui();
|
|
38
|
+
runtimeRef.current = aui.threads.__internal_getAssistantRuntime?.() ?? null;
|
|
39
39
|
return null;
|
|
40
40
|
};
|
|
41
41
|
|
|
@@ -74,14 +74,12 @@ describe("ExternalThread attachments", () => {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
await waitFor(() => {
|
|
77
|
-
expect(aui().thread
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
},
|
|
84
|
-
);
|
|
77
|
+
expect(aui().thread.composer().getState().attachments[0]).toMatchObject({
|
|
78
|
+
type: "document",
|
|
79
|
+
name: "notes.txt",
|
|
80
|
+
contentType: "text/plain",
|
|
81
|
+
content: [{ type: "text", text: "hello" }],
|
|
82
|
+
});
|
|
85
83
|
});
|
|
86
84
|
});
|
|
87
85
|
|
|
@@ -94,9 +92,9 @@ describe("ExternalThread attachments", () => {
|
|
|
94
92
|
content: [{ type: "text", text: "implementation detail" }],
|
|
95
93
|
} as File;
|
|
96
94
|
|
|
97
|
-
await act(() => aui().thread
|
|
95
|
+
await act(() => aui().thread.composer().addAttachment(foreignFile));
|
|
98
96
|
|
|
99
|
-
expect(aui().thread
|
|
97
|
+
expect(aui().thread.composer().getState().attachments[0]).toMatchObject({
|
|
100
98
|
type: "file",
|
|
101
99
|
name: "photo.png",
|
|
102
100
|
contentType: "image/png",
|
|
@@ -126,7 +124,7 @@ describe("ExternalThread attachments", () => {
|
|
|
126
124
|
"File type application/zip is not accepted. Accepted types: image/*",
|
|
127
125
|
);
|
|
128
126
|
expect(add).not.toHaveBeenCalled();
|
|
129
|
-
expect(aui().thread
|
|
127
|
+
expect(aui().thread.composer().getState().attachments).toHaveLength(0);
|
|
130
128
|
});
|
|
131
129
|
|
|
132
130
|
it("rejects prepared attachments that do not match the adapter accept string", async () => {
|
|
@@ -151,7 +149,7 @@ describe("ExternalThread attachments", () => {
|
|
|
151
149
|
).rejects.toThrow(
|
|
152
150
|
"File type text/plain is not accepted. Accepted types: .pdf",
|
|
153
151
|
);
|
|
154
|
-
expect(aui().thread
|
|
152
|
+
expect(aui().thread.composer().getState().attachments).toHaveLength(0);
|
|
155
153
|
});
|
|
156
154
|
|
|
157
155
|
it("restores the draft when an attachment upload fails on send", async () => {
|
|
@@ -192,21 +190,21 @@ describe("ExternalThread attachments", () => {
|
|
|
192
190
|
.addAttachment(new File(["data"], "notes.txt", { type: "text/plain" })),
|
|
193
191
|
);
|
|
194
192
|
await act(async () => {
|
|
195
|
-
aui().thread
|
|
193
|
+
aui().thread.composer().setText("hello");
|
|
196
194
|
});
|
|
197
195
|
await act(async () => {
|
|
198
|
-
aui().thread
|
|
196
|
+
aui().thread.composer().send();
|
|
199
197
|
});
|
|
200
198
|
|
|
201
199
|
// The draft is cleared optimistically while the upload runs.
|
|
202
|
-
expect(aui().thread
|
|
200
|
+
expect(aui().thread.composer().getState().text).toBe("");
|
|
203
201
|
|
|
204
202
|
await act(async () => {
|
|
205
203
|
rejectSend(new Error("upload failed"));
|
|
206
204
|
});
|
|
207
205
|
|
|
208
206
|
await waitFor(() => {
|
|
209
|
-
const state = aui().thread
|
|
207
|
+
const state = aui().thread.composer().getState();
|
|
210
208
|
expect(state.text).toBe("hello");
|
|
211
209
|
expect(state.attachments).toHaveLength(1);
|
|
212
210
|
});
|
|
@@ -255,13 +253,13 @@ describe("ExternalThread attachments", () => {
|
|
|
255
253
|
),
|
|
256
254
|
);
|
|
257
255
|
|
|
258
|
-
await act(() => invoke(aui().thread
|
|
256
|
+
await act(() => invoke(aui().thread.composer()));
|
|
259
257
|
|
|
260
258
|
expect(remove).toHaveBeenCalledTimes(1);
|
|
261
259
|
expect(remove).toHaveBeenCalledWith(
|
|
262
260
|
expect.objectContaining({ id: "att-1" }),
|
|
263
261
|
);
|
|
264
|
-
expect(aui().thread
|
|
262
|
+
expect(aui().thread.composer().getState().attachments).toHaveLength(0);
|
|
265
263
|
},
|
|
266
264
|
);
|
|
267
265
|
|
|
@@ -297,7 +295,7 @@ describe("ExternalThread attachments", () => {
|
|
|
297
295
|
},
|
|
298
296
|
});
|
|
299
297
|
|
|
300
|
-
const composer = () => aui().thread
|
|
298
|
+
const composer = () => aui().thread.message({ id: "u1" }).composer();
|
|
301
299
|
await act(async () => {
|
|
302
300
|
composer().beginEdit();
|
|
303
301
|
});
|
|
@@ -349,18 +347,18 @@ describe("ExternalThread attachments", () => {
|
|
|
349
347
|
.addAttachment(new File(["data"], "notes.txt", { type: "text/plain" })),
|
|
350
348
|
);
|
|
351
349
|
await act(async () => {
|
|
352
|
-
aui().thread
|
|
353
|
-
aui().thread
|
|
350
|
+
aui().thread.composer().setText("hello");
|
|
351
|
+
aui().thread.composer().send();
|
|
354
352
|
});
|
|
355
353
|
await act(async () => {
|
|
356
|
-
aui().thread
|
|
354
|
+
aui().thread.composer().setText("meanwhile");
|
|
357
355
|
});
|
|
358
356
|
|
|
359
357
|
await act(async () => {
|
|
360
358
|
rejectSend(new Error("upload failed"));
|
|
361
359
|
});
|
|
362
360
|
|
|
363
|
-
const state = aui().thread
|
|
361
|
+
const state = aui().thread.composer().getState();
|
|
364
362
|
expect(state.text).toBe("hello\nmeanwhile");
|
|
365
363
|
expect(state.attachments).toHaveLength(1);
|
|
366
364
|
expect(state.attachments[0]).toMatchObject({ id: "att-1" });
|
|
@@ -66,18 +66,18 @@ const adapterFor = (
|
|
|
66
66
|
describe("ExternalThread branches", () => {
|
|
67
67
|
it("defaults to single-branch state without an adapter", () => {
|
|
68
68
|
const { aui } = renderThread(baseProps());
|
|
69
|
-
const state = aui().thread
|
|
69
|
+
const state = aui().thread.getState();
|
|
70
70
|
expect(state.messages[1]!.branchNumber).toBe(1);
|
|
71
71
|
expect(state.messages[1]!.branchCount).toBe(1);
|
|
72
72
|
expect(state.capabilities.switchToBranch).toBe(false);
|
|
73
73
|
expect(() =>
|
|
74
|
-
aui().thread
|
|
74
|
+
aui().thread.message({ index: 1 }).switchToBranch({ position: "next" }),
|
|
75
75
|
).not.toThrow();
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
it("derives branchNumber and branchCount from the adapter", () => {
|
|
79
79
|
const { aui } = renderThread(baseProps(adapterFor(["a1", "a2", "a3"])));
|
|
80
|
-
const state = aui().thread
|
|
80
|
+
const state = aui().thread.getState();
|
|
81
81
|
expect(state.messages[1]!.branchNumber).toBe(2);
|
|
82
82
|
expect(state.messages[1]!.branchCount).toBe(3);
|
|
83
83
|
expect(state.capabilities.switchToBranch).toBe(true);
|
|
@@ -88,7 +88,7 @@ describe("ExternalThread branches", () => {
|
|
|
88
88
|
const { aui } = renderThread(
|
|
89
89
|
baseProps(adapterFor(["a1", "a2", "a3"], switchToBranch)),
|
|
90
90
|
);
|
|
91
|
-
const msg = () => aui().thread
|
|
91
|
+
const msg = () => aui().thread.message({ index: 1 });
|
|
92
92
|
|
|
93
93
|
msg().switchToBranch({ position: "previous" });
|
|
94
94
|
expect(switchToBranch).toHaveBeenLastCalledWith("a1");
|
|
@@ -101,7 +101,7 @@ describe("ExternalThread branches", () => {
|
|
|
101
101
|
isRunning: false,
|
|
102
102
|
branches: adapterFor(["a1", "a2"], switchToBranch),
|
|
103
103
|
});
|
|
104
|
-
edge.aui().thread
|
|
104
|
+
edge.aui().thread.message({ index: 1 }).switchToBranch({
|
|
105
105
|
position: "previous",
|
|
106
106
|
});
|
|
107
107
|
expect(switchToBranch).not.toHaveBeenCalled();
|
|
@@ -112,7 +112,7 @@ describe("ExternalThread branches", () => {
|
|
|
112
112
|
const { aui } = renderThread(
|
|
113
113
|
baseProps(adapterFor(["a1", "a2"], switchToBranch)),
|
|
114
114
|
);
|
|
115
|
-
const msg = () => aui().thread
|
|
115
|
+
const msg = () => aui().thread.message({ index: 1 });
|
|
116
116
|
|
|
117
117
|
msg().switchToBranch({ branchId: "not-in-the-list" });
|
|
118
118
|
expect(switchToBranch).toHaveBeenLastCalledWith("not-in-the-list");
|
|
@@ -130,11 +130,11 @@ describe("ExternalThread branches", () => {
|
|
|
130
130
|
switchToBranch,
|
|
131
131
|
}),
|
|
132
132
|
);
|
|
133
|
-
const state = aui().thread
|
|
133
|
+
const state = aui().thread.getState();
|
|
134
134
|
expect(state.messages[1]!.branchNumber).toBe(1);
|
|
135
135
|
expect(state.messages[1]!.branchCount).toBe(1);
|
|
136
136
|
|
|
137
|
-
aui().thread
|
|
137
|
+
aui().thread.message({ index: 1 }).switchToBranch({ position: "next" });
|
|
138
138
|
expect(switchToBranch).not.toHaveBeenCalled();
|
|
139
139
|
});
|
|
140
140
|
|
|
@@ -145,14 +145,14 @@ describe("ExternalThread branches", () => {
|
|
|
145
145
|
isRunning: false,
|
|
146
146
|
branches: adapterFor(["a1", "a2", "a3"]),
|
|
147
147
|
});
|
|
148
|
-
const before = aui().thread
|
|
148
|
+
const before = aui().thread.getState().messages[1];
|
|
149
149
|
|
|
150
150
|
rerender({
|
|
151
151
|
messages,
|
|
152
152
|
isRunning: false,
|
|
153
153
|
branches: adapterFor(["a1", "a2", "a3"]),
|
|
154
154
|
});
|
|
155
|
-
const after = aui().thread
|
|
155
|
+
const after = aui().thread.getState().messages[1];
|
|
156
156
|
|
|
157
157
|
expect(after!.branchNumber).toBe(2);
|
|
158
158
|
expect(before!.parts[0]).toBe(after!.parts[0]);
|
|
@@ -66,7 +66,7 @@ describe("ExternalThread part status", () => {
|
|
|
66
66
|
isRunning: true,
|
|
67
67
|
});
|
|
68
68
|
const part = (toolCallId: string) =>
|
|
69
|
-
aui().thread
|
|
69
|
+
aui().thread.message({ id: "a1" }).part({ toolCallId }).getState();
|
|
70
70
|
expect(part("tc1").status).toEqual({ type: "running" });
|
|
71
71
|
});
|
|
72
72
|
|
|
@@ -75,7 +75,7 @@ describe("ExternalThread part status", () => {
|
|
|
75
75
|
messages: [assistantMessage({ type: "running" }, "ok")],
|
|
76
76
|
isRunning: true,
|
|
77
77
|
});
|
|
78
|
-
const state = aui().thread
|
|
78
|
+
const state = aui().thread.message({ id: "a1" }).getState();
|
|
79
79
|
expect(state.parts[0]!.status).toEqual({ type: "complete" });
|
|
80
80
|
expect(state.parts[1]!.status).toEqual({ type: "complete" });
|
|
81
81
|
});
|
|
@@ -96,7 +96,7 @@ describe("ExternalThread part status", () => {
|
|
|
96
96
|
isRunning: false,
|
|
97
97
|
});
|
|
98
98
|
expect(
|
|
99
|
-
aui().thread
|
|
99
|
+
aui().thread.message({ id: "u1" }).part({ index: 0 }).getState().status,
|
|
100
100
|
).toEqual({ type: "complete" });
|
|
101
101
|
expect(
|
|
102
102
|
aui()
|
|
@@ -106,7 +106,7 @@ describe("ExternalThread part status", () => {
|
|
|
106
106
|
.getState().status,
|
|
107
107
|
).toEqual({ type: "requires-action", reason: "tool-calls" });
|
|
108
108
|
expect(
|
|
109
|
-
aui().thread
|
|
109
|
+
aui().thread.message({ id: "a1" }).part({ index: 0 }).getState().status,
|
|
110
110
|
).toEqual({ type: "complete" });
|
|
111
111
|
});
|
|
112
112
|
});
|
|
@@ -120,7 +120,7 @@ describe("ExternalThread unset optional callbacks", () => {
|
|
|
120
120
|
isRunning: false,
|
|
121
121
|
});
|
|
122
122
|
const part = () =>
|
|
123
|
-
aui().thread
|
|
123
|
+
aui().thread.message({ id: "a1" }).part({ toolCallId: "tc1" });
|
|
124
124
|
|
|
125
125
|
expect(() => part().addToolResult("ok")).toThrow(
|
|
126
126
|
"Runtime does not support tool results (onAddToolResult is not set).",
|
|
@@ -128,10 +128,10 @@ describe("ExternalThread unset optional callbacks", () => {
|
|
|
128
128
|
expect(() => part().resumeToolCall(undefined)).toThrow(
|
|
129
129
|
"Runtime does not support resuming tool calls (onResumeToolCall is not set).",
|
|
130
130
|
);
|
|
131
|
-
expect(() => aui().thread
|
|
131
|
+
expect(() => aui().thread.resumeRun()).toThrow(
|
|
132
132
|
"Runtime does not support resuming runs (onResume is not set).",
|
|
133
133
|
);
|
|
134
|
-
expect(() => aui().thread
|
|
134
|
+
expect(() => aui().thread.importExternalState({})).toThrow(
|
|
135
135
|
"Runtime does not support importing external states (onLoadExternalState is not set).",
|
|
136
136
|
);
|
|
137
137
|
});
|
|
@@ -142,16 +142,14 @@ describe("ExternalThread composer", () => {
|
|
|
142
142
|
const onNew = vi.fn();
|
|
143
143
|
const { aui } = renderThread({ messages: [], isRunning: false, onNew });
|
|
144
144
|
|
|
145
|
-
aui().thread
|
|
146
|
-
aui().thread
|
|
145
|
+
aui().thread.composer().setText("hello");
|
|
146
|
+
aui().thread.composer().send();
|
|
147
147
|
|
|
148
148
|
await waitFor(() => expect(onNew).toHaveBeenCalledTimes(1));
|
|
149
149
|
expect(onNew.mock.calls[0]![0].content).toEqual([
|
|
150
150
|
{ type: "text", text: "hello" },
|
|
151
151
|
]);
|
|
152
|
-
await waitFor(() =>
|
|
153
|
-
expect(aui().thread().getState().composer.text).toBe(""),
|
|
154
|
-
);
|
|
152
|
+
await waitFor(() => expect(aui().thread.getState().composer.text).toBe(""));
|
|
155
153
|
});
|
|
156
154
|
|
|
157
155
|
it("stamps the thread head as parentId on queue-adapter sends", async () => {
|
|
@@ -177,8 +175,8 @@ describe("ExternalThread composer", () => {
|
|
|
177
175
|
},
|
|
178
176
|
});
|
|
179
177
|
|
|
180
|
-
aui().thread
|
|
181
|
-
aui().thread
|
|
178
|
+
aui().thread.composer().setText("queued");
|
|
179
|
+
aui().thread.composer().send();
|
|
182
180
|
|
|
183
181
|
await waitFor(() => expect(enqueue).toHaveBeenCalledTimes(1));
|
|
184
182
|
expect(enqueue.mock.calls[0]![0].parentId).toBe("u1");
|
|
@@ -188,10 +186,10 @@ describe("ExternalThread composer", () => {
|
|
|
188
186
|
const onNew = vi.fn();
|
|
189
187
|
const { aui } = renderThread({ messages: [], isRunning: false, onNew });
|
|
190
188
|
|
|
191
|
-
aui().thread
|
|
192
|
-
aui().thread
|
|
193
|
-
aui().thread
|
|
194
|
-
aui().thread
|
|
189
|
+
aui().thread.composer().send();
|
|
190
|
+
aui().thread.composer().setText("first");
|
|
191
|
+
aui().thread.composer().send();
|
|
192
|
+
aui().thread.composer().send();
|
|
195
193
|
|
|
196
194
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
197
195
|
expect(onNew).toHaveBeenCalledTimes(1);
|
|
@@ -219,10 +217,10 @@ describe("ExternalThread duplicate message ids", () => {
|
|
|
219
217
|
],
|
|
220
218
|
});
|
|
221
219
|
|
|
222
|
-
const state = aui().thread
|
|
220
|
+
const state = aui().thread.getState();
|
|
223
221
|
expect(state.messages.map((m) => m.id)).toEqual(["u1", "dup"]);
|
|
224
222
|
|
|
225
|
-
const dup = aui().thread
|
|
223
|
+
const dup = aui().thread.message({ id: "dup" }).getState();
|
|
226
224
|
expect(dup.parts[0]).toMatchObject({ type: "text", text: "fresh" });
|
|
227
225
|
expect(warn).toHaveBeenCalledWith(expect.stringContaining('"dup"'));
|
|
228
226
|
} finally {
|