@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.
Files changed (69) hide show
  1. package/dist/augmentations.d.ts.map +1 -1
  2. package/dist/context/react/utils/createContextHook.d.ts.map +1 -1
  3. package/dist/context/react/utils/createContextStoreHook.d.ts.map +1 -1
  4. package/dist/context/react/utils/createStateHookForRuntime.d.ts.map +1 -1
  5. package/dist/context/react/utils/createStateHookForRuntime.js.map +1 -1
  6. package/dist/context/react/utils/useRuntimeState.d.ts.map +1 -1
  7. package/dist/devtools/DevToolsHooks.d.ts.map +1 -1
  8. package/dist/devtools/DevToolsHooks.js.map +1 -1
  9. package/dist/index.d.ts +3 -3
  10. package/dist/index.js +3 -3
  11. package/dist/internal.d.ts +3 -4
  12. package/dist/internal.js +1 -3
  13. package/dist/internal.js.map +1 -1
  14. package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.d.ts.map +1 -1
  15. package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts +3 -2
  16. package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts.map +1 -1
  17. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
  18. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +4 -10
  19. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
  20. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.d.ts +1 -1
  21. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.d.ts.map +1 -1
  22. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js.map +1 -1
  23. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.d.ts.map +1 -1
  24. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  25. package/dist/mcp-apps/app-frame.d.ts.map +1 -1
  26. package/dist/mcp-apps/app-frame.js.map +1 -1
  27. package/dist/primitives/assistantModal/AssistantModalRoot.js +5 -1
  28. package/dist/primitives/assistantModal/AssistantModalRoot.js.map +1 -1
  29. package/dist/primitives/composer/trigger/triggerKeyboardResource.js.map +1 -1
  30. package/dist/primitives/messagePart/MessagePartImage.js.map +1 -1
  31. package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
  32. package/dist/primitives/thread/useThreadViewportAutoScroll.js +12 -2
  33. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  34. package/dist/unstable/useMentionAdapter.js.map +1 -1
  35. package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
  36. package/dist/utils/Primitive.d.ts.map +1 -1
  37. package/dist/utils/useToolArgsFieldStatus.d.ts +2 -2
  38. package/dist/utils/useToolArgsFieldStatus.d.ts.map +1 -1
  39. package/package.json +8 -8
  40. package/src/augmentations.ts +0 -2
  41. package/src/context/react/utils/createStateHookForRuntime.ts +0 -1
  42. package/src/devtools/DevToolsHooks.ts +0 -4
  43. package/src/index.ts +6 -1
  44. package/src/internal.ts +1 -4
  45. package/src/legacy-runtime/runtime/ThreadListRuntime.ts +1 -4
  46. package/src/legacy-runtime/runtime-cores/assistant-transport/types.ts +3 -1
  47. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +7 -27
  48. package/src/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.ts +1 -1
  49. package/src/mcp-apps/McpAppRenderer.tsx +1 -1
  50. package/src/mcp-apps/app-frame.tsx +2 -2
  51. package/src/primitives/assistantModal/AssistantModalRoot.tsx +1 -1
  52. package/src/primitives/composer/ComposerAttachmentDropzone.test.tsx +1 -1
  53. package/src/primitives/composer/ComposerInput.test.tsx +1 -1
  54. package/src/primitives/composer/trigger/TriggerPopoverRootContext.test.tsx +1 -1
  55. package/src/primitives/composer/trigger/triggerKeyboardResource.ts +0 -2
  56. package/src/primitives/messagePart/MessagePartImage.tsx +0 -1
  57. package/src/primitives/thread/useThreadViewportAutoScroll.ts +26 -2
  58. package/src/primitives/threadListItem/ThreadListItemTitle.test.tsx +1 -1
  59. package/src/tests/BaseComposerRuntimeCore.test.ts +2 -2
  60. package/src/tests/ExternalStoreThreadRuntimeCore.test.ts +62 -0
  61. package/src/tests/MessageParts.loading.test.tsx +1 -1
  62. package/src/tests/RemoteThreadListRuntime.adapterProvider.test.tsx +2 -4
  63. package/src/tests/auiV0Encode.test.ts +1 -1
  64. package/src/unstable/useMentionAdapter.ts +0 -3
  65. package/src/unstable/useSlashCommandAdapter.ts +0 -2
  66. package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.d.ts +0 -2
  67. package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.js +0 -2
  68. package/src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.test.ts +0 -892
  69. package/src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.ts +0 -4
@@ -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 { useState, useRef, useMemo } from "react";
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 "./useToolInvocations";
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(
@@ -28,7 +28,7 @@ const useAssistantModalOpenState = ({
28
28
  return aui.on("thread.runStart", () => {
29
29
  setOpen(true);
30
30
  });
31
- }, [unstable_openOnRunStart, aui]);
31
+ }, [unstable_openOnRunStart, aui, setOpen]);
32
32
 
33
33
  return state;
34
34
  };
@@ -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 only when handleScroll confirms we reached bottom.
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) < 1 ||
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
 
@@ -9,7 +9,7 @@ describe("auiV0Encode", () => {
9
9
  role: "assistant",
10
10
  status: { type: "complete", reason: "stop" },
11
11
  metadata: {
12
- unstable_state: undefined,
12
+ unstable_state: null,
13
13
  unstable_annotations: [],
14
14
  unstable_data: [],
15
15
  steps: [],
@@ -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: () => [],
@@ -1,2 +0,0 @@
1
- import { ToolExecutionStatus, useToolInvocations } from "@assistant-ui/core/react";
2
- export { type ToolExecutionStatus, useToolInvocations };
@@ -1,2 +0,0 @@
1
- import { useToolInvocations } from "@assistant-ui/core/react";
2
- export { useToolInvocations };