@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
@@ -0,0 +1,132 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { act, renderHook } from "@testing-library/react";
4
+ import type { AssistantClient } from "@assistant-ui/store";
5
+ import { describe, expect, it, vi } from "vitest";
6
+ import {
7
+ shallowEqualRecords,
8
+ useModelContextSnapshot,
9
+ type ModelContextSnapshotSource,
10
+ } from "./useModelContextSnapshot";
11
+
12
+ const EMPTY: Readonly<Record<string, string>> = Object.freeze({});
13
+
14
+ const createSource = (
15
+ overrides: Partial<
16
+ ModelContextSnapshotSource<Readonly<Record<string, string>>>
17
+ > = {},
18
+ ) => {
19
+ const listeners = new Set<() => void>();
20
+ const state = { tools: EMPTY as Record<string, string> };
21
+ const unsubscribe = vi.fn();
22
+ const source: ModelContextSnapshotSource<Readonly<Record<string, string>>> = {
23
+ empty: EMPTY,
24
+ read: vi.fn(() => ({ ...state.tools })),
25
+ subscribe: vi.fn((_aui, onChange) => {
26
+ listeners.add(onChange);
27
+ return () => {
28
+ listeners.delete(onChange);
29
+ unsubscribe();
30
+ };
31
+ }),
32
+ isEqual: shallowEqualRecords,
33
+ ...overrides,
34
+ };
35
+ return {
36
+ source,
37
+ state,
38
+ unsubscribe,
39
+ notify: () => listeners.forEach((listener) => listener()),
40
+ };
41
+ };
42
+
43
+ const aui = {} as AssistantClient;
44
+
45
+ describe("useModelContextSnapshot", () => {
46
+ it("seeds from the source on the first render when enabled", () => {
47
+ const { source } = createSource();
48
+ (source.read as ReturnType<typeof vi.fn>).mockReturnValue({ a: "1" });
49
+
50
+ const { result } = renderHook(() =>
51
+ useModelContextSnapshot(aui, true, source),
52
+ );
53
+
54
+ expect(result.current).toEqual({ a: "1" });
55
+ });
56
+
57
+ it("neither reads nor subscribes while disabled", () => {
58
+ const { source } = createSource();
59
+
60
+ const { result } = renderHook(() =>
61
+ useModelContextSnapshot(aui, false, source),
62
+ );
63
+
64
+ expect(result.current).toBe(source.empty);
65
+ expect(source.read).not.toHaveBeenCalled();
66
+ expect(source.subscribe).not.toHaveBeenCalled();
67
+ });
68
+
69
+ it("keeps the previous reference when isEqual reports no change", () => {
70
+ const { source, state, notify } = createSource();
71
+ state.tools = { a: "1" };
72
+
73
+ const { result } = renderHook(() =>
74
+ useModelContextSnapshot(aui, true, source),
75
+ );
76
+ const first = result.current;
77
+
78
+ act(() => {
79
+ state.tools = { a: "1" };
80
+ notify();
81
+ });
82
+ expect(result.current).toBe(first);
83
+
84
+ act(() => {
85
+ state.tools = { a: "2" };
86
+ notify();
87
+ });
88
+ expect(result.current).not.toBe(first);
89
+ expect(result.current).toEqual({ a: "2" });
90
+ });
91
+
92
+ it("takes every notification when the source omits isEqual", () => {
93
+ const { source, state, notify } = createSource({ isEqual: undefined });
94
+ state.tools = { a: "1" };
95
+
96
+ const { result } = renderHook(() =>
97
+ useModelContextSnapshot(aui, true, source),
98
+ );
99
+ const first = result.current;
100
+
101
+ act(() => notify());
102
+
103
+ expect(result.current).not.toBe(first);
104
+ expect(result.current).toEqual({ a: "1" });
105
+ });
106
+
107
+ it("unsubscribes on unmount", () => {
108
+ const { source, unsubscribe } = createSource();
109
+ const { unmount } = renderHook(() =>
110
+ useModelContextSnapshot(aui, true, source),
111
+ );
112
+
113
+ unmount();
114
+
115
+ expect(unsubscribe).toHaveBeenCalled();
116
+ });
117
+ });
118
+
119
+ describe("shallowEqualRecords", () => {
120
+ it("compares own keys by identity", () => {
121
+ expect(shallowEqualRecords({ a: 1 }, { a: 1 })).toBe(true);
122
+ expect(shallowEqualRecords({ a: 1 }, { a: 2 })).toBe(false);
123
+ expect(shallowEqualRecords({ a: 1 }, { a: 1, b: 2 })).toBe(false);
124
+ expect(shallowEqualRecords({ a: undefined }, { b: undefined })).toBe(false);
125
+ });
126
+
127
+ it("does not match a key inherited from Object.prototype", () => {
128
+ expect(
129
+ shallowEqualRecords({ toString: Object.prototype.toString }, { a: 1 }),
130
+ ).toBe(false);
131
+ });
132
+ });
@@ -0,0 +1,67 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import type { AssistantClient } from "@assistant-ui/store";
5
+ import type { Unsubscribe } from "@assistant-ui/core";
6
+
7
+ /**
8
+ * Where a snapshot comes from and what counts as a change to it. Declared once
9
+ * per consumer at module scope: the subscription effect keys on this object, so
10
+ * an inline source without an `isEqual` re-reads itself into a render loop.
11
+ */
12
+ export type ModelContextSnapshotSource<T> = {
13
+ /** Seeds the snapshot when it starts disabled. */
14
+ readonly empty: T;
15
+ /** Projects the fields the consumer observes out of the live model context. */
16
+ read(aui: AssistantClient): T;
17
+ subscribe(aui: AssistantClient, onChange: () => void): Unsubscribe;
18
+ /**
19
+ * An equal pair keeps the previous reference. Omitted by a consumer that
20
+ * re-derives on every notification, where a caller republishing an unchanged
21
+ * tool set is how it asks to be re-run.
22
+ */
23
+ isEqual?(previous: T, next: T): boolean;
24
+ };
25
+
26
+ /**
27
+ * `getModelContext()` rebuilds its result on every call, so a projection of the
28
+ * fields a consumer observes is held in state and refreshed on notification
29
+ * rather than read during render. Keeping the previous reference for an equal
30
+ * projection is what stops a notification carrying no observable change from
31
+ * invalidating everything derived from it.
32
+ */
33
+ export const useModelContextSnapshot = <T>(
34
+ aui: AssistantClient,
35
+ enabled: boolean,
36
+ source: ModelContextSnapshotSource<T>,
37
+ ): T => {
38
+ const [snapshot, setSnapshot] = useState(() =>
39
+ enabled ? source.read(aui) : source.empty,
40
+ );
41
+
42
+ useEffect(() => {
43
+ if (!enabled) return undefined;
44
+ const read = () => {
45
+ const next = source.read(aui);
46
+ setSnapshot((previous) =>
47
+ source.isEqual?.(previous, next) ? previous : next,
48
+ );
49
+ };
50
+ read();
51
+ return source.subscribe(aui, read);
52
+ }, [aui, enabled, source]);
53
+
54
+ return snapshot;
55
+ };
56
+
57
+ /** Shallow own-key comparison, for a projection whose values are records. */
58
+ export const shallowEqualRecords = (
59
+ previous: Readonly<Record<string, unknown>>,
60
+ next: Readonly<Record<string, unknown>>,
61
+ ): boolean => {
62
+ const keys = Object.keys(previous);
63
+ if (keys.length !== Object.keys(next).length) return false;
64
+ return keys.every(
65
+ (key) => Object.hasOwn(next, key) && previous[key] === next[key],
66
+ );
67
+ };
@@ -0,0 +1,141 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { act, renderHook } from "@testing-library/react";
4
+ import { startTransition, Suspense, type PropsWithChildren } from "react";
5
+ import { describe, expect, it, vi } from "vitest";
6
+ import {
7
+ unstable_useSlashCommandAdapter,
8
+ type Unstable_SlashCommand,
9
+ } from "./useSlashCommandAdapter";
10
+
11
+ const command = (
12
+ label: string,
13
+ execute: () => void,
14
+ ): Unstable_SlashCommand => ({
15
+ id: "run",
16
+ label,
17
+ execute,
18
+ });
19
+
20
+ describe("unstable_useSlashCommandAdapter", () => {
21
+ it("keeps commands scoped to committed renders", () => {
22
+ const executeA = vi.fn();
23
+ const executeB = vi.fn();
24
+ const commandsA = [command("Workspace A", executeA)];
25
+ const commandsB = [command("Workspace B", executeB)];
26
+ const renderedLabel = vi.fn();
27
+ const interruptedRender = vi.fn();
28
+ const pending = new Promise<never>(() => {});
29
+ let blocked = false;
30
+
31
+ const Blocker = () => {
32
+ if (blocked) {
33
+ interruptedRender();
34
+ throw pending;
35
+ }
36
+ return null;
37
+ };
38
+ const Wrapper = ({ children }: PropsWithChildren) => (
39
+ <Suspense fallback={null}>
40
+ {children}
41
+ <Blocker />
42
+ </Suspense>
43
+ );
44
+
45
+ const { result, rerender } = renderHook(
46
+ ({ commands }) => {
47
+ renderedLabel(commands[0]?.label);
48
+ return unstable_useSlashCommandAdapter({ commands });
49
+ },
50
+ {
51
+ initialProps: { commands: commandsA },
52
+ wrapper: Wrapper,
53
+ },
54
+ );
55
+
56
+ act(() => {
57
+ blocked = true;
58
+ startTransition(() => rerender({ commands: commandsB }));
59
+ });
60
+
61
+ expect(interruptedRender).toHaveBeenCalled();
62
+ expect(renderedLabel).toHaveBeenCalledWith("Workspace B");
63
+ expect(result.current.adapter.search?.("")).toEqual([
64
+ { id: "run", type: "command", label: "Workspace A" },
65
+ ]);
66
+
67
+ result.current.action.onExecute({
68
+ id: "run",
69
+ type: "command",
70
+ label: "Workspace A",
71
+ });
72
+ expect(executeA).toHaveBeenCalledOnce();
73
+ expect(executeB).not.toHaveBeenCalled();
74
+ });
75
+
76
+ it("updates search while keeping actions stable after commands commit", () => {
77
+ const executeA = vi.fn();
78
+ const executeB = vi.fn();
79
+ const commandsA = [command("Workspace A", executeA)];
80
+ const commandsB = [command("Workspace B", executeB)];
81
+ const { result, rerender } = renderHook(
82
+ ({ commands }) => unstable_useSlashCommandAdapter({ commands }),
83
+ { initialProps: { commands: commandsA } },
84
+ );
85
+ const initial = result.current;
86
+
87
+ rerender({ commands: commandsB });
88
+
89
+ expect(result.current.adapter).not.toBe(initial.adapter);
90
+ expect(result.current.action).toBe(initial.action);
91
+ expect(result.current.adapter.search?.("")).toEqual([
92
+ { id: "run", type: "command", label: "Workspace B" },
93
+ ]);
94
+
95
+ result.current.action.onExecute({
96
+ id: "run",
97
+ type: "command",
98
+ label: "Workspace B",
99
+ });
100
+ expect(executeA).not.toHaveBeenCalled();
101
+ expect(executeB).toHaveBeenCalledOnce();
102
+ });
103
+
104
+ it("matches the displayed label for a command with no explicit label", () => {
105
+ const { result } = renderHook(() =>
106
+ unstable_useSlashCommandAdapter({
107
+ commands: [{ id: "summarize", execute: vi.fn() }],
108
+ }),
109
+ );
110
+
111
+ expect(result.current.adapter.search?.("/")).toEqual([
112
+ { id: "summarize", type: "command", label: "/summarize" },
113
+ ]);
114
+ expect(result.current.adapter.search?.("sum")).toEqual([
115
+ { id: "summarize", type: "command", label: "/summarize" },
116
+ ]);
117
+ expect(result.current.adapter.search?.("nope")).toEqual([]);
118
+ });
119
+
120
+ it("keeps the adapter stable for equivalent inline commands", () => {
121
+ const executeA = vi.fn();
122
+ const executeB = vi.fn();
123
+ const { result, rerender } = renderHook(
124
+ ({ execute }) =>
125
+ unstable_useSlashCommandAdapter({
126
+ commands: [{ id: "run", label: "Run", execute }],
127
+ }),
128
+ { initialProps: { execute: executeA } },
129
+ );
130
+ const initialAdapter = result.current.adapter;
131
+
132
+ rerender({ execute: executeB });
133
+
134
+ expect(result.current.adapter).toBe(initialAdapter);
135
+ act(() =>
136
+ result.current.action.onExecute(result.current.adapter.search!("")[0]!),
137
+ );
138
+ expect(executeA).not.toHaveBeenCalled();
139
+ expect(executeB).toHaveBeenCalledOnce();
140
+ });
141
+ });
@@ -1,11 +1,12 @@
1
1
  "use client";
