@assistant-ui/react 0.15.16 → 0.15.18

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 (297) hide show
  1. package/README.md +4 -4
  2. package/dist/index.d.ts +8 -4
  3. package/dist/index.js +4 -2
  4. package/dist/mcp-apps/McpAppRenderer.d.ts +36 -9
  5. package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  6. package/dist/mcp-apps/McpAppRenderer.js +108 -57
  7. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  8. package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
  9. package/dist/mcp-apps/McpAppsRemoteHost.js +20 -17
  10. package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
  11. package/dist/mcp-apps/app-frame.d.ts.map +1 -1
  12. package/dist/mcp-apps/app-frame.js +229 -115
  13. package/dist/mcp-apps/app-frame.js.map +1 -1
  14. package/dist/mcp-apps/bridge.js +2 -3
  15. package/dist/mcp-apps/bridge.js.map +1 -1
  16. package/dist/mcp-apps/index.d.ts +3 -3
  17. package/dist/model-context/frame/useAssistantFrameHost.d.ts +1 -1
  18. package/dist/model-context/frame/useAssistantFrameHost.js +10 -11
  19. package/dist/model-context/frame/useAssistantFrameHost.js.map +1 -1
  20. package/dist/model-context/makeAssistantVisible.js +1 -1
  21. package/dist/model-context/makeAssistantVisible.js.map +1 -1
  22. package/dist/primitives/actionBar/ActionBarCopy.js +3 -3
  23. package/dist/primitives/actionBar/ActionBarCopy.js.map +1 -1
  24. package/dist/primitives/actionBar/ActionBarExportMarkdown.js +3 -3
  25. package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
  26. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +3 -3
  27. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js.map +1 -1
  28. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +3 -3
  29. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js.map +1 -1
  30. package/dist/primitives/actionBar/ActionBarRoot.js.map +1 -1
  31. package/dist/primitives/actionBar/ActionBarStopSpeaking.js +3 -3
  32. package/dist/primitives/actionBar/ActionBarStopSpeaking.js.map +1 -1
  33. package/dist/primitives/actionBarMore/ActionBarMoreContent.js.map +1 -1
  34. package/dist/primitives/actionBarMore/ActionBarMoreItem.js.map +1 -1
  35. package/dist/primitives/actionBarMore/ActionBarMoreRoot.js.map +1 -1
  36. package/dist/primitives/actionBarMore/ActionBarMoreSeparator.js.map +1 -1
  37. package/dist/primitives/actionBarMore/ActionBarMoreTrigger.js.map +1 -1
  38. package/dist/primitives/actionBarMore/scope.d.ts +2 -2
  39. package/dist/primitives/actionBarMore/scope.d.ts.map +1 -1
  40. package/dist/primitives/actionBarMore/scope.js.map +1 -1
  41. package/dist/primitives/assistantModal/AssistantModalAnchor.js.map +1 -1
  42. package/dist/primitives/assistantModal/AssistantModalContent.js +1 -1
  43. package/dist/primitives/assistantModal/AssistantModalContent.js.map +1 -1
  44. package/dist/primitives/assistantModal/AssistantModalRoot.js.map +1 -1
  45. package/dist/primitives/assistantModal/AssistantModalTrigger.js.map +1 -1
  46. package/dist/primitives/assistantModal/scope.d.ts +2 -2
  47. package/dist/primitives/assistantModal/scope.d.ts.map +1 -1
  48. package/dist/primitives/assistantModal/scope.js.map +1 -1
  49. package/dist/primitives/branchPicker/BranchPickerRoot.js.map +1 -1
  50. package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -1
  51. package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
  52. package/dist/primitives/composer/ComposerDictationTranscript.js.map +1 -1
  53. package/dist/primitives/composer/ComposerInput.js +1 -3
  54. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  55. package/dist/primitives/composer/ComposerInputPluginContext.d.ts +1 -2
  56. package/dist/primitives/composer/ComposerInputPluginContext.d.ts.map +1 -1
  57. package/dist/primitives/composer/ComposerInputPluginContext.js +1 -6
  58. package/dist/primitives/composer/ComposerInputPluginContext.js.map +1 -1
  59. package/dist/primitives/composer/ComposerQuote.js +5 -5
  60. package/dist/primitives/composer/ComposerQuote.js.map +1 -1
  61. package/dist/primitives/composer/ComposerRoot.js +3 -3
  62. package/dist/primitives/composer/ComposerRoot.js.map +1 -1
  63. package/dist/primitives/composer/trigger/TriggerPopover.d.ts +5 -0
  64. package/dist/primitives/composer/trigger/TriggerPopover.d.ts.map +1 -1
  65. package/dist/primitives/composer/trigger/TriggerPopover.js +157 -130
  66. package/dist/primitives/composer/trigger/TriggerPopover.js.map +1 -1
  67. package/dist/primitives/composer/trigger/TriggerPopoverAction.js +46 -15
  68. package/dist/primitives/composer/trigger/TriggerPopoverAction.js.map +1 -1
  69. package/dist/primitives/composer/trigger/TriggerPopoverBack.js +3 -3
  70. package/dist/primitives/composer/trigger/TriggerPopoverBack.js.map +1 -1
  71. package/dist/primitives/composer/trigger/TriggerPopoverCategories.js +4 -4
  72. package/dist/primitives/composer/trigger/TriggerPopoverCategories.js.map +1 -1
  73. package/dist/primitives/composer/trigger/TriggerPopoverDirective.js +40 -10
  74. package/dist/primitives/composer/trigger/TriggerPopoverDirective.js.map +1 -1
  75. package/dist/primitives/composer/trigger/TriggerPopoverItems.js +4 -4
  76. package/dist/primitives/composer/trigger/TriggerPopoverItems.js.map +1 -1
  77. package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts +2 -0
  78. package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts.map +1 -1
  79. package/dist/primitives/composer/trigger/TriggerPopoverResource.js +63 -61
  80. package/dist/primitives/composer/trigger/TriggerPopoverResource.js.map +1 -1
  81. package/dist/primitives/composer/trigger/TriggerPopoverRootContext.d.ts +2 -0
  82. package/dist/primitives/composer/trigger/TriggerPopoverRootContext.d.ts.map +1 -1
  83. package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js.map +1 -1
  84. package/dist/primitives/composer/trigger/detectTrigger.d.ts +21 -1
  85. package/dist/primitives/composer/trigger/detectTrigger.d.ts.map +1 -0
  86. package/dist/primitives/composer/trigger/detectTrigger.js +17 -3
  87. package/dist/primitives/composer/trigger/detectTrigger.js.map +1 -1
  88. package/dist/primitives/composer/trigger/index.d.ts +3 -1
  89. package/dist/primitives/composer/trigger/index.d.ts.map +1 -1
  90. package/dist/primitives/composer/trigger/index.js.map +1 -1
  91. package/dist/primitives/composer/trigger/matchesTriggerItemQuery.d.ts +6 -0
  92. package/dist/primitives/composer/trigger/matchesTriggerItemQuery.d.ts.map +1 -0
  93. package/dist/primitives/composer/trigger/matchesTriggerItemQuery.js +9 -0
  94. package/dist/primitives/composer/trigger/matchesTriggerItemQuery.js.map +1 -0
  95. package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts +3 -4
  96. package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts.map +1 -1
  97. package/dist/primitives/composer/trigger/triggerDetectionResource.js +15 -14
  98. package/dist/primitives/composer/trigger/triggerDetectionResource.js.map +1 -1
  99. package/dist/primitives/composer/trigger/triggerNavigationResource.d.ts.map +1 -1
  100. package/dist/primitives/composer/trigger/triggerNavigationResource.js +3 -5
  101. package/dist/primitives/composer/trigger/triggerNavigationResource.js.map +1 -1
  102. package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
  103. package/dist/primitives/composer/trigger/triggerSelectionResource.js +18 -19
  104. package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
  105. package/dist/primitives/composer.d.ts +3 -2
  106. package/dist/primitives/composer.js.map +1 -1
  107. package/dist/primitives/error/ErrorMessage.js.map +1 -1
  108. package/dist/primitives/message/MessageIf.js.map +1 -1
  109. package/dist/primitives/message/MessageParts.js.map +1 -1
  110. package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
  111. package/dist/primitives/message/MessageRoot.js +4 -4
  112. package/dist/primitives/message/MessageRoot.js.map +1 -1
  113. package/dist/primitives/messagePart/MessagePartText.d.ts +1 -1
  114. package/dist/primitives/messagePart/MessagePartText.js.map +1 -1
  115. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +3 -3
  116. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js.map +1 -1
  117. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +3 -3
  118. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
  119. package/dist/primitives/thread/ThreadIf.js.map +1 -1
  120. package/dist/primitives/thread/ThreadViewport.js +3 -3
  121. package/dist/primitives/thread/ThreadViewport.js.map +1 -1
  122. package/dist/primitives/thread/ThreadViewportFooter.js +3 -3
  123. package/dist/primitives/thread/ThreadViewportFooter.js.map +1 -1
  124. package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
  125. package/dist/primitives/thread/useThreadViewportAutoScroll.js +4 -4
  126. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  127. package/dist/primitives/threadList/ThreadListNew.js +3 -3
  128. package/dist/primitives/threadList/ThreadListNew.js.map +1 -1
  129. package/dist/primitives/threadListFocusGroup.d.ts +2 -1
  130. package/dist/primitives/threadListFocusGroup.d.ts.map +1 -1
  131. package/dist/primitives/threadListFocusGroup.js +2 -2
  132. package/dist/primitives/threadListFocusGroup.js.map +1 -1
  133. package/dist/primitives/threadListItem/ThreadListItemRoot.js +3 -3
  134. package/dist/primitives/threadListItem/ThreadListItemRoot.js.map +1 -1
  135. package/dist/primitives/threadListItem/ThreadListItemTrigger.js +1 -1
  136. package/dist/primitives/threadListItem/ThreadListItemTrigger.js.map +1 -1
  137. package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js +1 -1
  138. package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js.map +1 -1
  139. package/dist/primitives/threadListItemMore/ThreadListItemMoreItem.js.map +1 -1
  140. package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js +1 -1
  141. package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js.map +1 -1
  142. package/dist/primitives/threadListItemMore/ThreadListItemMoreSeparator.js.map +1 -1
  143. package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js +1 -2
  144. package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js.map +1 -1
  145. package/dist/primitives/threadListItemMore/scope.d.ts +2 -2
  146. package/dist/primitives/threadListItemMore/scope.d.ts.map +1 -1
  147. package/dist/primitives/threadListItemMore/scope.js.map +1 -1
  148. package/dist/sandbox-host/SandboxHost.d.ts.map +1 -1
  149. package/dist/sandbox-host/SandboxHost.js +148 -72
  150. package/dist/sandbox-host/SandboxHost.js.map +1 -1
  151. package/dist/unstable/useLiveCompletionAdapter.js +3 -1
  152. package/dist/unstable/useLiveCompletionAdapter.js.map +1 -1
  153. package/dist/unstable/useMentionAdapter.d.ts +9 -3
  154. package/dist/unstable/useMentionAdapter.d.ts.map +1 -1
  155. package/dist/unstable/useMentionAdapter.js +50 -43
  156. package/dist/unstable/useMentionAdapter.js.map +1 -1
  157. package/dist/unstable/useModelContextSnapshot.d.ts +34 -0
  158. package/dist/unstable/useModelContextSnapshot.d.ts.map +1 -0
  159. package/dist/unstable/useModelContextSnapshot.js +61 -0
  160. package/dist/unstable/useModelContextSnapshot.js.map +1 -0
  161. package/dist/unstable/useSlashCommandAdapter.d.ts.map +1 -1
  162. package/dist/unstable/useSlashCommandAdapter.js +37 -29
  163. package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
  164. package/dist/unstable/webmcp/WebMcpRegistrationResource.d.ts +14 -0
  165. package/dist/unstable/webmcp/WebMcpRegistrationResource.d.ts.map +1 -0
  166. package/dist/unstable/webmcp/WebMcpRegistrationResource.js +85 -0
  167. package/dist/unstable/webmcp/WebMcpRegistrationResource.js.map +1 -0
  168. package/dist/unstable/webmcp/convertTools.d.ts +21 -0
  169. package/dist/unstable/webmcp/convertTools.d.ts.map +1 -0
  170. package/dist/unstable/webmcp/convertTools.js +110 -0
  171. package/dist/unstable/webmcp/convertTools.js.map +1 -0
  172. package/dist/unstable/webmcp/useWebMcpProvider.d.ts +21 -0
  173. package/dist/unstable/webmcp/useWebMcpProvider.d.ts.map +1 -0
  174. package/dist/unstable/webmcp/useWebMcpProvider.js +105 -0
  175. package/dist/unstable/webmcp/useWebMcpProvider.js.map +1 -0
  176. package/dist/unstable/webmcp/webmcp-host.d.ts +39 -0
  177. package/dist/unstable/webmcp/webmcp-host.d.ts.map +1 -0
  178. package/dist/unstable/webmcp/webmcp-host.js +29 -0
  179. package/dist/unstable/webmcp/webmcp-host.js.map +1 -0
  180. package/dist/utils/Primitive.d.ts +6 -6
  181. package/dist/utils/Primitive.js +2 -2
  182. package/dist/utils/Primitive.js.map +1 -1
  183. package/dist/utils/createActionButton.js +3 -3
  184. package/dist/utils/createActionButton.js.map +1 -1
  185. package/dist/utils/hooks/useOnResizeContent.js +1 -1
  186. package/dist/utils/hooks/useOnResizeContent.js.map +1 -1
  187. package/dist/utils/hooks/useOnScrollToBottom.js +1 -1
  188. package/dist/utils/hooks/useOnScrollToBottom.js.map +1 -1
  189. package/dist/utils/smooth/SmoothContext.d.ts +24 -0
  190. package/dist/utils/smooth/SmoothContext.d.ts.map +1 -1
  191. package/dist/utils/smooth/useSmooth.js +1 -1
  192. package/dist/utils/smooth/useSmooth.js.map +1 -1
  193. package/package.json +14 -22
  194. package/src/index.ts +22 -1
  195. package/src/mcp-apps/McpAppRenderer.test.tsx +386 -9
  196. package/src/mcp-apps/McpAppRenderer.tsx +171 -69
  197. package/src/mcp-apps/McpAppsRemoteHost.concurrent.test.tsx +215 -0
  198. package/src/mcp-apps/McpAppsRemoteHost.ts +28 -12
  199. package/src/mcp-apps/app-frame.test.tsx +349 -0
  200. package/src/mcp-apps/app-frame.tsx +68 -9
  201. package/src/mcp-apps/bridge.ts +7 -2
  202. package/src/mcp-apps/index.ts +6 -1
  203. package/src/model-context/frame/AssistantFrame.test.ts +3 -3
  204. package/src/model-context/frame/SPEC_AssistantFrame.md +1 -1
  205. package/src/model-context/frame/useAssistantFrameHost.ts +2 -2
  206. package/src/model-context/makeAssistantVisible.tsx +1 -1
  207. package/src/primitives/actionBar/ActionBarCopy.tsx +1 -1
  208. package/src/primitives/actionBar/ActionBarExportMarkdown.tsx +1 -1
  209. package/src/primitives/actionBar/ActionBarFeedbackNegative.tsx +1 -1
  210. package/src/primitives/actionBar/ActionBarFeedbackPositive.tsx +1 -1
  211. package/src/primitives/actionBar/ActionBarStopSpeaking.tsx +1 -1
  212. package/src/primitives/actionBarMore/scope.ts +2 -2
  213. package/src/primitives/assistantModal/AssistantModalContent.tsx +1 -1
  214. package/src/primitives/assistantModal/scope.ts +2 -2
  215. package/src/primitives/composer/ComposerAttachmentDropzone.tsx +1 -1
  216. package/src/primitives/composer/ComposerInput.test.tsx +2 -1
  217. package/src/primitives/composer/ComposerInput.tsx +3 -3
  218. package/src/primitives/composer/ComposerInputPluginContext.tsx +0 -10
  219. package/src/primitives/composer/ComposerQuote.tsx +1 -1
  220. package/src/primitives/composer/ComposerRoot.tsx +1 -1
  221. package/src/primitives/composer/trigger/TriggerPopover.tsx +16 -1
  222. package/src/primitives/composer/trigger/TriggerPopoverAction.tsx +4 -2
  223. package/src/primitives/composer/trigger/TriggerPopoverBack.tsx +1 -1
  224. package/src/primitives/composer/trigger/TriggerPopoverBehavior.test.tsx +109 -0
  225. package/src/primitives/composer/trigger/TriggerPopoverCategories.tsx +1 -1
  226. package/src/primitives/composer/trigger/TriggerPopoverDirective.tsx +4 -2
  227. package/src/primitives/composer/trigger/TriggerPopoverItems.tsx +1 -1
  228. package/src/primitives/composer/trigger/TriggerPopoverResource.ts +4 -1
  229. package/src/primitives/composer/trigger/TriggerPopoverRootContext.tsx +2 -0
  230. package/src/primitives/composer/trigger/detectTrigger.test.ts +88 -5
  231. package/src/primitives/composer/trigger/detectTrigger.ts +50 -9
  232. package/src/primitives/composer/trigger/index.ts +1 -0
  233. package/src/primitives/composer/trigger/matchesTriggerItemQuery.test.ts +52 -0
  234. package/src/primitives/composer/trigger/matchesTriggerItemQuery.ts +13 -0
  235. package/src/primitives/composer/trigger/triggerDetectionResource.ts +10 -7
  236. package/src/primitives/composer/trigger/triggerNavigationResource.ts +3 -10
  237. package/src/primitives/composer/trigger/triggerSelectionResource.test.ts +15 -3
  238. package/src/primitives/composer/trigger/triggerSelectionResource.ts +1 -4
  239. package/src/primitives/composer.ts +1 -0
  240. package/src/primitives/message/MessageRoot.tsx +1 -1
  241. package/src/primitives/selectionToolbar/SelectionToolbarQuote.tsx +1 -1
  242. package/src/primitives/selectionToolbar/SelectionToolbarRoot.tsx +1 -1
  243. package/src/primitives/thread/ThreadViewport.tsx +1 -1
  244. package/src/primitives/thread/ThreadViewportFooter.tsx +1 -1
  245. package/src/primitives/thread/useThreadViewportAutoScroll.ts +8 -7
  246. package/src/primitives/threadList/ThreadListNew.tsx +1 -1
  247. package/src/primitives/threadListFocusGroup.ts +2 -2
  248. package/src/primitives/threadListItem/ThreadListItemRoot.tsx +1 -1
  249. package/src/primitives/threadListItem/ThreadListItemTrigger.tsx +1 -1
  250. package/src/primitives/threadListItemMore/ThreadListItemMoreContent.tsx +1 -1
  251. package/src/primitives/threadListItemMore/ThreadListItemMoreRoot.tsx +1 -1
  252. package/src/primitives/threadListItemMore/ThreadListItemMoreTrigger.tsx +2 -2
  253. package/src/primitives/threadListItemMore/scope.ts +2 -2
  254. package/src/sandbox-host/SandboxHost.test.tsx +165 -3
  255. package/src/sandbox-host/SandboxHost.tsx +42 -9
  256. package/src/tests/RemoteThreadListRuntime.adapterProvider.test.tsx +31 -1
  257. package/src/unstable/useComposerInputHistory.test.tsx +2 -1
  258. package/src/unstable/useLiveCompletionAdapter.test.tsx +60 -1
  259. package/src/unstable/useLiveCompletionAdapter.ts +4 -1
  260. package/src/unstable/useMentionAdapter.test.tsx +337 -0
  261. package/src/unstable/useMentionAdapter.ts +94 -57
  262. package/src/unstable/useModelContextSnapshot.test.tsx +132 -0
  263. package/src/unstable/useModelContextSnapshot.ts +67 -0
  264. package/src/unstable/useSlashCommandAdapter.test.tsx +141 -0
  265. package/src/unstable/useSlashCommandAdapter.ts +47 -19
  266. package/src/unstable/webmcp/WebMcpRegistrationResource.ts +92 -0
  267. package/src/unstable/webmcp/__tests__/webmcp.fake.tsx +168 -0
  268. package/src/unstable/webmcp/convertTools.test.ts +393 -0
  269. package/src/unstable/webmcp/convertTools.ts +180 -0
  270. package/src/unstable/webmcp/useWebMcpProvider.test.tsx +466 -0
  271. package/src/unstable/webmcp/useWebMcpProvider.ts +150 -0
  272. package/src/unstable/webmcp/webmcp-host.test.ts +142 -0
  273. package/src/unstable/webmcp/webmcp-host.ts +83 -0
  274. package/src/utils/Primitive.tsx +2 -2
  275. package/src/utils/createActionButton.tsx +1 -1
  276. package/src/utils/hooks/useOnResizeContent.ts +1 -1
  277. package/src/utils/hooks/useOnScrollToBottom.ts +1 -1
  278. package/src/utils/smooth/useSmooth.ts +1 -1
  279. package/dist/legacy-runtime/runtime/MessagePartRuntime.d.ts +0 -3
  280. package/dist/legacy-runtime/runtime/MessagePartRuntime.js +0 -2
  281. package/dist/legacy-runtime/runtime/MessageRuntime.d.ts +0 -3
  282. package/dist/legacy-runtime/runtime/MessageRuntime.js +0 -2
  283. package/dist/legacy-runtime/runtime/ThreadListRuntime.d.ts +0 -3
  284. package/dist/legacy-runtime/runtime/ThreadListRuntime.js +0 -2
  285. package/dist/utils/invokeCallbackSafely.d.ts +0 -5
  286. package/dist/utils/invokeCallbackSafely.d.ts.map +0 -1
  287. package/dist/utils/invokeCallbackSafely.js +0 -15
  288. package/dist/utils/invokeCallbackSafely.js.map +0 -1
  289. package/dist/utils/useToolArgsFieldStatus.d.ts +0 -15
  290. package/dist/utils/useToolArgsFieldStatus.d.ts.map +0 -1
  291. package/dist/utils/useToolArgsFieldStatus.js +0 -23
  292. package/dist/utils/useToolArgsFieldStatus.js.map +0 -1
  293. package/src/legacy-runtime/runtime/MessagePartRuntime.ts +0 -2
  294. package/src/legacy-runtime/runtime/MessageRuntime.ts +0 -3
  295. package/src/legacy-runtime/runtime/ThreadListRuntime.ts +0 -4
  296. package/src/utils/invokeCallbackSafely.ts +0 -11
  297. package/src/utils/useToolArgsFieldStatus.ts +0 -18
