@assistant-ui/react 0.14.13 → 0.14.15

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 (214) hide show
  1. package/README.md +5 -1
  2. package/dist/client/ExternalThread.d.ts +2 -12
  3. package/dist/client/ExternalThread.d.ts.map +1 -1
  4. package/dist/client/ExternalThread.js +30 -29
  5. package/dist/client/ExternalThread.js.map +1 -1
  6. package/dist/client/InMemoryThreadList.d.ts.map +1 -1
  7. package/dist/client/InMemoryThreadList.js +24 -13
  8. package/dist/client/InMemoryThreadList.js.map +1 -1
  9. package/dist/client/SingleThreadList.d.ts.map +1 -1
  10. package/dist/client/SingleThreadList.js +12 -8
  11. package/dist/client/SingleThreadList.js.map +1 -1
  12. package/dist/context/providers/ThreadViewportProvider.js +1 -1
  13. package/dist/context/providers/ThreadViewportProvider.js.map +1 -1
  14. package/dist/context/react/ThreadViewportContext.js +1 -1
  15. package/dist/context/react/utils/createContextHook.js +1 -1
  16. package/dist/context/react/utils/ensureBinding.js.map +1 -1
  17. package/dist/context/react/utils/useRuntimeState.js +1 -1
  18. package/dist/context/stores/ThreadViewport.js.map +1 -1
  19. package/dist/devtools/DevToolsHooks.js.map +1 -1
  20. package/dist/index.d.ts +4 -4
  21. package/dist/index.js +3 -3
  22. package/dist/legacy-runtime/AssistantRuntimeProvider.js +1 -1
  23. package/dist/legacy-runtime/cloud/auiV0.js +1 -1
  24. package/dist/legacy-runtime/hooks/AssistantContext.js.map +1 -1
  25. package/dist/legacy-runtime/hooks/AttachmentContext.js.map +1 -1
  26. package/dist/legacy-runtime/hooks/ComposerContext.js.map +1 -1
  27. package/dist/legacy-runtime/hooks/MessageContext.js.map +1 -1
  28. package/dist/legacy-runtime/hooks/MessagePartContext.js.map +1 -1
  29. package/dist/legacy-runtime/hooks/ThreadContext.js +1 -1
  30. package/dist/legacy-runtime/hooks/ThreadContext.js.map +1 -1
  31. package/dist/legacy-runtime/hooks/ThreadListItemContext.js.map +1 -1
  32. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js +1 -1
  33. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.d.ts +14 -0
  34. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.d.ts.map +1 -0
  35. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js +101 -0
  36. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js.map +1 -0
  37. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js +1 -1
  38. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
  39. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +13 -2
  40. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
  41. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js +1 -1
  42. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js +1 -1
  43. package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  44. package/dist/mcp-apps/McpAppRenderer.js +7 -7
  45. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  46. package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
  47. package/dist/mcp-apps/McpAppsRemoteHost.js +5 -4
  48. package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
  49. package/dist/mcp-apps/app-frame.d.ts +1 -1
  50. package/dist/mcp-apps/app-frame.d.ts.map +1 -1
  51. package/dist/mcp-apps/app-frame.js +82 -104
  52. package/dist/mcp-apps/app-frame.js.map +1 -1
  53. package/dist/mcp-apps/bridge.d.ts +3 -3
  54. package/dist/mcp-apps/bridge.d.ts.map +1 -1
  55. package/dist/mcp-apps/bridge.js +35 -10
  56. package/dist/mcp-apps/bridge.js.map +1 -1
  57. package/dist/mcp-apps/types.d.ts +2 -12
  58. package/dist/mcp-apps/types.d.ts.map +1 -1
  59. package/dist/mcp-apps/types.js.map +1 -1
  60. package/dist/model-context/frame/useAssistantFrameHost.js +1 -1
  61. package/dist/model-context/makeAssistantVisible.js +1 -1
  62. package/dist/model-context/makeAssistantVisible.js.map +1 -1
  63. package/dist/primitives/actionBar/ActionBarCopy.js +1 -1
  64. package/dist/primitives/actionBar/ActionBarExportMarkdown.js +1 -1
  65. package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
  66. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +1 -1
  67. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +1 -1
  68. package/dist/primitives/actionBar/ActionBarInteractionContext.js +1 -1
  69. package/dist/primitives/actionBar/ActionBarRoot.js +1 -1
  70. package/dist/primitives/actionBar/ActionBarStopSpeaking.js +1 -1
  71. package/dist/primitives/actionBarMore/ActionBarMoreContent.js +1 -1
  72. package/dist/primitives/actionBarMore/ActionBarMoreItem.js +1 -1
  73. package/dist/primitives/actionBarMore/ActionBarMoreRoot.js +1 -1
  74. package/dist/primitives/actionBarMore/ActionBarMoreSeparator.js +1 -1
  75. package/dist/primitives/actionBarMore/ActionBarMoreTrigger.js +1 -1
  76. package/dist/primitives/assistantModal/AssistantModalAnchor.js +1 -1
  77. package/dist/primitives/assistantModal/AssistantModalContent.js +1 -1
  78. package/dist/primitives/assistantModal/AssistantModalRoot.js +1 -1
  79. package/dist/primitives/assistantModal/AssistantModalTrigger.js +1 -1
  80. package/dist/primitives/attachment/AttachmentRemove.js +1 -1
  81. package/dist/primitives/attachment/AttachmentRemove.js.map +1 -1
  82. package/dist/primitives/attachment/AttachmentRoot.js +1 -1
  83. package/dist/primitives/attachment/AttachmentThumb.js +1 -1
  84. package/dist/primitives/branchPicker/BranchPickerRoot.js +1 -1
  85. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +1 -1
  86. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js.map +1 -1
  87. package/dist/primitives/chainOfThought/ChainOfThoughtRoot.js +1 -1
  88. package/dist/primitives/composer/ComposerAddAttachment.js +1 -1
  89. package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
  90. package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -1
  91. package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
  92. package/dist/primitives/composer/ComposerDictationTranscript.js +1 -1
  93. package/dist/primitives/composer/ComposerInput.js +1 -1
  94. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  95. package/dist/primitives/composer/ComposerInputPluginContext.js +1 -1
  96. package/dist/primitives/composer/ComposerQuote.js +1 -1
  97. package/dist/primitives/composer/ComposerQuote.js.map +1 -1
  98. package/dist/primitives/composer/ComposerRoot.js +1 -1
  99. package/dist/primitives/composer/ComposerSend.js +1 -1
  100. package/dist/primitives/composer/ComposerStopDictation.js +1 -1
  101. package/dist/primitives/composer/ComposerStopDictation.js.map +1 -1
  102. package/dist/primitives/composer/trigger/TriggerPopover.js +2 -2
  103. package/dist/primitives/composer/trigger/TriggerPopover.js.map +1 -1
  104. package/dist/primitives/composer/trigger/TriggerPopoverAction.js +1 -1
  105. package/dist/primitives/composer/trigger/TriggerPopoverBack.js +1 -1
  106. package/dist/primitives/composer/trigger/TriggerPopoverCategories.js +1 -1
  107. package/dist/primitives/composer/trigger/TriggerPopoverDirective.js +1 -1
  108. package/dist/primitives/composer/trigger/TriggerPopoverItems.js +1 -1
  109. package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts.map +1 -1
  110. package/dist/primitives/composer/trigger/TriggerPopoverResource.js +8 -7
  111. package/dist/primitives/composer/trigger/TriggerPopoverResource.js.map +1 -1
  112. package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js +1 -1
  113. package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts.map +1 -1
  114. package/dist/primitives/composer/trigger/triggerDetectionResource.js +5 -4
  115. package/dist/primitives/composer/trigger/triggerDetectionResource.js.map +1 -1
  116. package/dist/primitives/composer/trigger/triggerKeyboardResource.d.ts.map +1 -1
  117. package/dist/primitives/composer/trigger/triggerKeyboardResource.js +8 -7
  118. package/dist/primitives/composer/trigger/triggerKeyboardResource.js.map +1 -1
  119. package/dist/primitives/composer/trigger/triggerNavigationResource.d.ts.map +1 -1
  120. package/dist/primitives/composer/trigger/triggerNavigationResource.js +13 -12
  121. package/dist/primitives/composer/trigger/triggerNavigationResource.js.map +1 -1
  122. package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
  123. package/dist/primitives/composer/trigger/triggerSelectionResource.js +7 -6
  124. package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
  125. package/dist/primitives/error/ErrorMessage.js +1 -1
  126. package/dist/primitives/error/ErrorRoot.js +1 -1
  127. package/dist/primitives/message/MessagePartsGrouped.js +1 -1
  128. package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
  129. package/dist/primitives/message/MessageRoot.js +1 -1
  130. package/dist/primitives/message/MessageRoot.js.map +1 -1
  131. package/dist/primitives/messagePart/MessagePartImage.js +1 -1
  132. package/dist/primitives/messagePart/MessagePartText.js +1 -1
  133. package/dist/primitives/queueItem/QueueItemRemove.js +1 -1
  134. package/dist/primitives/queueItem/QueueItemRemove.js.map +1 -1
  135. package/dist/primitives/queueItem/QueueItemSteer.js +1 -1
  136. package/dist/primitives/queueItem/QueueItemSteer.js.map +1 -1
  137. package/dist/primitives/queueItem/QueueItemText.js +1 -1
  138. package/dist/primitives/reasoning/useScrollLock.js +1 -1
  139. package/dist/primitives/reasoning/useScrollLock.js.map +1 -1
  140. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +1 -1
  141. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js.map +1 -1
  142. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +1 -1
  143. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
  144. package/dist/primitives/suggestion/SuggestionDescription.js +1 -1
  145. package/dist/primitives/suggestion/SuggestionTitle.js +1 -1
  146. package/dist/primitives/suggestion/SuggestionTrigger.js +1 -1
  147. package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -1
  148. package/dist/primitives/thread/ThreadRoot.js +1 -1
  149. package/dist/primitives/thread/ThreadScrollToBottom.js +1 -1
  150. package/dist/primitives/thread/ThreadScrollToBottom.js.map +1 -1
  151. package/dist/primitives/thread/ThreadViewport.js +1 -1
  152. package/dist/primitives/thread/ThreadViewport.js.map +1 -1
  153. package/dist/primitives/thread/ThreadViewportFooter.js +1 -1
  154. package/dist/primitives/thread/ThreadViewportFooter.js.map +1 -1
  155. package/dist/primitives/thread/topAnchor/topAnchorTurn.js.map +1 -1
  156. package/dist/primitives/thread/topAnchor/topAnchorUtils.js.map +1 -1
  157. package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js +1 -1
  158. package/dist/primitives/thread/useThreadViewportAutoScroll.js +1 -1
  159. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  160. package/dist/primitives/threadList/ThreadListNew.js +1 -1
  161. package/dist/primitives/threadList/ThreadListRoot.js +1 -1
  162. package/dist/primitives/threadListItem/ThreadListItemRoot.js +1 -1
  163. package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js +1 -1
  164. package/dist/primitives/threadListItemMore/ThreadListItemMoreItem.js +1 -1
  165. package/dist/primitives/threadListItemMore/ThreadListItemMoreSeparator.js +1 -1
  166. package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js +1 -1
  167. package/dist/sandbox-host/SandboxHost.d.ts +50 -0
  168. package/dist/sandbox-host/SandboxHost.d.ts.map +1 -0
  169. package/dist/sandbox-host/SandboxHost.js +85 -0
  170. package/dist/sandbox-host/SandboxHost.js.map +1 -0
  171. package/dist/unstable/useMentionAdapter.d.ts +2 -2
  172. package/dist/unstable/useMentionAdapter.js +2 -2
  173. package/dist/unstable/useMentionAdapter.js.map +1 -1
  174. package/dist/unstable/useSlashCommandAdapter.js +1 -1
  175. package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
  176. package/dist/utils/Primitive.js +1 -1
  177. package/dist/utils/createActionButton.js +1 -1
  178. package/dist/utils/createActionButton.js.map +1 -1
  179. package/dist/utils/hooks/useManagedRef.js +1 -1
  180. package/dist/utils/hooks/useMediaQuery.js +1 -1
  181. package/dist/utils/hooks/useMediaQuery.js.map +1 -1
  182. package/dist/utils/hooks/useOnResizeContent.js +1 -1
  183. package/dist/utils/hooks/useOnScrollToBottom.js +1 -1
  184. package/dist/utils/hooks/useSizeHandle.js +1 -1
  185. package/dist/utils/json/is-json.js.map +1 -1
  186. package/dist/utils/smooth/SmoothContext.js +1 -1
  187. package/dist/utils/smooth/SmoothContext.js.map +1 -1
  188. package/dist/utils/smooth/useSmooth.js +1 -1
  189. package/dist/utils/smooth/useSmooth.js.map +1 -1
  190. package/package.json +21 -20
  191. package/src/client/ExternalThread.ts +484 -515
  192. package/src/client/InMemoryThreadList.ts +154 -142
  193. package/src/client/SingleThreadList.ts +88 -81
  194. package/src/context/providers/ThreadViewportProvider.tsx +2 -2
  195. package/src/index.ts +18 -3
  196. package/src/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.test.ts +426 -0
  197. package/src/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.ts +146 -0
  198. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +16 -1
  199. package/src/mcp-apps/McpAppRenderer.tsx +28 -35
  200. package/src/mcp-apps/McpAppsRemoteHost.ts +25 -24
  201. package/src/mcp-apps/app-frame.tsx +100 -141
  202. package/src/mcp-apps/bridge.test.ts +100 -60
  203. package/src/mcp-apps/bridge.ts +43 -21
  204. package/src/mcp-apps/types.ts +2 -12
  205. package/src/primitives/composer/trigger/TriggerPopover.tsx +1 -1
  206. package/src/primitives/composer/trigger/TriggerPopoverResource.ts +75 -76
  207. package/src/primitives/composer/trigger/triggerDetectionResource.ts +6 -5
  208. package/src/primitives/composer/trigger/triggerKeyboardResource.ts +9 -13
  209. package/src/primitives/composer/trigger/triggerNavigationResource.ts +14 -19
  210. package/src/primitives/composer/trigger/triggerSelectionResource.ts +8 -7
  211. package/src/sandbox-host/SandboxHost.test.tsx +231 -0
  212. package/src/sandbox-host/SandboxHost.tsx +185 -0
  213. package/src/tests/local-runtime-queue.test.tsx +305 -0
  214. package/src/unstable/useMentionAdapter.ts +2 -2
