@assistant-ui/react 0.14.9 → 0.14.12
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/augmentations.d.ts.map +1 -1
- package/dist/context/react/utils/createContextHook.d.ts.map +1 -1
- package/dist/context/react/utils/createContextStoreHook.d.ts.map +1 -1
- package/dist/context/react/utils/createStateHookForRuntime.d.ts.map +1 -1
- package/dist/context/react/utils/createStateHookForRuntime.js.map +1 -1
- package/dist/context/react/utils/useRuntimeState.d.ts.map +1 -1
- package/dist/devtools/DevToolsHooks.d.ts.map +1 -1
- package/dist/devtools/DevToolsHooks.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/internal.d.ts +3 -4
- package/dist/internal.js +1 -3
- package/dist/internal.js.map +1 -1
- package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts +3 -2
- package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +4 -10
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.d.ts +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/mcp-apps/app-frame.d.ts.map +1 -1
- package/dist/mcp-apps/app-frame.js.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.js +5 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerKeyboardResource.js.map +1 -1
- package/dist/primitives/messagePart/MessagePartImage.js.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +12 -2
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/dist/unstable/useMentionAdapter.js.map +1 -1
- package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
- package/dist/utils/Primitive.d.ts.map +1 -1
- package/dist/utils/useToolArgsFieldStatus.d.ts +2 -2
- package/dist/utils/useToolArgsFieldStatus.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/augmentations.ts +0 -2
- package/src/context/react/utils/createStateHookForRuntime.ts +0 -1
- package/src/devtools/DevToolsHooks.ts +0 -4
- package/src/index.ts +6 -1
- package/src/internal.ts +1 -4
- package/src/legacy-runtime/runtime/ThreadListRuntime.ts +1 -4
- package/src/legacy-runtime/runtime-cores/assistant-transport/types.ts +3 -1
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +7 -27
- package/src/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.ts +1 -1
- package/src/mcp-apps/McpAppRenderer.tsx +1 -1
- package/src/mcp-apps/app-frame.tsx +2 -2
- package/src/primitives/assistantModal/AssistantModalRoot.tsx +1 -1
- package/src/primitives/composer/ComposerAttachmentDropzone.test.tsx +1 -1
- package/src/primitives/composer/ComposerInput.test.tsx +1 -1
- package/src/primitives/composer/trigger/TriggerPopoverRootContext.test.tsx +1 -1
- package/src/primitives/composer/trigger/triggerKeyboardResource.ts +0 -2
- package/src/primitives/messagePart/MessagePartImage.tsx +0 -1
- package/src/primitives/thread/useThreadViewportAutoScroll.ts +26 -2
- package/src/primitives/threadListItem/ThreadListItemTitle.test.tsx +1 -1
- package/src/tests/BaseComposerRuntimeCore.test.ts +2 -2
- package/src/tests/ExternalStoreThreadRuntimeCore.test.ts +62 -0
- package/src/tests/MessageParts.loading.test.tsx +1 -1
- package/src/tests/RemoteThreadListRuntime.adapterProvider.test.tsx +2 -4
- package/src/tests/auiV0Encode.test.ts +1 -1
- package/src/unstable/useMentionAdapter.ts +0 -3
- package/src/unstable/useSlashCommandAdapter.ts +0 -2
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.d.ts +0 -2
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.js +0 -2
- package/src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.test.ts +0 -892
- package/src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.ts +0 -4
package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import { useExternalStoreRuntime } from "../external-store/useExternalStoreRuntime";
|
|
10
10
|
import type { AssistantRuntime } from "../../runtime/AssistantRuntime";
|
|
11
11
|
import type { AddToolResultOptions } from "@assistant-ui/core";
|
|
12
|
-
import {
|
|
12
|
+
import { useMemo, useRef, useState } from "react";
|
|
13
13
|
import {
|
|
14
14
|
AssistantMessageAccumulator,
|
|
15
15
|
DataStreamDecoder,
|
|
@@ -29,10 +29,7 @@ import type {
|
|
|
29
29
|
import { useCommandQueue } from "./commandQueue";
|
|
30
30
|
import { useRunManager } from "./runManager";
|
|
31
31
|
import { useConvertedState } from "./useConvertedState";
|
|
32
|
-
import {
|
|
33
|
-
type ToolExecutionStatus,
|
|
34
|
-
useToolInvocations,
|
|
35
|
-
} from "./useToolInvocations";
|
|
32
|
+
import type { ToolExecutionStatus } from "@assistant-ui/core";
|
|
36
33
|
import { createRequestHeaders } from "@assistant-ui/core";
|
|
37
34
|
import { useRemoteThreadListRuntime } from "../remote-thread-list/useRemoteThreadListRuntime";
|
|
38
35
|
import { InMemoryThreadListAdapter } from "@assistant-ui/core";
|
|
@@ -116,23 +113,16 @@ export function useAssistantTransportState<T>(
|
|
|
116
113
|
const useAssistantTransportThreadRuntime = <T>(
|
|
117
114
|
options: AssistantTransportOptions<T>,
|
|
118
115
|
): AssistantRuntime => {
|
|
119
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
120
116
|
const agentStateRef = useRef(options.initialState);
|
|
121
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
122
117
|
const [, rerender] = useState(0);
|
|
123
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
124
118
|
const resumeFlagRef = useRef(false);
|
|
125
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
126
119
|
const parentIdRef = useRef<string | null | undefined>(undefined);
|
|
127
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
128
120
|
const commandQueue = useCommandQueue({
|
|
129
121
|
onQueue: () => runManager.schedule(),
|
|
130
122
|
});
|
|
131
123
|
|
|
132
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
133
124
|
const threadId = useAuiState((s) => s.threadListItem.remoteId);
|
|
134
125
|
|
|
135
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
136
126
|
const runManager = useRunManager({
|
|
137
127
|
onRun: async (signal: AbortSignal) => {
|
|
138
128
|
const isResume = resumeFlagRef.current;
|
|
@@ -276,18 +266,15 @@ const useAssistantTransportThreadRuntime = <T>(
|
|
|
276
266
|
});
|
|
277
267
|
|
|
278
268
|
// Tool execution status state
|
|
279
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
280
269
|
const [toolStatuses, setToolStatuses] = useState<
|
|
281
270
|
Record<string, ToolExecutionStatus>
|
|
282
271
|
>({});
|
|
283
272
|
|
|
284
273
|
// Reactive conversion of agent state + connection metadata → UI state
|
|
285
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
286
274
|
const pendingCommands = useMemo(
|
|
287
275
|
() => [...commandQueue.state.inTransit, ...commandQueue.state.queued],
|
|
288
276
|
[commandQueue.state],
|
|
289
277
|
);
|
|
290
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
291
278
|
const converted = useConvertedState(
|
|
292
279
|
options.converter,
|
|
293
280
|
agentStateRef.current,
|
|
@@ -297,12 +284,13 @@ const useAssistantTransportThreadRuntime = <T>(
|
|
|
297
284
|
);
|
|
298
285
|
|
|
299
286
|
// Create runtime
|
|
300
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
301
287
|
const runtime = useExternalStoreRuntime({
|
|
302
288
|
messages: converted.messages,
|
|
303
289
|
state: converted.state,
|
|
304
290
|
isRunning: converted.isRunning,
|
|
305
291
|
adapters: options.adapters,
|
|
292
|
+
unstable_enableToolInvocations: true,
|
|
293
|
+
setToolStatuses,
|
|
306
294
|
extras: {
|
|
307
295
|
[symbolAssistantTransportExtras]: true,
|
|
308
296
|
sendCommand: (command: AssistantTransportCommand) => {
|
|
@@ -324,7 +312,6 @@ const useAssistantTransportThreadRuntime = <T>(
|
|
|
324
312
|
}),
|
|
325
313
|
onCancel: async () => {
|
|
326
314
|
runManager.cancel();
|
|
327
|
-
await toolInvocations.abort();
|
|
328
315
|
},
|
|
329
316
|
onResume: async () => {
|
|
330
317
|
if (!options.resumeApi)
|
|
@@ -343,25 +330,19 @@ const useAssistantTransportThreadRuntime = <T>(
|
|
|
343
330
|
toolName: toolOptions.toolName,
|
|
344
331
|
isError: toolOptions.isError,
|
|
345
332
|
...(toolOptions.artifact && { artifact: toolOptions.artifact }),
|
|
333
|
+
...(toolOptions.modelContent !== undefined && {
|
|
334
|
+
modelContent: toolOptions.modelContent,
|
|
335
|
+
}),
|
|
346
336
|
};
|
|
347
337
|
|
|
348
338
|
commandQueue.enqueue(command);
|
|
349
339
|
},
|
|
350
340
|
onLoadExternalState: async (state) => {
|
|
351
341
|
agentStateRef.current = state as T;
|
|
352
|
-
toolInvocations.reset();
|
|
353
342
|
rerender((prev) => prev + 1);
|
|
354
343
|
},
|
|
355
344
|
});
|
|
356
345
|
|
|
357
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
358
|
-
const toolInvocations = useToolInvocations({
|
|
359
|
-
state: converted,
|
|
360
|
-
getTools: () => runtime.thread.getModelContext().tools,
|
|
361
|
-
onResult: commandQueue.enqueue,
|
|
362
|
-
setToolStatuses,
|
|
363
|
-
});
|
|
364
|
-
|
|
365
346
|
return runtime;
|
|
366
347
|
};
|
|
367
348
|
|
|
@@ -373,7 +354,6 @@ export const useAssistantTransportRuntime = <T>(
|
|
|
373
354
|
): AssistantRuntime => {
|
|
374
355
|
const runtime = useRemoteThreadListRuntime({
|
|
375
356
|
runtimeHook: function RuntimeHook() {
|
|
376
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
377
357
|
return useAssistantTransportThreadRuntime(options);
|
|
378
358
|
},
|
|
379
359
|
adapter: new InMemoryThreadListAdapter(),
|
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
AssistantTransportState,
|
|
5
5
|
AssistantTransportStateConverter,
|
|
6
6
|
} from "./types";
|
|
7
|
-
import type { ToolExecutionStatus } from "
|
|
7
|
+
import type { ToolExecutionStatus } from "@assistant-ui/core";
|
|
8
8
|
|
|
9
9
|
export function useConvertedState<T>(
|
|
10
10
|
converter: AssistantTransportStateConverter<T>,
|
|
@@ -113,7 +113,6 @@ function InlineRenderer({
|
|
|
113
113
|
const [loadedResource, setLoadedResource] = useState<LoadedResourceState>();
|
|
114
114
|
|
|
115
115
|
const resourceUri = appForRender?.resourceUri;
|
|
116
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: re-fetches only when URI changes; mcp.app object identity is unstable across renders
|
|
117
116
|
useEffect(() => {
|
|
118
117
|
if (appForRender == null || resourceUri == null) return;
|
|
119
118
|
let cancelled = false;
|
|
@@ -137,6 +136,7 @@ function InlineRenderer({
|
|
|
137
136
|
return () => {
|
|
138
137
|
cancelled = true;
|
|
139
138
|
};
|
|
139
|
+
// oxlint-disable-next-line tap-hooks/exhaustive-deps -- re-fetch only when URI changes; appForRender identity is unstable and internalsRef is a stable ref
|
|
140
140
|
}, [resourceUri]);
|
|
141
141
|
|
|
142
142
|
const bridgeHandlers = useMemo<McpAppBridgeHandlers>(
|
|
@@ -21,7 +21,6 @@ function useBridgeNotify<T>(
|
|
|
21
21
|
lastSentRef: MutableRefObject<T | undefined>,
|
|
22
22
|
notify: (bridge: McpAppBridge, v: T) => void,
|
|
23
23
|
) {
|
|
24
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: refs and notify are stable; we re-run only when value changes.
|
|
25
24
|
useEffect(() => {
|
|
26
25
|
if (!bridgeRef.current) return;
|
|
27
26
|
if (value === undefined) return;
|
|
@@ -32,6 +31,7 @@ function useBridgeNotify<T>(
|
|
|
32
31
|
}
|
|
33
32
|
notify(bridgeRef.current, value);
|
|
34
33
|
lastSentRef.current = value;
|
|
34
|
+
// oxlint-disable-next-line tap-hooks/exhaustive-deps -- refs are stable; notify is assumed stable; re-run only when value changes
|
|
35
35
|
}, [value]);
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -131,7 +131,6 @@ export function McpAppFrame({
|
|
|
131
131
|
|
|
132
132
|
const resourceUri = resource.uri;
|
|
133
133
|
|
|
134
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: re-mounts only on resource URI; live values flow through liveRef
|
|
135
134
|
useEffect(() => {
|
|
136
135
|
const container = containerRef.current;
|
|
137
136
|
if (!container) return;
|
|
@@ -255,6 +254,7 @@ export function McpAppFrame({
|
|
|
255
254
|
pendingHostContextRef.current = undefined;
|
|
256
255
|
setContentHeight(undefined);
|
|
257
256
|
};
|
|
257
|
+
// oxlint-disable-next-line tap-hooks/exhaustive-deps -- re-mount only on resource URI change; live values flow through liveRef
|
|
258
258
|
}, [resourceUri]);
|
|
259
259
|
|
|
260
260
|
useBridgeNotify(
|
|
@@ -8,7 +8,7 @@ import { ComposerPrimitiveAttachmentDropzone } from "./ComposerAttachmentDropzon
|
|
|
8
8
|
|
|
9
9
|
const addAttachment = vi.fn<(file: File) => Promise<void>>();
|
|
10
10
|
|
|
11
|
-
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
11
|
+
(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
|
|
12
12
|
|
|
13
13
|
vi.mock("@assistant-ui/store", async (importOriginal) => {
|
|
14
14
|
const actual = await importOriginal<typeof import("@assistant-ui/store")>();
|
|
@@ -33,7 +33,7 @@ const plugin = {
|
|
|
33
33
|
|
|
34
34
|
let pluginRegistry: { getPlugins: () => (typeof plugin)[] } | null = null;
|
|
35
35
|
|
|
36
|
-
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
36
|
+
(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
|
|
37
37
|
|
|
38
38
|
vi.mock("@assistant-ui/store", () => {
|
|
39
39
|
const aui = {
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
useTriggerPopoverAriaPublish,
|
|
12
12
|
} from "./TriggerPopoverRootContext";
|
|
13
13
|
|
|
14
|
-
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
14
|
+
(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
|
|
15
15
|
|
|
16
16
|
type PublishHandle = ReturnType<typeof useTriggerPopoverAriaPublish>;
|
|
17
17
|
|
|
@@ -64,12 +64,10 @@ export const TriggerKeyboardResource = resource(
|
|
|
64
64
|
}): TriggerKeyboardResourceOutput => {
|
|
65
65
|
const [highlightedIndex, setHighlightedIndex] = tapState(0);
|
|
66
66
|
|
|
67
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: intentional reset on list change
|
|
68
67
|
tapEffect(() => {
|
|
69
68
|
setHighlightedIndex(0);
|
|
70
69
|
}, [navigableList]);
|
|
71
70
|
|
|
72
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: intentional reset
|
|
73
71
|
tapEffect(() => {
|
|
74
72
|
setHighlightedIndex(0);
|
|
75
73
|
}, [isSearchMode, activeCategoryId]);
|
|
@@ -37,7 +37,6 @@ export const MessagePartPrimitiveImage = forwardRef<
|
|
|
37
37
|
MessagePartPrimitiveImage.Props
|
|
38
38
|
>((props, forwardedRef) => {
|
|
39
39
|
const { image } = useMessagePartImage();
|
|
40
|
-
// biome-ignore lint/performance/noImgElement: next/image not applicable here
|
|
41
40
|
return <Primitive.img src={image} {...props} ref={forwardedRef} />;
|
|
42
41
|
});
|
|
43
42
|
|
|
@@ -59,9 +59,13 @@ export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
const lastScrollTop = useRef<number>(0);
|
|
62
|
+
const lastScrollHeight = useRef<number>(0);
|
|
63
|
+
const lastObservedScrollHeight = useRef<number>(0);
|
|
64
|
+
const lastObservedClientHeight = useRef<number>(0);
|
|
62
65
|
|
|
63
66
|
// Pending bottom-scroll intent. Planted by initialize/run-start/switch/button
|
|
64
|
-
// triggers, cleared
|
|
67
|
+
// triggers, cleared when handleScroll confirms we reached bottom, or when the
|
|
68
|
+
// user actively scrolls up while content size is stable.
|
|
65
69
|
const scrollingToBottomBehaviorRef = useRef<ScrollBehavior | null>(null);
|
|
66
70
|
|
|
67
71
|
const scrollToBottom = useCallback((behavior: ScrollBehavior) => {
|
|
@@ -110,7 +114,7 @@ export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
|
|
|
110
114
|
|
|
111
115
|
const isAtBottom = threadViewportStore.getState().isAtBottom;
|
|
112
116
|
const newIsAtBottom =
|
|
113
|
-
Math.abs(div.scrollHeight - div.scrollTop - div.clientHeight)
|
|
117
|
+
Math.abs(div.scrollHeight - div.scrollTop - div.clientHeight) <= 1 ||
|
|
114
118
|
div.scrollHeight <= div.clientHeight;
|
|
115
119
|
|
|
116
120
|
const isInFlightDownwardScroll =
|
|
@@ -126,6 +130,12 @@ export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
|
|
|
126
130
|
if (viewportOverflows) {
|
|
127
131
|
scrollingToBottomBehaviorRef.current = null;
|
|
128
132
|
}
|
|
133
|
+
} else if (
|
|
134
|
+
lastScrollTop.current > div.scrollTop &&
|
|
135
|
+
lastScrollHeight.current === div.scrollHeight
|
|
136
|
+
) {
|
|
137
|
+
// scrollHeight equality rules out content-driven shifts being misread as user scroll-up
|
|
138
|
+
scrollingToBottomBehaviorRef.current = null;
|
|
129
139
|
}
|
|
130
140
|
|
|
131
141
|
const shouldUpdate =
|
|
@@ -139,9 +149,23 @@ export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
|
|
|
139
149
|
}
|
|
140
150
|
|
|
141
151
|
lastScrollTop.current = div.scrollTop;
|
|
152
|
+
lastScrollHeight.current = div.scrollHeight;
|
|
142
153
|
};
|
|
143
154
|
|
|
144
155
|
const resizeRef = useOnResizeContent(() => {
|
|
156
|
+
const div = divRef.current;
|
|
157
|
+
if (!div) return;
|
|
158
|
+
|
|
159
|
+
const { scrollHeight, clientHeight } = div;
|
|
160
|
+
if (
|
|
161
|
+
scrollHeight === lastObservedScrollHeight.current &&
|
|
162
|
+
clientHeight === lastObservedClientHeight.current
|
|
163
|
+
) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
lastObservedScrollHeight.current = scrollHeight;
|
|
167
|
+
lastObservedClientHeight.current = clientHeight;
|
|
168
|
+
|
|
145
169
|
const scrollBehavior = scrollingToBottomBehaviorRef.current;
|
|
146
170
|
if (scrollBehavior && hasActiveTopAnchor()) {
|
|
147
171
|
// Let the top-anchor reserve own scrolling while a run starts to avoid a bottom-scroll race.
|
|
@@ -5,7 +5,7 @@ import { ThreadListItemPrimitiveTitle } from "./ThreadListItemTitle";
|
|
|
5
5
|
|
|
6
6
|
const mockUseAuiState = vi.fn();
|
|
7
7
|
type UseAuiStateSelector = Parameters<
|
|
8
|
-
typeof import("@assistant-ui/store")["useAuiState"]
|
|
8
|
+
(typeof import("@assistant-ui/store"))["useAuiState"]
|
|
9
9
|
>[0];
|
|
10
10
|
|
|
11
11
|
vi.mock("@assistant-ui/store", async (importOriginal) => {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
2
|
import { BaseComposerRuntimeCore } from "../legacy-runtime/runtime-cores/composer/BaseComposerRuntimeCore";
|
|
3
|
-
import type { AttachmentAdapter } from "../legacy-runtime/runtime-cores/adapters/attachment";
|
|
4
|
-
import type { DictationAdapter } from "../legacy-runtime/runtime-cores/adapters/speech/SpeechAdapterTypes";
|
|
5
3
|
import type {
|
|
6
4
|
AppendMessage,
|
|
5
|
+
AttachmentAdapter,
|
|
7
6
|
CreateAttachment,
|
|
7
|
+
DictationAdapter,
|
|
8
8
|
PendingAttachment,
|
|
9
9
|
SendOptions,
|
|
10
10
|
} from "@assistant-ui/core";
|
|
@@ -255,6 +255,68 @@ describe("ExternalStoreThreadRuntimeCore", () => {
|
|
|
255
255
|
"Runtime does not support cancelling runs.",
|
|
256
256
|
);
|
|
257
257
|
});
|
|
258
|
+
|
|
259
|
+
it("keeps a partially-streamed optimistic message on cancel", async () => {
|
|
260
|
+
// Only empty optimistic heads are evicted; one with content survives.
|
|
261
|
+
const optimisticAssistant = {
|
|
262
|
+
...createAssistantMessage("server-msg", "partial answer"),
|
|
263
|
+
status: { type: "running" as const },
|
|
264
|
+
metadata: {
|
|
265
|
+
unstable_state: null,
|
|
266
|
+
unstable_annotations: [],
|
|
267
|
+
unstable_data: [],
|
|
268
|
+
steps: [],
|
|
269
|
+
custom: {},
|
|
270
|
+
isOptimistic: true,
|
|
271
|
+
},
|
|
272
|
+
} as ThreadMessage;
|
|
273
|
+
const setMessages = vi.fn();
|
|
274
|
+
const adapter = createBaseAdapter({
|
|
275
|
+
messages: [createUserMessage("u1"), optimisticAssistant],
|
|
276
|
+
isRunning: true,
|
|
277
|
+
onCancel: vi.fn(),
|
|
278
|
+
setMessages,
|
|
279
|
+
});
|
|
280
|
+
const core = new ExternalStoreThreadRuntimeCore(contextProvider, adapter);
|
|
281
|
+
|
|
282
|
+
core.cancelRun();
|
|
283
|
+
|
|
284
|
+
// cancelRun resyncs to the store via setTimeout(0); inspect the push.
|
|
285
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
286
|
+
const lastCall = setMessages.mock.lastCall?.[0] as ThreadMessage[];
|
|
287
|
+
expect(lastCall.map((m) => m.id)).toContain("server-msg");
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("evicts an empty optimistic head on cancel", async () => {
|
|
291
|
+
// An empty optimistic head is dropped on cancel, falling back to its prev.
|
|
292
|
+
const optimisticAssistant = {
|
|
293
|
+
...createAssistantMessage("server-msg", ""),
|
|
294
|
+
content: [],
|
|
295
|
+
status: { type: "running" as const },
|
|
296
|
+
metadata: {
|
|
297
|
+
unstable_state: null,
|
|
298
|
+
unstable_annotations: [],
|
|
299
|
+
unstable_data: [],
|
|
300
|
+
steps: [],
|
|
301
|
+
custom: {},
|
|
302
|
+
isOptimistic: true,
|
|
303
|
+
},
|
|
304
|
+
} as ThreadMessage;
|
|
305
|
+
const setMessages = vi.fn();
|
|
306
|
+
const adapter = createBaseAdapter({
|
|
307
|
+
messages: [createUserMessage("u1"), optimisticAssistant],
|
|
308
|
+
isRunning: true,
|
|
309
|
+
onCancel: vi.fn(),
|
|
310
|
+
setMessages,
|
|
311
|
+
});
|
|
312
|
+
const core = new ExternalStoreThreadRuntimeCore(contextProvider, adapter);
|
|
313
|
+
|
|
314
|
+
core.cancelRun();
|
|
315
|
+
|
|
316
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
317
|
+
const lastCall = setMessages.mock.lastCall?.[0] as ThreadMessage[];
|
|
318
|
+
expect(lastCall.map((m) => m.id)).not.toContain("server-msg");
|
|
319
|
+
});
|
|
258
320
|
});
|
|
259
321
|
|
|
260
322
|
describe("optimistic assistant message", () => {
|
|
@@ -71,7 +71,7 @@ const RuntimeProvider: FC<
|
|
|
71
71
|
|
|
72
72
|
const renderThread = (MessageComponent: FC, messages?: ThreadMessageLike[]) => {
|
|
73
73
|
render(
|
|
74
|
-
<RuntimeProvider messages={messages}>
|
|
74
|
+
<RuntimeProvider messages={messages!}>
|
|
75
75
|
<ThreadPrimitive.Messages components={{ Message: MessageComponent }} />
|
|
76
76
|
</RuntimeProvider>,
|
|
77
77
|
);
|
|
@@ -7,15 +7,13 @@ import {
|
|
|
7
7
|
RuntimeAdapterProvider,
|
|
8
8
|
useRemoteThreadListRuntime,
|
|
9
9
|
useRuntimeAdapters,
|
|
10
|
+
type RuntimeAdapters as RuntimeAdaptersShape,
|
|
10
11
|
} from "@assistant-ui/core/react";
|
|
11
12
|
import { makeAdapter } from "./remote-thread-list-test-helpers";
|
|
12
13
|
import { useLocalRuntime } from "../legacy-runtime/runtime-cores/local/useLocalRuntime";
|
|
13
14
|
import { AssistantRuntimeProvider } from "../context";
|
|
14
15
|
import type { ChatModelAdapter, RemoteThreadListAdapter } from "../index";
|
|
15
|
-
import type {
|
|
16
|
-
RuntimeAdapters as RuntimeAdaptersShape,
|
|
17
|
-
ThreadHistoryAdapter,
|
|
18
|
-
} from "@assistant-ui/core";
|
|
16
|
+
import type { ThreadHistoryAdapter } from "@assistant-ui/core";
|
|
19
17
|
|
|
20
18
|
type CapturedAdapters = RuntimeAdaptersShape | null;
|
|
21
19
|
|
|
@@ -92,7 +92,6 @@ export function unstable_useMentionAdapter(
|
|
|
92
92
|
iconMap?: Record<string, Unstable_IconComponent>;
|
|
93
93
|
fallbackIcon?: Unstable_IconComponent;
|
|
94
94
|
} {
|
|
95
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
96
95
|
const aui = useAui();
|
|
97
96
|
|
|
98
97
|
const items = options?.items;
|
|
@@ -105,7 +104,6 @@ export function unstable_useMentionAdapter(
|
|
|
105
104
|
const formatter = options?.formatter;
|
|
106
105
|
const onInserted = options?.onInserted;
|
|
107
106
|
|
|
108
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
109
107
|
const adapter = useMemo<Unstable_TriggerAdapter>(() => {
|
|
110
108
|
const getModelContextTools = (): Unstable_TriggerItem[] => {
|
|
111
109
|
if (!wantsTools) return [];
|
|
@@ -182,7 +180,6 @@ export function unstable_useMentionAdapter(
|
|
|
182
180
|
};
|
|
183
181
|
}, [aui, items, categories, wantsTools, toolsConfig]);
|
|
184
182
|
|
|
185
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: `unstable_` prefix not recognized as hook
|
|
186
183
|
const directive = useMemo<Unstable_MentionDirective>(
|
|
187
184
|
() => ({
|
|
188
185
|
formatter: formatter ?? unstable_defaultDirectiveFormatter,
|
|
@@ -60,11 +60,9 @@ export function unstable_useSlashCommandAdapter(
|
|
|
60
60
|
} {
|
|
61
61
|
const { commands, removeOnExecute } = options;
|
|
62
62
|
|
|
63
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: `unstable_` prefix not recognized as hook
|
|
64
63
|
const commandsRef = useRef(commands);
|
|
65
64
|
commandsRef.current = commands;
|
|
66
65
|
|
|
67
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: `unstable_` prefix not recognized as hook
|
|
68
66
|
return useMemo(() => {
|
|
69
67
|
const adapter: Unstable_TriggerAdapter = {
|
|
70
68
|
categories: () => [],
|