@@ -1,7 +1,7 @@
1
1
  // @vitest-environment jsdom
2
2
  import { render, waitFor } from "@testing-library/react";
3
3
  import { resource, useResource, withKey } from "@assistant-ui/tap";
4
- import { memo } from "react";
4
+ import { act, memo, startTransition, Suspense } from "react";
5
5
  import type {
6
6
  ToolCallMessagePartComponent,
7
7
  ToolCallMessagePartProps,
@@ -9,16 +9,21 @@ import type {
9
9
  import { beforeEach, describe, expect, it, vi } from "vitest";
10
10
  import type {
11
11
  McpAppBridgeHandlers,
12
+ McpAppFrameProps,
13
+ McpAppHostContext,
12
14
  McpAppsHost,
13
15
  McpAppsRemoteHostOptions,
14
16
  } from "./types";
15
17
 
16
- const { framePropsMock } = vi.hoisted(() => ({ framePropsMock: vi.fn() }));
18
+ const { appendMock, framePropsMock } = vi.hoisted(() => ({
19
+ appendMock: vi.fn(),
20
+ framePropsMock: vi.fn(),
21
+ }));
17
22
 
18
23
  vi.mock("@assistant-ui/store", async (importOriginal) => ({
19
24
  ...(await importOriginal()),
20
25
  useAui: () => ({
21
- thread: () => ({ append: vi.fn() }),
26
+ thread: { append: appendMock },
22
27
  }),
23
28
  }));
24
29
 
@@ -29,7 +34,7 @@ vi.mock("./app-frame", () => ({
29
34
  },
30
35
  }));
31
36
 
32
- import { McpAppRenderer } from "./McpAppRenderer";
37
+ import { McpAppRenderer, type McpAppRendererOptions } from "./McpAppRenderer";
33
38
  import { McpAppsRemoteHost } from "./McpAppsRemoteHost";
34
39
 
35
40
  const useHost = ({ host }: { host: McpAppsHost }) => host;
@@ -43,9 +48,12 @@ const createDeferred = <T,>() => {
43
48
  return { promise, resolve };
44
49
  };
45
50
 
46
- const createPart = (serverId?: string): ToolCallMessagePartProps => ({
51
+ const createPart = (
52
+ serverId?: string,
53
+ toolCallId = "call-1",
54
+ ): ToolCallMessagePartProps => ({
47
55
  type: "tool-call",
48
- toolCallId: "call-1",
56
+ toolCallId,
49
57
  toolName: "search",
50
58
  args: {},
51
59
  argsText: "{}",
@@ -61,14 +69,31 @@ const createPart = (serverId?: string): ToolCallMessagePartProps => ({
61
69
  respondToApproval: vi.fn(),
62
70
  });
63
71
 
64
- function Harness({ host, serverId }: { host: McpAppsHost; serverId?: string }) {
72
+ const createPartWithoutApp = (): ToolCallMessagePartProps => {
73
+ const part = createPart();
74
+ delete part.mcp;
75
+ return part;
76
+ };
77
+
78
+ function Harness({
79
+ host,
80
+ serverId,
81
+ handlers,
82
+ part,
83
+ }: {
84
+ host: McpAppsHost;
85
+ serverId?: string;
86
+ handlers?: McpAppRendererOptions["handlers"];
87
+ part?: ToolCallMessagePartProps;
88
+ }) {
65
89
  const renderer = useResource(
66
90
  McpAppRenderer({
67
91
  host: Host({ host }),
92
+ ...(handlers === undefined ? {} : { handlers }),
68
93
  }),
69
94
  );
70
95
  const Renderer = renderer.render;
71
- return <Renderer {...createPart(serverId)} />;
96
+ return <Renderer {...(part ?? createPart(serverId))} />;
72
97
  }
73
98
 
74
99
  const MemoizedPart = memo(function MemoizedPart({
@@ -88,6 +113,60 @@ function MemoizedHarness({ host }: { host: McpAppsHost }) {
88
113
  return <MemoizedPart Renderer={renderer.render} />;
89
114
  }
90
115
 
116
+ const MemoizedPartById = memo(function MemoizedPartById({
117
+ Renderer,
118
+ toolCallId,
119
+ }: {
120
+ Renderer: ToolCallMessagePartComponent;
121
+ toolCallId: string;
122
+ }) {
123
+ return <Renderer {...createPart(undefined, toolCallId)} />;
124
+ });
125
+
126
+ function OptionsHarness({
127
+ host,
128
+ hostContext,
129
+ handlers,
130
+ forPart,
131
+ toolCallIds = ["call-1"],
132
+ }: {
133
+ host: McpAppsHost;
134
+ hostContext?: McpAppHostContext;
135
+ handlers?: McpAppRendererOptions["handlers"];
136
+ forPart?: McpAppRendererOptions["forPart"];
137
+ toolCallIds?: string[];
138
+ }) {
139
+ const renderer = useResource(
140
+ McpAppRenderer({
141
+ host: Host({ host }),
142
+ ...(hostContext === undefined ? {} : { hostContext }),
143
+ ...(handlers === undefined ? {} : { handlers }),
144
+ ...(forPart === undefined ? {} : { forPart }),
145
+ }),
146
+ );
147
+ return toolCallIds.map((toolCallId) => (
148
+ <MemoizedPartById
149
+ key={toolCallId}
150
+ Renderer={renderer.render}
151
+ toolCallId={toolCallId}
152
+ />
153
+ ));
154
+ }
155
+
156
+ const framePropsCalls = () =>
157
+ framePropsMock.mock.calls.map(([props]) => props as McpAppFrameProps);
158
+
159
+ const loadingHost = (): McpAppsHost => ({
160
+ loadResource: vi.fn(async ({ uri }) => ({
161
+ uri,
162
+ mimeType: "text/html;profile=mcp-app" as const,
163
+ html: "",
164
+ })),
165
+ callTool: vi.fn(),
166
+ readResource: vi.fn(),
167
+ listResources: vi.fn(),
168
+ });
169
+
91
170
  function RemoteHarness({
92
171
  url,
93
172
  headers,
@@ -110,9 +189,146 @@ function RemoteHarness({
110
189
 
111
190
  describe("McpAppRenderer", () => {
112
191
  beforeEach(() => {
192
+ appendMock.mockReset();
113
193
  framePropsMock.mockReset();
114
194
  });
115
195
 
196
+ it("loads newly mounted parts through the committed host", async () => {
197
+ const hostA = loadingHost();
198
+ const hostB = loadingHost();
199
+ const view = render(
200
+ <OptionsHarness host={hostA} toolCallIds={["call-1"]} />,
201
+ );
202
+ await waitFor(() => expect(hostA.loadResource).toHaveBeenCalledOnce());
203
+ vi.mocked(hostA.loadResource).mockClear();
204
+
205
+ view.rerender(
206
+ <OptionsHarness host={hostB} toolCallIds={["call-1", "call-2"]} />,
207
+ );
208
+ await waitFor(() => expect(hostB.loadResource).toHaveBeenCalled());
209
+
210
+ expect(hostA.loadResource).not.toHaveBeenCalled();
211
+ });
212
+
213
+ it("merges forPart handlers over the thread-wide handlers", async () => {
214
+ const onInitialized = vi.fn();
215
+ render(
216
+ <OptionsHarness
217
+ host={loadingHost()}
218
+ handlers={{ onInitialized }}
219
+ forPart={(part) => ({
220
+ handlers: {
221
+ requestDisplayMode: ({ mode }) => {
222
+ void part;
223
+ return { mode };
224
+ },
225
+ },
226
+ })}
227
+ />,
228
+ );
229
+
230
+ await waitFor(() => expect(framePropsCalls().length).toBeGreaterThan(0));
231
+ const handlers = framePropsCalls().at(-1)?.handlers;
232
+ expect(handlers?.requestDisplayMode).toBeDefined();
233
+ handlers?.onInitialized?.();
234
+ expect(onInitialized).toHaveBeenCalledOnce();
235
+ });
236
+
237
+ it("leaves mounted parts alone when renderer options are unchanged", async () => {
238
+ const host = loadingHost();
239
+ const hostContext: McpAppHostContext = { displayMode: "inline" };
240
+ const view = () => <OptionsHarness host={host} hostContext={hostContext} />;
241
+
242
+ const rendered = render(view());
243
+ await waitFor(() => expect(framePropsCalls().length).toBeGreaterThan(0));
244
+ const rendersBefore = framePropsCalls().length;
245
+
246
+ rendered.rerender(view());
247
+ expect(framePropsCalls().length).toBe(rendersBefore);
248
+ });
249
+
250
+ it("delivers a changed renderer host context to mounted parts", async () => {
251
+ const host = loadingHost();
252
+ const view = render(
253
+ <OptionsHarness host={host} hostContext={{ displayMode: "inline" }} />,
254
+ );
255
+ await waitFor(() =>
256
+ expect(framePropsCalls().at(-1)?.hostContext).toEqual({
257
+ displayMode: "inline",
258
+ }),
259
+ );
260
+
261
+ view.rerender(
262
+ <OptionsHarness
263
+ host={host}
264
+ hostContext={{ displayMode: "fullscreen" }}
265
+ />,
266
+ );
267
+ await waitFor(() =>
268
+ expect(framePropsCalls().at(-1)?.hostContext).toEqual({
269
+ displayMode: "fullscreen",
270
+ }),
271
+ );
272
+ });
273
+
274
+ it("resolves host context per part through forPart", async () => {
275
+ const modes: Record<string, string> = {
276
+ "call-1": "inline",
277
+ "call-2": "fullscreen",
278
+ };
279
+ render(
280
+ <OptionsHarness
281
+ host={loadingHost()}
282
+ hostContext={{ theme: "light" }}
283
+ toolCallIds={["call-1", "call-2"]}
284
+ forPart={(part) => ({
285
+ hostContext: {
286
+ theme: "light",
287
+ displayMode: modes[part.toolCallId] as "inline" | "fullscreen",
288
+ },
289
+ })}
290
+ />,
291
+ );
292
+
293
+ await waitFor(() => expect(framePropsCalls().length).toBeGreaterThan(1));
294
+ const delivered = framePropsCalls().map((props) => props.hostContext);
295
+ expect(delivered).toContainEqual({
296
+ theme: "light",
297
+ displayMode: "inline",
298
+ });
299
+ expect(delivered).toContainEqual({
300
+ theme: "light",
301
+ displayMode: "fullscreen",
302
+ });
303
+ });
304
+
305
+ it("gives forPart handlers the part that owns the frame", async () => {
306
+ const promoted: string[] = [];
307
+ render(
308
+ <OptionsHarness
309
+ host={loadingHost()}
310
+ toolCallIds={["call-1", "call-2"]}
311
+ forPart={(part) => ({
312
+ hostContext: { toolCallId: part.toolCallId },
313
+ handlers: {
314
+ requestDisplayMode: ({ mode }) => {
315
+ promoted.push(`${part.toolCallId}:${mode}`);
316
+ return { mode };
317
+ },
318
+ },
319
+ })}
320
+ />,
321
+ );
322
+
323
+ await waitFor(() => expect(framePropsCalls().length).toBeGreaterThan(1));
324
+ const second = framePropsCalls().find(
325
+ (props) => props.hostContext?.["toolCallId"] === "call-2",
326
+ );
327
+ await second?.handlers?.requestDisplayMode?.({ mode: "fullscreen" });
328
+
329
+ expect(promoted).toEqual(["call-2:fullscreen"]);
330
+ });
331
+
116
332
  it("injects serverId into loadResource and reloads when it changes", async () => {
117
333
  const loadResource = vi.fn(async ({ uri }: { uri: string }) => ({
118
334
  uri,
@@ -142,6 +358,64 @@ describe("McpAppRenderer", () => {
142
358
  });
143
359
  });
144
360
 
361
+ it("keeps bridge server ids scoped to committed renders", async () => {
362
+ const host: McpAppsHost = {
363
+ loadResource: vi.fn(async ({ uri }) => ({
364
+ uri,
365
+ mimeType: "text/html;profile=mcp-app" as const,
366
+ html: "",
367
+ })),
368
+ callTool: vi.fn(),
369
+ readResource: vi.fn(),
370
+ listResources: vi.fn(),
371
+ };
372
+ const interruptedRender = vi.fn();
373
+ const pending = new Promise<never>(() => {});
374
+ const Blocker = ({ blocked }: { blocked: boolean }) => {
375
+ if (blocked) {
376
+ interruptedRender();
377
+ throw pending;
378
+ }
379
+ return null;
380
+ };
381
+ const view = (part: ToolCallMessagePartProps, blocked: boolean) => (
382
+ <Suspense fallback={null}>
383
+ <Harness host={host} part={part} />
384
+ <Blocker blocked={blocked} />
385
+ </Suspense>
386
+ );
387
+ const rendered = render(view(createPart("server-a"), false));
388
+ await waitFor(() => expect(framePropsMock).toHaveBeenCalled());
389
+ const handlers = framePropsMock.mock.lastCall?.[0]
390
+ .handlers as McpAppBridgeHandlers;
391
+
392
+ act(() => {
393
+ startTransition(() =>
394
+ rendered.rerender(view(createPart("server-b"), true)),
395
+ );
396
+ });
397
+ expect(interruptedRender).toHaveBeenCalled();
398
+ await handlers.callTool?.({ name: "search" });
399
+
400
+ expect(host.callTool).toHaveBeenCalledWith({
401
+ name: "search",
402
+ serverId: "server-a",
403
+ });
404
+
405
+ framePropsMock.mockClear();
406
+ vi.mocked(host.callTool).mockClear();
407
+ rendered.rerender(view(createPartWithoutApp(), false));
408
+ await waitFor(() => expect(framePropsMock).toHaveBeenCalled());
409
+ expect(framePropsMock.mock.lastCall?.[0].app.serverId).toBe("server-a");
410
+ const fallbackHandlers = framePropsMock.mock.lastCall?.[0]
411
+ .handlers as McpAppBridgeHandlers;
412
+ await fallbackHandlers.callTool?.({ name: "search" });
413
+ expect(host.callTool).toHaveBeenCalledWith({
414
+ name: "search",
415
+ serverId: "server-a",
416
+ });
417
+ });
418
+
145
419
  it("reloads the resource and hides stale HTML when the host changes", async () => {
146
420
  const nextResource = createDeferred<{
147
421
  uri: string;
@@ -175,7 +449,7 @@ describe("McpAppRenderer", () => {
175
449
  framePropsMock.mockClear();
176
450
  view.rerender(<MemoizedHarness host={nextHost} />);
177
451
 
178
- expect(nextHost.loadResource).toHaveBeenCalledTimes(1);
452
+ await waitFor(() => expect(nextHost.loadResource).toHaveBeenCalledTimes(1));
179
453
  expect(framePropsMock).not.toHaveBeenCalled();
180
454
 
181
455
  nextResource.resolve({
@@ -397,4 +671,107 @@ describe("McpAppRenderer", () => {
397
671
  await handlers.listResources?.(params);
398
672
  expect(listResources).toHaveBeenCalledWith(params);
399
673
  });
674
+
675
+ it("appends MCP Apps message content in the default sendMessage handler", async () => {
676
+ render(<Harness host={loadingHost()} />);
677
+ await waitFor(() => expect(framePropsMock).toHaveBeenCalled());
678
+ const handlers = framePropsMock.mock.lastCall?.[0]
679
+ .handlers as McpAppBridgeHandlers;
680
+
681
+ expect(
682
+ handlers.sendMessage?.({
683
+ role: "user",
684
+ content: [
685
+ { type: "text", text: "Open Grade 5 Math" },
686
+ { type: "image", data: "image", mimeType: "image/png" },
687
+ { type: "text", text: "Period 4" },
688
+ ],
689
+ }),
690
+ ).toEqual({ ok: true });
691
+ expect(appendMock).toHaveBeenCalledWith({
692
+ content: [
693
+ { type: "text", text: "Open Grade 5 Math" },
694
+ { type: "text", text: "Period 4" },
695
+ ],
696
+ });
697
+
698
+ expect(handlers.sendMessage?.({ text: "typed instead" })).toEqual({
699
+ ok: true,
700
+ });
701
+ expect(appendMock).toHaveBeenLastCalledWith({
702
+ content: [{ type: "text", text: "typed instead" }],
703
+ });
704
+
705
+ expect(
706
+ handlers.sendMessage?.({
707
+ role: "user",
708
+ content: [{ type: "image", data: "image", mimeType: "image/png" }],
709
+ }),
710
+ ).toEqual({
711
+ isError: true,
712
+ ok: false,
713
+ reason: "no text content to append",
714
+ });
715
+
716
+ expect(handlers.sendMessage?.({ role: "user" })).toEqual({
717
+ isError: true,
718
+ ok: false,
719
+ reason: "unrecognised params shape",
720
+ });
721
+ expect(appendMock).toHaveBeenCalledTimes(2);
722
+ });
723
+
724
+ it("uses caller UI handlers and keeps data-plane handlers on the host", async () => {
725
+ const host: McpAppsHost = {
726
+ loadResource: vi.fn(async ({ uri }) => ({
727
+ uri,
728
+ mimeType: "text/html;profile=mcp-app" as const,
729
+ html: "",
730
+ })),
731
+ callTool: vi.fn(),
732
+ readResource: vi.fn(),
733
+ listResources: vi.fn(),
734
+ };
735
+ const requestDisplayMode = vi.fn(({ mode }) => ({ mode }));
736
+ const updateModelContext = vi.fn();
737
+ const openLink = vi.fn();
738
+ const sendMessage = vi.fn();
739
+ const onInitialized = vi.fn();
740
+
741
+ render(
742
+ <Harness
743
+ host={host}
744
+ handlers={{
745
+ requestDisplayMode,
746
+ updateModelContext,
747
+ openLink,
748
+ sendMessage,
749
+ onInitialized,
750
+ }}
751
+ />,
752
+ );
753
+ await waitFor(() => expect(framePropsMock).toHaveBeenCalled());
754
+ const handlers = framePropsMock.mock.lastCall?.[0]
755
+ .handlers as McpAppBridgeHandlers;
756
+
757
+ expect(await handlers.requestDisplayMode?.({ mode: "fullscreen" })).toEqual(
758
+ { mode: "fullscreen" },
759
+ );
760
+ await handlers.updateModelContext?.({ text: "context" });
761
+ await handlers.openLink?.({ url: "https://example.com" });
762
+ await handlers.sendMessage?.({ text: "hello" });
763
+ handlers.onInitialized?.();
764
+ await handlers.callTool?.({ name: "search" });
765
+ await handlers.readResource?.({ uri: "ui://resource" });
766
+ await handlers.listResources?.();
767
+
768
+ expect(requestDisplayMode).toHaveBeenCalledWith({ mode: "fullscreen" });
769
+ expect(updateModelContext).toHaveBeenCalledWith({ text: "context" });
770
+ expect(openLink).toHaveBeenCalledWith({ url: "https://example.com" });
771
+ expect(sendMessage).toHaveBeenCalledWith({ text: "hello" });
772
+ expect(onInitialized).toHaveBeenCalledOnce();
773
+ expect(host.callTool).toHaveBeenCalledWith({ name: "search" });
774
+ expect(host.readResource).toHaveBeenCalledWith({ uri: "ui://resource" });
775
+ expect(host.listResources).toHaveBeenCalledWith(undefined);
776
+ });
400
777
  });