@assistant-ui/react 0.15.24 → 0.15.26

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 (204) hide show
  1. package/dist/hooks/useToolCallElapsed.d.ts +3 -1
  2. package/dist/hooks/useToolCallElapsed.d.ts.map +1 -1
  3. package/dist/hooks/useToolCallElapsed.js +8 -8
  4. package/dist/hooks/useToolCallElapsed.js.map +1 -1
  5. package/dist/index.d.ts +39 -7
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +3 -3
  8. package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  9. package/dist/mcp-apps/McpAppRenderer.js +3 -2
  10. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  11. package/dist/mcp-apps/app-frame.d.ts.map +1 -1
  12. package/dist/mcp-apps/app-frame.js +56 -46
  13. package/dist/mcp-apps/app-frame.js.map +1 -1
  14. package/dist/mcp-apps/csp.d.ts +4 -0
  15. package/dist/mcp-apps/csp.d.ts.map +1 -0
  16. package/dist/mcp-apps/csp.js +93 -0
  17. package/dist/mcp-apps/csp.js.map +1 -0
  18. package/dist/mcp-apps/types.d.ts +1 -0
  19. package/dist/mcp-apps/types.d.ts.map +1 -1
  20. package/dist/mcp-apps/types.js.map +1 -1
  21. package/dist/model-context/makeAssistantVisible.d.ts +1 -0
  22. package/dist/model-context/makeAssistantVisible.d.ts.map +1 -1
  23. package/dist/model-context/makeAssistantVisible.js +31 -12
  24. package/dist/model-context/makeAssistantVisible.js.map +1 -1
  25. package/dist/primitives/assistantModal/AssistantModalRoot.d.ts +1 -0
  26. package/dist/primitives/assistantModal/AssistantModalRoot.d.ts.map +1 -1
  27. package/dist/primitives/assistantModal/AssistantModalRoot.js.map +1 -1
  28. package/dist/primitives/attachment.d.ts +3 -1
  29. package/dist/primitives/attachment.d.ts.map +1 -1
  30. package/dist/primitives/attachment.js.map +1 -1
  31. package/dist/primitives/composer/ComposerCancel.d.ts +5 -1
  32. package/dist/primitives/composer/ComposerCancel.d.ts.map +1 -1
  33. package/dist/primitives/composer/ComposerCancel.js +14 -1
  34. package/dist/primitives/composer/ComposerCancel.js.map +1 -1
  35. package/dist/primitives/composer/ComposerInput.d.ts +15 -1
  36. package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
  37. package/dist/primitives/composer/ComposerInput.js +5 -4
  38. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  39. package/dist/primitives/composer/ComposerRoot.d.ts.map +1 -1
  40. package/dist/primitives/composer/ComposerRoot.js +24 -19
  41. package/dist/primitives/composer/ComposerRoot.js.map +1 -1
  42. package/dist/primitives/composer/composerInputSelector.d.ts +3 -0
  43. package/dist/primitives/composer/composerInputSelector.d.ts.map +1 -0
  44. package/dist/primitives/composer/composerInputSelector.js +7 -0
  45. package/dist/primitives/composer/composerInputSelector.js.map +1 -0
  46. package/dist/primitives/composer/trigger/TriggerPopover.d.ts +11 -11
  47. package/dist/primitives/composer/trigger/TriggerPopover.d.ts.map +1 -1
  48. package/dist/primitives/composer/trigger/TriggerPopover.js +62 -60
  49. package/dist/primitives/composer/trigger/TriggerPopover.js.map +1 -1
  50. package/dist/primitives/composer/trigger/TriggerPopoverAction.d.ts +8 -8
  51. package/dist/primitives/composer/trigger/TriggerPopoverAction.d.ts.map +1 -1
  52. package/dist/primitives/composer/trigger/TriggerPopoverAction.js +6 -6
  53. package/dist/primitives/composer/trigger/TriggerPopoverAction.js.map +1 -1
  54. package/dist/primitives/composer/trigger/TriggerPopoverCategories.d.ts +3 -3
  55. package/dist/primitives/composer/trigger/TriggerPopoverCategories.d.ts.map +1 -1
  56. package/dist/primitives/composer/trigger/TriggerPopoverCategories.js.map +1 -1
  57. package/dist/primitives/composer/trigger/TriggerPopoverDirective.d.ts +9 -9
  58. package/dist/primitives/composer/trigger/TriggerPopoverDirective.d.ts.map +1 -1
  59. package/dist/primitives/composer/trigger/TriggerPopoverDirective.js +7 -7
  60. package/dist/primitives/composer/trigger/TriggerPopoverDirective.js.map +1 -1
  61. package/dist/primitives/composer/trigger/TriggerPopoverItems.d.ts +5 -5
  62. package/dist/primitives/composer/trigger/TriggerPopoverItems.d.ts.map +1 -1
  63. package/dist/primitives/composer/trigger/TriggerPopoverItems.js.map +1 -1
  64. package/dist/primitives/composer/trigger/TriggerPopoverRootContext.d.ts +8 -8
  65. package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js +8 -8
  66. package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js.map +1 -1
  67. package/dist/primitives/composer/trigger/index.d.ts +1 -1
  68. package/dist/primitives/composer/useComposerCancelWithFocus.d.ts +2 -0
  69. package/dist/primitives/composer/useComposerCancelWithFocus.d.ts.map +1 -0
  70. package/dist/primitives/composer/useComposerCancelWithFocus.js +42 -0
  71. package/dist/primitives/composer/useComposerCancelWithFocus.js.map +1 -0
  72. package/dist/primitives/composer.d.ts +55 -15
  73. package/dist/primitives/composer.d.ts.map +1 -1
  74. package/dist/primitives/composer.js +8 -1
  75. package/dist/primitives/composer.js.map +1 -1
  76. package/dist/primitives/message/MessagePartsGrouped.d.ts +8 -1
  77. package/dist/primitives/message/MessagePartsGrouped.d.ts.map +1 -1
  78. package/dist/primitives/message/MessagePartsGrouped.js +58 -278
  79. package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
  80. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.d.ts.map +1 -1
  81. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +5 -2
  82. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
  83. package/dist/primitives/thread/ThreadLoadEarlier.d.ts +19 -0
  84. package/dist/primitives/thread/ThreadLoadEarlier.d.ts.map +1 -0
  85. package/dist/primitives/thread/ThreadLoadEarlier.js +36 -0
  86. package/dist/primitives/thread/ThreadLoadEarlier.js.map +1 -0
  87. package/dist/primitives/thread/ThreadMessages.d.ts +1 -1
  88. package/dist/primitives/thread/ThreadMessages.d.ts.map +1 -1
  89. package/dist/primitives/thread/ThreadMessages.js +2 -2
  90. package/dist/primitives/thread/ThreadViewport.d.ts.map +1 -1
  91. package/dist/primitives/thread/ThreadViewport.js +4 -3
  92. package/dist/primitives/thread/ThreadViewport.js.map +1 -1
  93. package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js +2 -2
  94. package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js.map +1 -1
  95. package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
  96. package/dist/primitives/thread/useThreadViewportAutoScroll.js +195 -17
  97. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  98. package/dist/primitives/thread.d.ts +2 -0
  99. package/dist/primitives/thread.d.ts.map +1 -1
  100. package/dist/primitives/thread.js +5 -2
  101. package/dist/primitives/thread.js.map +1 -1
  102. package/dist/sandbox-host/SandboxHost.d.ts.map +1 -1
  103. package/dist/sandbox-host/SandboxHost.js +9 -3
  104. package/dist/sandbox-host/SandboxHost.js.map +1 -1
  105. package/dist/unstable/useComposerInput.d.ts +7 -4
  106. package/dist/unstable/useComposerInput.d.ts.map +1 -1
  107. package/dist/unstable/useComposerInput.js +4 -4
  108. package/dist/unstable/useComposerInput.js.map +1 -1
  109. package/dist/unstable/useComposerInputHistory.d.ts +3 -2
  110. package/dist/unstable/useComposerInputHistory.d.ts.map +1 -1
  111. package/dist/unstable/useComposerInputHistory.js +2 -2
  112. package/dist/unstable/useComposerInputHistory.js.map +1 -1
  113. package/dist/unstable/useLiveCompletionAdapter.d.ts +3 -2
  114. package/dist/unstable/useLiveCompletionAdapter.d.ts.map +1 -1
  115. package/dist/unstable/useLiveCompletionAdapter.js +8 -7
  116. package/dist/unstable/useLiveCompletionAdapter.js.map +1 -1
  117. package/dist/unstable/useMentionAdapter.d.ts +12 -3
  118. package/dist/unstable/useMentionAdapter.d.ts.map +1 -1
  119. package/dist/unstable/useMentionAdapter.js +2 -2
  120. package/dist/unstable/useMentionAdapter.js.map +1 -1
  121. package/dist/unstable/useMessageStallDetection.d.ts +4 -2
  122. package/dist/unstable/useMessageStallDetection.d.ts.map +1 -1
  123. package/dist/unstable/useMessageStallDetection.js +12 -8
  124. package/dist/unstable/useMessageStallDetection.js.map +1 -1
  125. package/dist/unstable/useSlashCommandAdapter.d.ts +5 -2
  126. package/dist/unstable/useSlashCommandAdapter.d.ts.map +1 -1
  127. package/dist/unstable/useSlashCommandAdapter.js +5 -4
  128. package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
  129. package/dist/unstable/webmcp/useWebMcpProvider.d.ts +4 -0
  130. package/dist/unstable/webmcp/useWebMcpProvider.d.ts.map +1 -1
  131. package/dist/unstable/webmcp/useWebMcpProvider.js +2 -0
  132. package/dist/unstable/webmcp/useWebMcpProvider.js.map +1 -1
  133. package/dist/utils/getSelectionMessageId.d.ts.map +1 -1
  134. package/dist/utils/getSelectionMessageId.js +31 -8
  135. package/dist/utils/getSelectionMessageId.js.map +1 -1
  136. package/dist/utils/hooks/useOnResizeContent.d.ts.map +1 -1
  137. package/dist/utils/hooks/useOnResizeContent.js +2 -22
  138. package/dist/utils/hooks/useOnResizeContent.js.map +1 -1
  139. package/dist/utils/smooth/useSmooth.js +6 -6
  140. package/dist/utils/smooth/useSmooth.js.map +1 -1
  141. package/dist/utils/useIsomorphicLayoutEffect.d.ts +3 -0
  142. package/dist/utils/useIsomorphicLayoutEffect.d.ts.map +1 -0
  143. package/dist/utils/useIsomorphicLayoutEffect.js +6 -0
  144. package/dist/utils/useIsomorphicLayoutEffect.js.map +1 -0
  145. package/package.json +12 -11
  146. package/src/hooks/useToolCallElapsed.test.tsx +37 -0
  147. package/src/hooks/useToolCallElapsed.ts +10 -7
  148. package/src/index.ts +47 -5
  149. package/src/mcp-apps/McpAppRenderer.tsx +3 -9
  150. package/src/mcp-apps/app-frame.test.tsx +38 -0
  151. package/src/mcp-apps/app-frame.tsx +5 -8
  152. package/src/mcp-apps/csp.test.ts +112 -0
  153. package/src/mcp-apps/csp.ts +124 -0
  154. package/src/mcp-apps/types.ts +1 -0
  155. package/src/model-context/frame/AssistantFrame.test.ts +45 -0
  156. package/src/model-context/frame/SPEC_AssistantFrame.md +1 -0
  157. package/src/model-context/makeAssistantVisible.test.tsx +315 -5
  158. package/src/model-context/makeAssistantVisible.tsx +43 -7
  159. package/src/primitives/assistantModal/AssistantModalRoot.tsx +1 -0
  160. package/src/primitives/attachment.ts +4 -1
  161. package/src/primitives/composer/ComposerCancel.focus.test.tsx +307 -0
  162. package/src/primitives/composer/ComposerCancel.ts +8 -1
  163. package/src/primitives/composer/ComposerInput.tsx +20 -2
  164. package/src/primitives/composer/ComposerRoot.tsx +6 -5
  165. package/src/primitives/composer/composerInputSelector.ts +4 -0
  166. package/src/primitives/composer/trigger/TriggerPopover.tsx +11 -11
  167. package/src/primitives/composer/trigger/TriggerPopoverAction.tsx +10 -13
  168. package/src/primitives/composer/trigger/TriggerPopoverCategories.tsx +2 -2
  169. package/src/primitives/composer/trigger/TriggerPopoverDirective.tsx +11 -14
  170. package/src/primitives/composer/trigger/TriggerPopoverExports.test.ts +61 -0
  171. package/src/primitives/composer/trigger/TriggerPopoverItems.tsx +3 -3
  172. package/src/primitives/composer/trigger/TriggerPopoverRootContext.tsx +8 -8
  173. package/src/primitives/composer/useComposerCancelWithFocus.ts +57 -0
  174. package/src/primitives/composer.ts +70 -15
  175. package/src/primitives/message/MessagePartsGrouped.test.tsx +106 -2
  176. package/src/primitives/message/MessagePartsGrouped.tsx +36 -134
  177. package/src/primitives/message/MessageRoot.test.tsx +125 -2
  178. package/src/primitives/selectionToolbar/SelectionToolbarRoot.test.tsx +20 -12
  179. package/src/primitives/selectionToolbar/SelectionToolbarRoot.tsx +6 -2
  180. package/src/primitives/thread/ThreadLoadEarlier.tsx +35 -0
  181. package/src/primitives/thread/ThreadMessages.tsx +1 -0
  182. package/src/primitives/thread/ThreadViewport.tsx +3 -3
  183. package/src/primitives/thread/topAnchor/useTopAnchorReserve.ts +2 -2
  184. package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +922 -68
  185. package/src/primitives/thread/useThreadViewportAutoScroll.ts +330 -56
  186. package/src/primitives/thread.ts +2 -0
  187. package/src/sandbox-host/SandboxHost.test.tsx +34 -0
  188. package/src/sandbox-host/SandboxHost.tsx +11 -12
  189. package/src/tests/toolCallTiming.test.tsx +19 -2
  190. package/src/unstable/useComposerInput.ts +7 -4
  191. package/src/unstable/useComposerInputHistory.ts +3 -2
  192. package/src/unstable/useLiveCompletionAdapter.ts +9 -14
  193. package/src/unstable/useMentionAdapter.ts +12 -3
  194. package/src/unstable/useMessageStallDetection.test.tsx +40 -0
  195. package/src/unstable/useMessageStallDetection.ts +14 -8
  196. package/src/unstable/useSlashCommandAdapter.ts +8 -4
  197. package/src/unstable/webmcp/useWebMcpProvider.ts +4 -0
  198. package/src/utils/getSelectionMessageId.test.ts +231 -0
  199. package/src/utils/getSelectionMessageId.ts +45 -7
  200. package/src/utils/hooks/useOnResizeContent.ts +2 -29
  201. package/src/utils/smooth/useSmooth.test.tsx +16 -5
  202. package/src/utils/smooth/useSmooth.ts +7 -7
  203. package/src/utils/useIsomorphicLayoutEffect.test.tsx +21 -0
  204. package/src/utils/useIsomorphicLayoutEffect.ts +9 -0