2
2
 
3
- import { useMemo, useRef } from "react";
3
+ import { useLayoutEffect, useMemo, useRef } from "react";
4
4
  import type {
5
5
  Unstable_TriggerAdapter,
6
6
  Unstable_TriggerItem,
7
7
  } from "@assistant-ui/core";
8
8
  import type { Unstable_IconComponent } from "./useMentionAdapter";
9
+ import { matchesTriggerItemQuery } from "../primitives/composer/trigger/matchesTriggerItemQuery";
9
10
 
10
11
  export type Unstable_SlashCommand = {
11
12
  readonly id: string;
@@ -61,34 +62,51 @@ export function unstable_useSlashCommandAdapter(
61
62
  const { commands, removeOnExecute } = options;
62
63
 
63
64
  const commandsRef = useRef(commands);
64
- commandsRef.current = commands;
65
+ const committedItemsRef = useRef<readonly Unstable_TriggerItem[]>(undefined);
66
+ const nextItems = commands.map(toItem);
67
+ // A referential cache, never state: `items` is always structurally equal to
68
+ // `nextItems`, so the render-time read cannot change what the adapter shows.
69
+ const items = areTriggerItemsEqual(committedItemsRef.current, nextItems)
70
+ ? committedItemsRef.current
71
+ : nextItems;
65
72
 
66
- return useMemo(() => {
67
- const adapter: Unstable_TriggerAdapter = {
73
+ // The adapter's callbacks must only observe commands from committed renders.
74
+ useLayoutEffect(() => {
75
+ commandsRef.current = commands;
76
+ committedItemsRef.current = items;
77
+ }, [commands, items]);
78
+
79
+ const adapter = useMemo<Unstable_TriggerAdapter>(
80
+ () => ({
68
81
  categories: () => [],
69
82
  categoryItems: () => [],
70
83
  search: (query: string) => {
71
84
  const lower = query.toLowerCase();
72
- return commandsRef.current
73
- .filter((c) => matchesQuery(c, lower))
74
- .map(toItem);
85
+ return items.filter((item) => matchesTriggerItemQuery(item, lower));
75
86
  },
76
- };
87
+ }),
88
+ [items],
89
+ );
77
90
 
78
- const action: Unstable_SlashCommandAction = {
91
+ const action = useMemo<Unstable_SlashCommandAction>(
92
+ () => ({
79
93
  onExecute: (item) => {
80
94
  commandsRef.current.find((c) => c.id === item.id)?.execute();
81
95
  },
82
96
  ...(removeOnExecute !== undefined ? { removeOnExecute } : {}),
83
- };
97
+ }),
98
+ [removeOnExecute],
99
+ );
84
100
 
85
- return {
101
+ return useMemo(
102
+ () => ({
86
103
  adapter,
87
104
  action,
88
105
  ...(options.iconMap ? { iconMap: options.iconMap } : {}),
89
106
  ...(options.fallbackIcon ? { fallbackIcon: options.fallbackIcon } : {}),
90
- };
91
- }, [removeOnExecute, options.iconMap, options.fallbackIcon]);
107
+ }),
108
+ [adapter, action, options.iconMap, options.fallbackIcon],
109
+ );
92
110
  }
93
111
 
94
112
  function toItem(cmd: Unstable_SlashCommand): Unstable_TriggerItem {
@@ -101,10 +119,20 @@ function toItem(cmd: Unstable_SlashCommand): Unstable_TriggerItem {
101
119
  };
102
120
  }
103
121
 
104
- function matchesQuery(cmd: Unstable_SlashCommand, lower: string): boolean {
105
- if (!lower) return true;
106
- if (cmd.id.toLowerCase().includes(lower)) return true;
107
- if (cmd.label?.toLowerCase().includes(lower)) return true;
108
- if (cmd.description?.toLowerCase().includes(lower)) return true;
109
- return false;
122
+ function areTriggerItemsEqual(
123
+ previous: readonly Unstable_TriggerItem[] | undefined,
124
+ next: readonly Unstable_TriggerItem[],
125
+ ): previous is readonly Unstable_TriggerItem[] {
126
+ if (!previous || previous.length !== next.length) return false;
127
+
128
+ return previous.every((item, index) => {
129
+ const nextItem = next[index];
130
+ return (
131
+ nextItem !== undefined &&
132
+ item.id === nextItem.id &&
133
+ item.label === nextItem.label &&
134
+ item.description === nextItem.description &&
135
+ item.metadata?.icon === nextItem.metadata?.icon
136
+ );
137
+ });
110
138
  }
@@ -0,0 +1,92 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { resource } from "@assistant-ui/tap";
3
+ import type { Tool } from "assistant-stream";
4
+ import { toWebMcpTool } from "./convertTools";
5
+ import type { WebMcpHost } from "./webmcp-host";
6
+
7
+ export type WebMcpRegistrationProps = {
8
+ host: WebMcpHost;
9
+ name: string;
10
+ /** Re-registers when it changes; an execute-only edit reads through instead. */
11
+ signature: string;
12
+ tool: Tool<any, any>;
13
+ };
14
+
15
+ // The explainer specifies NotAllowedError for a page whose tools permission is
16
+ // off, which a cross-origin iframe without allow="tools" reaches routinely. It
17
+ // documents no rejection for a name already taken, so that stays a hedge.
18
+ const notPermitted = (error: unknown) =>
19
+ (error as { name?: unknown } | null | undefined)?.name === "NotAllowedError";
20
+
21
+ const notPermittedMessage = (name: string) =>
22
+ `[assistant-ui] WebMCP registration for tool "${name}" was not permitted; the page's tools permission is disabled.`;
23
+
24
+ const useWebMcpRegistration = ({
25
+ host,
26
+ name,
27
+ signature,
28
+ tool,
29
+ }: WebMcpRegistrationProps): string | null => {
30
+ // A refused name is remembered for as long as the tool stays in the model
31
+ // context, so a permanent collision warns once rather than on every sync.
32
+ const [refused, setRefused] = useState(false);
33
+ const toolRef = useRef(tool);
34
+ // Commit phase, so an abandoned render cannot hand the host a tool the tree
35
+ // never committed.
36
+ useEffect(() => {
37
+ toolRef.current = tool;
38
+ }, [tool]);
39
+
40
+ useEffect(() => {
41
+ if (refused) return undefined;
42
+
43
+ const lifecycle = new AbortController();
44
+ let live = true;
45
+ let dispose: (() => void) | undefined;
46
+
47
+ const refuse = (message: string, error: unknown) => {
48
+ if (!live) return;
49
+ live = false;
50
+ setRefused(true);
51
+ console.warn(message, error);
52
+ };
53
+
54
+ try {
55
+ dispose = host.registerTool(
56
+ toWebMcpTool(name, () => toolRef.current, lifecycle.signal),
57
+ (error) =>
58
+ refuse(
59
+ notPermitted(error)
60
+ ? notPermittedMessage(name)
61
+ : `[assistant-ui] WebMCP registration for tool "${name}" failed (name may already be registered).`,
62
+ error,
63
+ ),
64
+ );
65
+ } catch (error) {
66
+ refuse(
67
+ notPermitted(error)
68
+ ? notPermittedMessage(name)
69
+ : `[assistant-ui] Skipping WebMCP registration for tool "${name}": registerTool failed (name may already be registered).`,
70
+ error,
71
+ );
72
+ return undefined;
73
+ }
74
+
75
+ return () => {
76
+ live = false;
77
+ lifecycle.abort();
78
+ try {
79
+ dispose();
80
+ } catch (error) {
81
+ console.warn(
82
+ `[assistant-ui] Unregistering WebMCP tool "${name}" failed.`,
83
+ error,
84
+ );
85
+ }
86
+ };
87
+ }, [host, name, signature, refused]);
88
+
89
+ return refused ? null : name;
90
+ };
91
+
92
+ export const WebMcpRegistrationResource = resource(useWebMcpRegistration);