@@ -0,0 +1,305 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { render, act } from "@testing-library/react";
4
+ import type { FC } from "react";
5
+ import { describe, it, expect } from "vitest";
6
+ import { useAui } from "@assistant-ui/store";
7
+ import { AssistantRuntimeProvider } from "../context";
8
+ import { useLocalRuntime } from "../legacy-runtime/runtime-cores/local/useLocalRuntime";
9
+ import type { ChatModelAdapter } from "../legacy-runtime/runtime-cores/local/ChatModelAdapter";
10
+
11
+ const flush = () => new Promise<void>((resolve) => setTimeout(resolve, 0));
12
+
13
+ const createCountingAdapter = () => {
14
+ const releases: Array<() => void> = [];
15
+ let runCount = 0;
16
+ const adapter: ChatModelAdapter = {
17
+ async *run({ abortSignal }) {
18
+ runCount++;
19
+ await new Promise<void>((resolve) => {
20
+ releases.push(resolve);
21
+ abortSignal.addEventListener("abort", () => resolve(), { once: true });
22
+ });
23
+ yield { content: [{ type: "text", text: "done" }] };
24
+ },
25
+ };
26
+ return { adapter, releases, getRunCount: () => runCount };
27
+ };
28
+
29
+ const userTexts = (aui: ReturnType<typeof useAui>) =>
30
+ aui
31
+ .thread()
32
+ .getState()
33
+ .messages.filter((m) => m.role === "user")
34
+ .map((m) =>
35
+ m.content.map((p) => (p.type === "text" ? p.text : "")).join(""),
36
+ );
37
+
38
+ const renderWithRuntime = (adapter: ChatModelAdapter, enableQueue: boolean) => {
39
+ const captured: { aui?: ReturnType<typeof useAui> } = {};
40
+ const Capture: FC = () => {
41
+ captured.aui = useAui();
42
+ return null;
43
+ };
44
+ const App: FC = () => {
45
+ const runtime = useLocalRuntime(adapter, {
46
+ unstable_enableMessageQueue: enableQueue,
47
+ });
48
+ return (
49
+ <AssistantRuntimeProvider runtime={runtime}>
50
+ <Capture />
51
+ </AssistantRuntimeProvider>
52
+ );
53
+ };
54
+ render(<App />);
55
+ return captured.aui!;
56
+ };
57
+
58
+ const send = async (aui: ReturnType<typeof useAui>, text: string) => {
59
+ await act(async () => {
60
+ aui.thread().composer().setText(text);
61
+ aui.thread().composer().send();
62
+ await flush();
63
+ });
64
+ };
65
+
66
+ describe("local runtime message queue", () => {
67
+ it("buffers a send while running and flushes it when the run ends", async () => {
68
+ const { adapter, releases } = createCountingAdapter();
69
+ const aui = renderWithRuntime(adapter, true);
70
+
71
+ await send(aui, "first");
72
+ expect(aui.thread().getState().isRunning).toBe(true);
73
+ expect(aui.thread().getState().capabilities.queue).toBe(true);
74
+
75
+ await send(aui, "second");
76
+ expect(
77
+ aui
78
+ .thread()
79
+ .composer()
80
+ .getState()
81
+ .queue.map((q) => q.prompt),
82
+ ).toEqual(["second"]);
83
+ expect(userTexts(aui)).toEqual(["first"]);
84
+
85
+ await act(async () => {
86
+ releases[0]!();
87
+ await flush();
88
+ await flush();
89
+ });
90
+ expect(aui.thread().composer().getState().queue).toEqual([]);
91
+ expect(userTexts(aui)).toContain("second");
92
+ });
93
+
94
+ it("drains two queued items in separate runs, not all at once", async () => {
95
+ const { adapter, releases, getRunCount } = createCountingAdapter();
96
+ const aui = renderWithRuntime(adapter, true);
97
+
98
+ await send(aui, "first");
99
+ expect(getRunCount()).toBe(1);
100
+
101
+ await send(aui, "a");
102
+ await send(aui, "b");
103
+ expect(aui.thread().composer().getState().queue).toHaveLength(2);
104
+
105
+ await act(async () => {
106
+ releases[0]!();
107
+ await flush();
108
+ await flush();
109
+ });
110
+ expect(getRunCount()).toBe(2);
111
+ expect(
112
+ aui
113
+ .thread()
114
+ .composer()
115
+ .getState()
116
+ .queue.map((q) => q.prompt),
117
+ ).toEqual(["b"]);
118
+ });
119
+
120
+ it("queueItem(index).remove() drops a queued message", async () => {
121
+ const { adapter } = createCountingAdapter();
122
+ const aui = renderWithRuntime(adapter, true);
123
+
124
+ await send(aui, "first");
125
+ await send(aui, "a");
126
+ await send(aui, "b");
127
+ expect(aui.thread().composer().getState().queue).toHaveLength(2);
128
+
129
+ await act(async () => {
130
+ aui.thread().composer().queueItem({ index: 0 }).remove();
131
+ await flush();
132
+ });
133
+ const queue = aui.thread().composer().getState().queue;
134
+ expect(queue).toHaveLength(1);
135
+ expect(queue[0]!.prompt).toBe("b");
136
+ });
137
+
138
+ it("clears the queue when the run is cancelled, without flushing", async () => {
139
+ const { adapter, getRunCount } = createCountingAdapter();
140
+ const aui = renderWithRuntime(adapter, true);
141
+
142
+ await send(aui, "first");
143
+ await send(aui, "a");
144
+ await send(aui, "b");
145
+ expect(aui.thread().composer().getState().queue).toHaveLength(2);
146
+
147
+ await act(async () => {
148
+ aui.thread().cancelRun();
149
+ await flush();
150
+ await flush();
151
+ });
152
+
153
+ expect(aui.thread().composer().getState().queue).toEqual([]);
154
+ // cancelling must not start the next queued message
155
+ expect(getRunCount()).toBe(1);
156
+ });
157
+
158
+ it("applies an edit instead of queuing it, dropping pending items", async () => {
159
+ const { adapter, releases } = createCountingAdapter();
160
+ const aui = renderWithRuntime(adapter, true);
161
+
162
+ await send(aui, "first");
163
+ await act(async () => {
164
+ releases[0]!();
165
+ await flush();
166
+ });
167
+
168
+ // start a second run and queue a message behind it
169
+ await send(aui, "second");
170
+ await send(aui, "queued");
171
+ expect(aui.thread().composer().getState().queue).toHaveLength(1);
172
+
173
+ // edit the first message while the run is in progress
174
+ await act(async () => {
175
+ const message = aui.thread().message({ index: 0 });
176
+ message.composer().beginEdit();
177
+ message.composer().setText("edited");
178
+ message.composer().send();
179
+ await flush();
180
+ });
181
+
182
+ // the edit is applied (branches the thread) and the stale queue is cleared
183
+ expect(aui.thread().composer().getState().queue).toEqual([]);
184
+ });
185
+
186
+ it("buffers a send during a regenerate instead of interrupting it", async () => {
187
+ const { adapter, releases, getRunCount } = createCountingAdapter();
188
+ const aui = renderWithRuntime(adapter, true);
189
+
190
+ await send(aui, "first");
191
+ await act(async () => {
192
+ releases[0]!();
193
+ await flush();
194
+ });
195
+ expect(getRunCount()).toBe(1);
196
+
197
+ // regenerate the assistant message: a run started outside the queue
198
+ await act(async () => {
199
+ aui.thread().message({ index: 1 }).reload();
200
+ await flush();
201
+ });
202
+ expect(getRunCount()).toBe(2);
203
+ expect(aui.thread().getState().isRunning).toBe(true);
204
+
205
+ // sending now must buffer, not interrupt the regenerate
206
+ await act(async () => {
207
+ aui.thread().composer().setText("Y");
208
+ aui.thread().composer().send();
209
+ await flush();
210
+ });
211
+ expect(getRunCount()).toBe(2);
212
+ expect(
213
+ aui
214
+ .thread()
215
+ .composer()
216
+ .getState()
217
+ .queue.map((q) => q.prompt),
218
+ ).toEqual(["Y"]);
219
+ });
220
+
221
+ it("advances exactly once after a failed run, without deadlocking", async () => {
222
+ const releases: Array<() => void> = [];
223
+ let runCount = 0;
224
+ const adapter: ChatModelAdapter = {
225
+ async *run({ abortSignal }) {
226
+ runCount++;
227
+ if (runCount === 2) throw new Error("model boom");
228
+ await new Promise<void>((resolve) => {
229
+ releases.push(resolve);
230
+ abortSignal.addEventListener("abort", () => resolve(), {
231
+ once: true,
232
+ });
233
+ });
234
+ yield { content: [{ type: "text", text: "done" }] };
235
+ },
236
+ };
237
+ const aui = renderWithRuntime(adapter, true);
238
+
239
+ await send(aui, "first");
240
+ await send(aui, "a");
241
+ await send(aui, "b");
242
+ expect(aui.thread().composer().getState().queue).toHaveLength(2);
243
+
244
+ // run 1 settles -> "a" drains (run 2 throws) -> "b" drains (run 3)
245
+ await act(async () => {
246
+ releases[0]!();
247
+ await flush();
248
+ await flush();
249
+ });
250
+ expect(runCount).toBe(3);
251
+ expect(aui.thread().composer().getState().queue).toEqual([]);
252
+
253
+ // "b" is running; a new send must buffer behind it, not interrupt it
254
+ await act(async () => {
255
+ aui.thread().composer().setText("c");
256
+ aui.thread().composer().send();
257
+ await flush();
258
+ });
259
+ expect(runCount).toBe(3);
260
+ expect(
261
+ aui
262
+ .thread()
263
+ .composer()
264
+ .getState()
265
+ .queue.map((q) => q.prompt),
266
+ ).toEqual(["c"]);
267
+ });
268
+
269
+ it("does not expose the queue capability when the flag is off", async () => {
270
+ const { adapter } = createCountingAdapter();
271
+ const aui = renderWithRuntime(adapter, false);
272
+ expect(aui.thread().getState().capabilities.queue).toBe(false);
273
+ });
274
+
275
+ it("tears the queue down when the flag is toggled off at runtime", async () => {
276
+ const { adapter } = createCountingAdapter();
277
+ const captured: { aui?: ReturnType<typeof useAui> } = {};
278
+ const Capture: FC = () => {
279
+ captured.aui = useAui();
280
+ return null;
281
+ };
282
+ const App: FC<{ enabled: boolean }> = ({ enabled }) => {
283
+ const runtime = useLocalRuntime(adapter, {
284
+ unstable_enableMessageQueue: enabled,
285
+ });
286
+ return (
287
+ <AssistantRuntimeProvider runtime={runtime}>
288
+ <Capture />
289
+ </AssistantRuntimeProvider>
290
+ );
291
+ };
292
+
293
+ const { rerender } = render(<App enabled={true} />);
294
+ await act(async () => {
295
+ await flush();
296
+ });
297
+ expect(captured.aui!.thread().getState().capabilities.queue).toBe(true);
298
+
299
+ await act(async () => {
300
+ rerender(<App enabled={false} />);
301
+ await flush();
302
+ });
303
+ expect(captured.aui!.thread().getState().capabilities.queue).toBe(false);
304
+ });
305
+ });
@@ -45,7 +45,7 @@ export type Unstable_UseMentionAdapterOptions = {
45
45
  /** Categorized mentions for drill-down navigation. */
46
46
  readonly categories?: readonly Unstable_MentionCategory[];
47
47
  /**
48
- * How tools registered via `useAssistantTool` integrate.
48
+ * How tools registered in model context integrate.
49
49
  * - `false`: exclude.
50
50
  * - `true`: include (default when no `items`/`categories`; as a category
51
51
  * if `categories` is set, flat otherwise).
@@ -75,7 +75,7 @@ export type Unstable_MentionDirective = {
75
75
  * @deprecated Under active development and might change without notice.
76
76
  *
77
77
  * Creates a spreadable `{ adapter, directive }` bundle for `@` mentions.
78
- * Supports tools registered via `useAssistantTool`, explicit items, or both —
78
+ * Supports tools registered in model context, explicit items, or both —
79
79
  * flat or categorized.
80
80
  *
81
81
  * @example