@@ -0,0 +1,61 @@
1
+ import { describe, expect, expectTypeOf, it } from "vitest";
2
+ import * as ComposerPrimitive from "../../composer";
3
+ import {
4
+ defaultDirectiveFormatter,
5
+ unstable_defaultDirectiveFormatter,
6
+ type DirectiveFormatter,
7
+ type TriggerAdapter,
8
+ type TriggerCategory,
9
+ type TriggerItem,
10
+ type Unstable_DirectiveFormatter,
11
+ type Unstable_TriggerBehavior,
12
+ type Unstable_TriggerItem,
13
+ } from "../../../index";
14
+ import type { TriggerBehavior } from "./triggerSelectionResource";
15
+
16
+ describe("stable trigger popover exports", () => {
17
+ it("keeps every old composer member as an alias of its stable member", () => {
18
+ expect(ComposerPrimitive.Unstable_TriggerPopover).toBe(
19
+ ComposerPrimitive.TriggerPopover,
20
+ );
21
+ expect(ComposerPrimitive.Unstable_TriggerPopover.Directive).toBe(
22
+ ComposerPrimitive.TriggerPopover.Directive,
23
+ );
24
+ expect(ComposerPrimitive.Unstable_TriggerPopover.Action).toBe(
25
+ ComposerPrimitive.TriggerPopover.Action,
26
+ );
27
+ expect(ComposerPrimitive.Unstable_TriggerPopoverRoot).toBe(
28
+ ComposerPrimitive.TriggerPopoverRoot,
29
+ );
30
+ expect(ComposerPrimitive.Unstable_TriggerPopoverCategories).toBe(
31
+ ComposerPrimitive.TriggerPopoverCategories,
32
+ );
33
+ expect(ComposerPrimitive.Unstable_TriggerPopoverCategoryItem).toBe(
34
+ ComposerPrimitive.TriggerPopoverCategoryItem,
35
+ );
36
+ expect(ComposerPrimitive.Unstable_TriggerPopoverItems).toBe(
37
+ ComposerPrimitive.TriggerPopoverItems,
38
+ );
39
+ expect(ComposerPrimitive.Unstable_TriggerPopoverItem).toBe(
40
+ ComposerPrimitive.TriggerPopoverItem,
41
+ );
42
+ expect(ComposerPrimitive.Unstable_TriggerPopoverBack).toBe(
43
+ ComposerPrimitive.TriggerPopoverBack,
44
+ );
45
+ });
46
+
47
+ it("keeps the old members' namespaced prop types", () => {
48
+ expectTypeOf<ComposerPrimitive.Unstable_TriggerPopoverItem.Props>().toEqualTypeOf<ComposerPrimitive.TriggerPopoverItem.Props>();
49
+ expectTypeOf<ComposerPrimitive.Unstable_TriggerPopoverBack.Element>().toEqualTypeOf<ComposerPrimitive.TriggerPopoverBack.Element>();
50
+ });
51
+
52
+ it("exports the stable adapter, item, category and formatter names beside the old ones", () => {
53
+ expect(unstable_defaultDirectiveFormatter).toBe(defaultDirectiveFormatter);
54
+ expectTypeOf<Unstable_TriggerItem>().toEqualTypeOf<TriggerItem>();
55
+ expectTypeOf<Unstable_DirectiveFormatter>().toEqualTypeOf<DirectiveFormatter>();
56
+ expectTypeOf<TriggerAdapter["categories"]>().returns.toEqualTypeOf<
57
+ readonly TriggerCategory[]
58
+ >();
59
+ expectTypeOf<Unstable_TriggerBehavior>().toEqualTypeOf<TriggerBehavior>();
60
+ });
61
+ });
@@ -10,7 +10,7 @@ import {
10
10
  } from "react";
11
11
  import { composeEventHandlers } from "radix-ui/internal";
12
12
  import { useTriggerPopoverScopeContext } from "./TriggerPopover";
13
- import type { Unstable_TriggerItem } from "@assistant-ui/core";
13
+ import type { TriggerItem } from "@assistant-ui/core";
14
14
 
15
15
  export namespace ComposerPrimitiveTriggerPopoverItems {
16
16
  export type Element = ComponentRef<typeof Primitive.div>;
@@ -18,7 +18,7 @@ export namespace ComposerPrimitiveTriggerPopoverItems {
18
18
  ComponentPropsWithoutRef<typeof Primitive.div>,
19
19
  "children"
20
20
  > & {
21
- children: (items: readonly Unstable_TriggerItem[]) => ReactNode;
21
+ children: (items: readonly TriggerItem[]) => ReactNode;
22
22
  };
23
23
  }
24
24
 
@@ -53,7 +53,7 @@ ComposerPrimitiveTriggerPopoverItems.displayName =
53
53
  export namespace ComposerPrimitiveTriggerPopoverItem {
54
54
  export type Element = ComponentRef<typeof Primitive.button>;
55
55
  export type Props = ComponentPropsWithoutRef<typeof Primitive.button> & {
56
- item: Unstable_TriggerItem;
56
+ item: TriggerItem;
57
57
  index?: number | undefined;
58
58
  };
59
59
  }
@@ -294,21 +294,21 @@ const TriggerPopoverRootInner: FC<
294
294
  *
295
295
  * @example
296
296
  * ```tsx
297
- * <ComposerPrimitive.Unstable_TriggerPopoverRoot>
298
- * <ComposerPrimitive.Unstable_TriggerPopover char="@" adapter={mentionAdapter}>
299
- * <ComposerPrimitive.Unstable_TriggerPopover.Directive formatter={formatter} />
297
+ * <ComposerPrimitive.TriggerPopoverRoot>
298
+ * <ComposerPrimitive.TriggerPopover char="@" adapter={mentionAdapter}>
299
+ * <ComposerPrimitive.TriggerPopover.Directive formatter={formatter} />
300
300
  * ...
301
- * </ComposerPrimitive.Unstable_TriggerPopover>
301
+ * </ComposerPrimitive.TriggerPopover>
302
302
  *
303
- * <ComposerPrimitive.Unstable_TriggerPopover char="/" adapter={slashAdapter}>
304
- * <ComposerPrimitive.Unstable_TriggerPopover.Action onExecute={handler} />
303
+ * <ComposerPrimitive.TriggerPopover char="/" adapter={slashAdapter}>
304
+ * <ComposerPrimitive.TriggerPopover.Action onExecute={handler} />
305
305
  * ...
306
- * </ComposerPrimitive.Unstable_TriggerPopover>
306
+ * </ComposerPrimitive.TriggerPopover>
307
307
  *
308
308
  * <ComposerPrimitive.Root>
309
309
  * <ComposerPrimitive.Input />
310
310
  * </ComposerPrimitive.Root>
311
- * </ComposerPrimitive.Unstable_TriggerPopoverRoot>
311
+ * </ComposerPrimitive.TriggerPopoverRoot>
312
312
  * ```
313
313
  */
314
314
  export const ComposerPrimitiveTriggerPopoverRoot: FC<
@@ -0,0 +1,57 @@
1
+ "use client";
2
+
3
+ import { useCallback } from "react";
4
+ import { useAui } from "@assistant-ui/store";
5
+ import { useThreadRootElementRef } from "../thread/ThreadRootElementContext";
6
+ import { COMPOSER_INPUT_SELECTOR } from "./composerInputSelector";
7
+
8
+ const COMPOSER_SELECTOR = "[data-aui-composer-type]";
9
+ const THREAD_SELECTOR = "[data-aui-thread-root]";
10
+
11
+ export const useComposerCancelWithFocus = (cancel: () => void) => {
12
+ const aui = useAui();
13
+ const threadRootRef = useThreadRootElementRef();
14
+
15
+ const cancelWithFocus = useCallback(
16
+ (source: HTMLElement | null) => {
17
+ const root = threadRootRef?.current;
18
+ const doc = source?.ownerDocument;
19
+ const active = doc?.activeElement;
20
+ const composer = source?.closest(COMPOSER_SELECTOR);
21
+ const shouldRestore =
22
+ aui.composer.getState().type === "edit" &&
23
+ root &&
24
+ active &&
25
+ composer?.getAttribute("data-aui-composer-type") === "edit" &&
26
+ composer.contains(active) &&
27
+ composer.closest(THREAD_SELECTOR) === root;
28
+ const candidates = shouldRestore
29
+ ? Array.from(
30
+ root.querySelectorAll<HTMLElement>(COMPOSER_INPUT_SELECTOR),
31
+ ).filter(
32
+ (input) =>
33
+ input.closest(THREAD_SELECTOR) === root &&
34
+ input
35
+ .closest(COMPOSER_SELECTOR)
36
+ ?.getAttribute("data-aui-composer-type") === "thread",
37
+ )
38
+ : [];
39
+
40
+ cancel();
41
+
42
+ if (!doc || !active || !shouldRestore) return;
43
+ if (doc.activeElement !== active && doc.activeElement !== doc.body)
44
+ return;
45
+
46
+ for (const input of candidates) {
47
+ if (!input.isConnected || input.closest("[hidden], [inert]")) continue;
48
+ input.focus({ preventScroll: true });
49
+ if (doc.activeElement !== active && doc.activeElement !== doc.body)
50
+ break;
51
+ }
52
+ },
53
+ [aui, cancel, threadRootRef],
54
+ );
55
+
56
+ return cancelWithFocus;
57
+ };
@@ -1,3 +1,5 @@
1
+ import type { TriggerMatch, TriggerMatcher } from "./composer/trigger";
2
+
1
3
  export { ComposerPrimitiveRoot as Root } from "./composer/ComposerRoot";
2
4
  export { ComposerPrimitiveInput as Input } from "./composer/ComposerInput";
3
5
  export { ComposerPrimitiveSend as Send } from "./composer/ComposerSend";
@@ -15,18 +17,71 @@ export { ComposerPrimitiveQuoteText as QuoteText } from "./composer/ComposerQuot
15
17
  export { ComposerPrimitiveQuoteDismiss as QuoteDismiss } from "./composer/ComposerQuote";
16
18
  export { ComposerPrimitiveQueue as Queue } from "./composer/ComposerQueue";
17
19
 
18
- export { ComposerPrimitiveTriggerPopover as Unstable_TriggerPopover } from "./composer/trigger";
19
- export { ComposerPrimitiveTriggerPopoverRoot as Unstable_TriggerPopoverRoot } from "./composer/trigger";
20
- export { ComposerPrimitiveTriggerPopoverCategories as Unstable_TriggerPopoverCategories } from "./composer/trigger";
21
- export { ComposerPrimitiveTriggerPopoverCategoryItem as Unstable_TriggerPopoverCategoryItem } from "./composer/trigger";
22
- export { ComposerPrimitiveTriggerPopoverItems as Unstable_TriggerPopoverItems } from "./composer/trigger";
23
- export { ComposerPrimitiveTriggerPopoverItem as Unstable_TriggerPopoverItem } from "./composer/trigger";
24
- export { ComposerPrimitiveTriggerPopoverBack as Unstable_TriggerPopoverBack } from "./composer/trigger";
25
- export { useTriggerPopoverRootContext as unstable_useTriggerPopoverRootContext } from "./composer/trigger";
26
- export { useTriggerPopoverRootContextOptional as unstable_useTriggerPopoverRootContextOptional } from "./composer/trigger";
27
- export { useTriggerPopoverScopeContext as unstable_useTriggerPopoverScopeContext } from "./composer/trigger";
28
- export { useTriggerPopoverScopeContextOptional as unstable_useTriggerPopoverScopeContextOptional } from "./composer/trigger";
29
- export { useTriggerPopoverTriggers as unstable_useTriggerPopoverTriggers } from "./composer/trigger";
30
- export { useTriggerPopoverTriggersOptional as unstable_useTriggerPopoverTriggersOptional } from "./composer/trigger";
31
- export type { RegisteredTrigger as Unstable_RegisteredTrigger } from "./composer/trigger";
32
- export type { TriggerMatcher as Unstable_TriggerMatcher } from "./composer/trigger";
20
+ export { ComposerPrimitiveTriggerPopover as TriggerPopover } from "./composer/trigger";
21
+ export {
22
+ /** @deprecated Use `ComposerPrimitive.TriggerPopover` instead. */
23
+ ComposerPrimitiveTriggerPopover as Unstable_TriggerPopover,
24
+ } from "./composer/trigger";
25
+ export { ComposerPrimitiveTriggerPopoverRoot as TriggerPopoverRoot } from "./composer/trigger";
26
+ export {
27
+ /** @deprecated Use `ComposerPrimitive.TriggerPopoverRoot` instead. */
28
+ ComposerPrimitiveTriggerPopoverRoot as Unstable_TriggerPopoverRoot,
29
+ } from "./composer/trigger";
30
+ export { ComposerPrimitiveTriggerPopoverCategories as TriggerPopoverCategories } from "./composer/trigger";
31
+ export {
32
+ /** @deprecated Use `ComposerPrimitive.TriggerPopoverCategories` instead. */
33
+ ComposerPrimitiveTriggerPopoverCategories as Unstable_TriggerPopoverCategories,
34
+ } from "./composer/trigger";
35
+ export { ComposerPrimitiveTriggerPopoverCategoryItem as TriggerPopoverCategoryItem } from "./composer/trigger";
36
+ export {
37
+ /** @deprecated Use `ComposerPrimitive.TriggerPopoverCategoryItem` instead. */
38
+ ComposerPrimitiveTriggerPopoverCategoryItem as Unstable_TriggerPopoverCategoryItem,
39
+ } from "./composer/trigger";
40
+ export { ComposerPrimitiveTriggerPopoverItems as TriggerPopoverItems } from "./composer/trigger";
41
+ export {
42
+ /** @deprecated Use `ComposerPrimitive.TriggerPopoverItems` instead. */
43
+ ComposerPrimitiveTriggerPopoverItems as Unstable_TriggerPopoverItems,
44
+ } from "./composer/trigger";
45
+ export { ComposerPrimitiveTriggerPopoverItem as TriggerPopoverItem } from "./composer/trigger";
46
+ export {
47
+ /** @deprecated Use `ComposerPrimitive.TriggerPopoverItem` instead. */
48
+ ComposerPrimitiveTriggerPopoverItem as Unstable_TriggerPopoverItem,
49
+ } from "./composer/trigger";
50
+ export { ComposerPrimitiveTriggerPopoverBack as TriggerPopoverBack } from "./composer/trigger";
51
+ export {
52
+ /** @deprecated Use `ComposerPrimitive.TriggerPopoverBack` instead. */
53
+ ComposerPrimitiveTriggerPopoverBack as Unstable_TriggerPopoverBack,
54
+ } from "./composer/trigger";
55
+ export {
56
+ /** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */
57
+ useTriggerPopoverRootContext as unstable_useTriggerPopoverRootContext,
58
+ } from "./composer/trigger";
59
+ export {
60
+ /** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */
61
+ useTriggerPopoverRootContextOptional as unstable_useTriggerPopoverRootContextOptional,
62
+ } from "./composer/trigger";
63
+ export {
64
+ /** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */
65
+ useTriggerPopoverScopeContext as unstable_useTriggerPopoverScopeContext,
66
+ } from "./composer/trigger";
67
+ export {
68
+ /** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */
69
+ useTriggerPopoverScopeContextOptional as unstable_useTriggerPopoverScopeContextOptional,
70
+ } from "./composer/trigger";
71
+ export {
72
+ /** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */
73
+ useTriggerPopoverTriggers as unstable_useTriggerPopoverTriggers,
74
+ } from "./composer/trigger";
75
+ export {
76
+ /** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */
77
+ useTriggerPopoverTriggersOptional as unstable_useTriggerPopoverTriggersOptional,
78
+ } from "./composer/trigger";
79
+ export type {
80
+ /** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */
81
+ RegisteredTrigger as Unstable_RegisteredTrigger,
82
+ } from "./composer/trigger";
83
+ export type { TriggerMatch, TriggerMatcher } from "./composer/trigger";
84
+ /** @deprecated Use `TriggerMatcher` instead. */
85
+ export type Unstable_TriggerMatcher = TriggerMatcher;
86
+ /** @deprecated Use `TriggerMatch` instead. */
87
+ export type Unstable_TriggerMatch = TriggerMatch;
@@ -12,10 +12,12 @@ import { describe, expect, it } from "vitest";
12
12
  import type { ThreadMessageLike } from "@assistant-ui/core";
13
13
  import {
14
14
  AssistantRuntimeProvider,
15
+ Tools,
15
16
  useAssistantDataUI,
16
17
  useExternalStoreRuntime,
17
18
  } from "@assistant-ui/core/react";
18
- import { useAui } from "@assistant-ui/store";
19
+ import { AuiConfig, useAui } from "@assistant-ui/store";
20
+ import { resource } from "@assistant-ui/tap";
19
21
  import { ThreadPrimitiveMessageByIndex } from "../thread/ThreadMessages";
20
22
  import {
21
23
  type MessagePrimitiveUnstable_PartsGrouped,
@@ -48,6 +50,11 @@ const partsMessage =
48
50
  const Named = () => <b>named</b>;
49
51
  const Fallback = () => <i>fallback</i>;
50
52
  const GlobalFallback = () => <b>global-fallback</b>;
53
+ const Mcp = () => <b>mcp</b>;
54
+ const McpApp = resource(function McpApp() {
55
+ return { render: Mcp };
56
+ });
57
+ const mcpConfig = AuiConfig({ tools: Tools({ mcpApp: McpApp() }) });
51
58
 
52
59
  const RegisterFallbackDataUI: FC<{ render: typeof GlobalFallback }> = ({
53
60
  render,
@@ -69,11 +76,13 @@ const Example = ({
69
76
  content,
70
77
  Message: MessageComponent = Message,
71
78
  extra,
79
+ config,
72
80
  isRunning = false,
73
81
  }: {
74
82
  content: ThreadMessageLike["content"];
75
83
  Message?: FC;
76
84
  extra?: ReactNode;
85
+ config?: AuiConfig;
77
86
  isRunning?: boolean;
78
87
  }) => {
79
88
  const messages: ThreadMessageLike[] = [
@@ -86,7 +95,10 @@ const Example = ({
86
95
  onNew: async () => {},
87
96
  });
88
97
  return (
89
- <AssistantRuntimeProvider runtime={runtime}>
98
+ <AssistantRuntimeProvider
99
+ runtime={runtime}
100
+ {...(config === undefined ? {} : { config })}
101
+ >
90
102
  {extra}
91
103
  <ThreadPrimitiveMessageByIndex
92
104
  index={0}
@@ -439,6 +451,17 @@ describe("MessagePrimitive.Unstable_PartsGroupedByParentId", () => {
439
451
  },
440
452
  );
441
453
 
454
+ it("falls back to the default text component for an undefined slot", () => {
455
+ render(
456
+ <Example
457
+ content={[{ type: "text", text: "hello" }]}
458
+ Message={partsMessage({ Text: undefined })}
459
+ />,
460
+ );
461
+
462
+ expect(screen.getByText("hello")).toBeTruthy();
463
+ });
464
+
442
465
  it("renders tool and data UIs registered under an inherited name", () => {
443
466
  const { container } = render(
444
467
  <Example
@@ -461,6 +484,87 @@ describe("MessagePrimitive.Unstable_PartsGroupedByParentId", () => {
461
484
  expect(container.innerHTML).toBe("<b>named</b><b>named</b>");
462
485
  });
463
486
 
487
+ it("renders tools.mcpApp for a tool call with a ui:// resource", () => {
488
+ const { container } = render(
489
+ <Example
490
+ content={[
491
+ {
492
+ type: "tool-call",
493
+ toolCallId: "call",
494
+ toolName: "show_chart",
495
+ args: {},
496
+ mcp: { app: { resourceUri: "ui://chart" } },
497
+ },
498
+ ]}
499
+ Message={partsMessage({ tools: { Fallback } })}
500
+ config={mcpConfig}
501
+ />,
502
+ );
503
+
504
+ expect(container.innerHTML).toBe("<b>mcp</b>");
505
+ });
506
+
507
+ it("prefers an inline by_name component over tools.mcpApp", () => {
508
+ const { container } = render(
509
+ <Example
510
+ content={[
511
+ {
512
+ type: "tool-call",
513
+ toolCallId: "call",
514
+ toolName: "show_chart",
515
+ args: {},
516
+ mcp: { app: { resourceUri: "ui://chart" } },
517
+ },
518
+ ]}
519
+ Message={partsMessage({
520
+ tools: { by_name: { show_chart: Named }, Fallback },
521
+ })}
522
+ config={mcpConfig}
523
+ />,
524
+ );
525
+
526
+ expect(container.innerHTML).toBe("<b>named</b>");
527
+ });
528
+
529
+ it("uses inline Fallback when the tool call has no ui:// resource", () => {
530
+ const { container } = render(
531
+ <Example
532
+ content={[
533
+ {
534
+ type: "tool-call",
535
+ toolCallId: "call",
536
+ toolName: "show_chart",
537
+ args: {},
538
+ },
539
+ ]}
540
+ Message={partsMessage({ tools: { Fallback } })}
541
+ config={mcpConfig}
542
+ />,
543
+ );
544
+
545
+ expect(container.innerHTML).toBe("<i>fallback</i>");
546
+ });
547
+
548
+ it("uses inline Fallback when the resource URI is not an MCP App URI", () => {
549
+ const { container } = render(
550
+ <Example
551
+ content={[
552
+ {
553
+ type: "tool-call",
554
+ toolCallId: "call",
555
+ toolName: "show_chart",
556
+ args: {},
557
+ mcp: { app: { resourceUri: "https://example.com/chart" } },
558
+ },
559
+ ]}
560
+ Message={partsMessage({ tools: { Fallback } })}
561
+ config={mcpConfig}
562
+ />,
563
+ );
564
+
565
+ expect(container.innerHTML).toBe("<i>fallback</i>");
566
+ });
567
+
464
568
  it("uses dataRenderers.fallbacks[0] before inline data.Fallback", async () => {
465
569
  const { container } = render(
466
570
  <Example
@@ -7,23 +7,23 @@ import {
7
7
  type PropsWithChildren,
8
8
  useMemo,
9
9
  } from "react";
10
- import { useAuiState, useAui } from "@assistant-ui/store";
10
+ import { useAuiState } from "@assistant-ui/store";
11
11
  import { PartByIndexProvider } from "../../context/providers/PartByIndexProvider";
12
12
  import { TextMessagePartProvider } from "../../context/providers/TextMessagePartProvider";
13
13
  import { MessagePartPrimitiveText } from "../messagePart/MessagePartText";
14
14
  import { MessagePartPrimitiveImage } from "../messagePart/MessagePartImage";
15
- import type {
16
- Unstable_AudioMessagePartComponent,
17
- DataMessagePartComponent,
18
- DataMessagePartProps,
19
- EmptyMessagePartComponent,
20
- TextMessagePartComponent,
21
- ImageMessagePartComponent,
22
- SourceMessagePartComponent,
23
- ToolCallMessagePartComponent,
24
- ToolCallMessagePartProps,
25
- FileMessagePartComponent,
26
- ReasoningMessagePartComponent,
15
+ import {
16
+ MessagePartComponent,
17
+ type Unstable_AudioMessagePartComponent,
18
+ type DataMessagePartComponent,
19
+ type EmptyMessagePartComponent,
20
+ type TextMessagePartComponent,
21
+ type ImageMessagePartComponent,
22
+ type SourceMessagePartComponent,
23
+ type ToolCallMessagePartComponent,
24
+ type ToolCallMessagePartProps,
25
+ type FileMessagePartComponent,
26
+ type ReasoningMessagePartComponent,
27
27
  } from "@assistant-ui/core/react";
28
28
  import { MessagePartPrimitiveInProgress } from "../messagePart/MessagePartInProgress";
29
29
  import type { MessagePartStatus } from "@assistant-ui/core";
@@ -86,6 +86,13 @@ const useMessagePartsGrouped = (
86
86
  return { groups, partKeys: getMessagePartKeys(parts) };
87
87
  };
88
88
 
89
+ /**
90
+ * @deprecated Prefer `<MessagePrimitive.GroupedParts>` for adjacent
91
+ * grouping — it dispatches all rendering through one `switch (part.type)`
92
+ * and supports nested group paths. Keep this primitive only for
93
+ * non-adjacent clustering (e.g., gathering parts with the same parent-id
94
+ * across the message).
95
+ */
89
96
  export namespace MessagePrimitiveUnstable_PartsGrouped {
90
97
  export type Props = {
91
98
  /**
@@ -231,34 +238,6 @@ export namespace MessagePrimitiveUnstable_PartsGrouped {
231
238
  };
232
239
  }
233
240
 
234
- const ToolUIDisplay = ({
235
- Fallback,
236
- ...props
237
- }: {
238
- Fallback: ToolCallMessagePartComponent | undefined;
239
- } & ToolCallMessagePartProps) => {
240
- const Render = useAuiState(
241
- (s) => s.tools.toolUIs[props.toolName]?.[0]?.render ?? Fallback,
242
- );
243
- if (!Render) return null;
244
- return <Render {...props} />;
245
- };
246
-
247
- const DataUIDisplay = ({
248
- Fallback,
249
- ...props
250
- }: {
251
- Fallback: DataMessagePartComponent | undefined;
252
- } & DataMessagePartProps) => {
253
- const Render = useAuiState((s) => {
254
- const named = s.dataRenderers.renderers[props.name]?.[0];
255
- if (named) return named;
256
- return s.dataRenderers.fallbacks[0] ?? Fallback;
257
- });
258
- if (!Render) return null;
259
- return <Render {...props} />;
260
- };
261
-
262
241
  const defaultComponents = {
263
242
  Text: () => (
264
243
  <p style={{ whiteSpace: "pre-line" }}>
@@ -280,104 +259,17 @@ type MessagePartComponentProps = {
280
259
  components: MessagePrimitiveUnstable_PartsGrouped.Props["components"];
281
260
  };
282
261
 
283
- const MessagePartComponent: FC<MessagePartComponentProps> = ({
284
- components: {
285
- Text = defaultComponents.Text,
286
- Reasoning = defaultComponents.Reasoning,
287
- Image = defaultComponents.Image,
288
- Source = defaultComponents.Source,
289
- File = defaultComponents.File,
290
- Unstable_Audio: Audio = defaultComponents.Unstable_Audio,
291
- tools = {},
292
- data,
293
- } = {},
294
- }) => {
295
- const aui = useAui();
296
- const part = useAuiState((s) => s.part);
297
-
298
- const type = part.type;
299
- if (type === "tool-call") {
300
- const addResult = aui.part.addToolResult;
301
- const resume = aui.part.resumeToolCall;
302
- const respondToApproval = aui.part.respondToToolApproval;
303
- const unstable_recordInteraction = aui.part.unstable_recordInteraction;
304
- if ("Override" in tools)
305
- return (
306
- <tools.Override
307
- {...part}
308
- addResult={addResult}
309
- resume={resume}
310
- respondToApproval={respondToApproval}
311
- {...(unstable_recordInteraction && { unstable_recordInteraction })}
312
- />
313
- );
314
- const Tool =
315
- (tools.by_name && Object.hasOwn(tools.by_name, part.toolName)
316
- ? tools.by_name[part.toolName]
317
- : undefined) ?? tools.Fallback;
318
- return (
319
- <ToolUIDisplay
320
- {...part}
321
- Fallback={Tool}
322
- addResult={addResult}
323
- resume={resume}
324
- respondToApproval={respondToApproval}
325
- {...(unstable_recordInteraction && { unstable_recordInteraction })}
326
- />
327
- );
328
- }
329
-
330
- if (part.status?.type === "requires-action")
331
- throw new Error("Encountered unexpected requires-action status");
332
-
333
- switch (type) {
334
- case "text":
335
- return <Text {...part} />;
336
-
337
- case "reasoning":
338
- return <Reasoning {...part} />;
339
-
340
- case "source":
341
- return <Source {...part} />;
342
-
343
- case "image":
344
- return <Image {...part} />;
345
-
346
- case "file":
347
- return <File {...part} />;
348
-
349
- case "audio":
350
- return <Audio {...part} />;
351
-
352
- case "data": {
353
- const Data =
354
- (data?.by_name && Object.hasOwn(data.by_name, part.name)
355
- ? data.by_name[part.name]
356
- : undefined) ?? data?.Fallback;
357
- return <DataUIDisplay {...part} Fallback={Data} />;
358
- }
359
-
360
- default:
361
- console.warn(`Unknown message part type: ${type}`);
362
- return null;
363
- }
364
- };
365
-
366
262
  type MessagePartProps = {
367
263
  partIndex: number;
368
264
  components: MessagePrimitiveUnstable_PartsGrouped.Props["components"];
369
265
  };
370
266
 
371
- const MessagePartImpl: FC<MessagePartProps> = ({ partIndex, components }) => {
372
- return (
267
+ const MessagePart = memo(
268
+ ({ partIndex, components }: MessagePartProps) => (
373
269
  <PartByIndexProvider index={partIndex}>
374
270
  <MessagePartComponent components={components} />
375
271
  </PartByIndexProvider>
376
- );
377
- };
378
-
379
- const MessagePart = memo(
380
- MessagePartImpl,
272
+ ),
381
273
  (prev, next) =>
382
274
  prev.partIndex === next.partIndex &&
383
275
  prev.components?.Text === next.components?.Text &&
@@ -387,8 +279,7 @@ const MessagePart = memo(
387
279
  prev.components?.File === next.components?.File &&
388
280
  prev.components?.Unstable_Audio === next.components?.Unstable_Audio &&
389
281
  prev.components?.tools === next.components?.tools &&
390
- prev.components?.data === next.components?.data &&
391
- prev.components?.Group === next.components?.Group,
282
+ prev.components?.data === next.components?.data,
392
283
  );
393
284
 
394
285
  const EmptyPartFallback: FC<{
@@ -473,6 +364,17 @@ export const MessagePrimitiveUnstable_PartsGrouped: FC<
473
364
  return <EmptyParts components={components} />;
474
365
  }
475
366
 
367
+ const leafComponents = {
368
+ ...components,
369
+ Text: components?.Text ?? defaultComponents.Text,
370
+ Reasoning: components?.Reasoning ?? defaultComponents.Reasoning,
371
+ Image: components?.Image ?? defaultComponents.Image,
372
+ Source: components?.Source ?? defaultComponents.Source,
373
+ File: components?.File ?? defaultComponents.File,
374
+ Unstable_Audio:
375
+ components?.Unstable_Audio ?? defaultComponents.Unstable_Audio,
376
+ };
377
+
476
378
  return messageGroups.map((group, groupIndex) => {
477
379
  const GroupComponent = components?.Group ?? defaultComponents.Group;
478
380
  const identity = partKeys[group.indices[0]!];
@@ -491,7 +393,7 @@ export const MessagePrimitiveUnstable_PartsGrouped: FC<
491
393
  <MessagePart
492
394
  key={partKeys[partIndex]}
493
395
  partIndex={partIndex}
494
- components={components}
396
+ components={leafComponents}
495
397
  />
496
398
  ))}
497
399
  </GroupComponent>