@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,88 +1,164 @@
1
1
  "use client";
2
- import { invokeCallbackSafely } from "../utils/invokeCallbackSafely.js";
3
- import { useEffect, useRef, useState } from "@assistant-ui/tap/react-shim";
2
+ import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
3
+ import { useEffect, useLayoutEffect, useRef, useState } from "@assistant-ui/tap/react-shim";
4
4
  import { jsx } from "react/jsx-runtime";
5
- import { SafeContentFrame } from "safe-content-frame";
5
+ import { invokeUserCallback } from "@assistant-ui/core/internal";
6
+ import { SafeContentFrame, isShimLoadError } from "safe-content-frame";
6
7
  //#region src/sandbox-host/SandboxHost.tsx
7
8
  const DEFAULT_PRODUCT = "assistant-ui-sandbox";
8
9
  const DEFAULT_MAX_HEIGHT = 800;
10
+ const LOAD_TIMEOUT_MS = 1e4;
9
11
  function isSandboxFrameMessage(event, frame) {
10
12
  return event.source === frame.iframe.contentWindow && event.origin === frame.origin;
11
13
  }
12
- function SandboxHost({ content, contentKey, sandbox, maxHeight = DEFAULT_MAX_HEIGHT, createBridge, onError, containerProps }) {
14
+ function SandboxHost(t0) {
15
+ const $ = c(21);
16
+ const { content, contentKey, sandbox, maxHeight: t1, createBridge, onError, containerProps } = t0;
17
+ const maxHeight = t1 === void 0 ? DEFAULT_MAX_HEIGHT : t1;
13
18
  const containerRef = useRef(null);
14
19
  const [contentHeight, setContentHeight] = useState(void 0);
15
- const liveRef = useRef(null);
16
- liveRef.current = {
17
- content,
18
- sandbox,
19
- createBridge,
20
- onError
21
- };
22
- useEffect(() => {
23
- const container = containerRef.current;
24
- if (!container) return;
25
- let cancelled = false;
26
- let frame = null;
27
- let bridge = null;
28
- let onMessage = null;
29
- const { content: liveContent, sandbox: sb } = liveRef.current;
30
- const scf = new SafeContentFrame(sb?.product ?? DEFAULT_PRODUCT, {
31
- ...sb?.sandbox !== void 0 && { sandbox: sb.sandbox },
32
- ...sb?.useShadowDom !== void 0 && { useShadowDom: sb.useShadowDom },
33
- ...sb?.enableBrowserCaching !== void 0 && { enableBrowserCaching: sb.enableBrowserCaching },
34
- ...sb?.salt !== void 0 && { salt: sb.salt }
35
- });
36
- const renderOpts = sb?.unsafeDocumentWrite !== void 0 ? { unsafeDocumentWrite: sb.unsafeDocumentWrite } : void 0;
37
- scf.renderHtml(liveContent.html, container, renderOpts).then((rendered) => {
38
- if (cancelled) {
39
- rendered.dispose();
40
- return;
41
- }
42
- frame = rendered;
43
- bridge = liveRef.current.createBridge({
44
- iframe: rendered.iframe,
45
- origin: rendered.origin,
46
- sendMessage: rendered.sendMessage
47
- }, { setHeight: (height) => {
48
- if (typeof height === "number" && Number.isFinite(height) && height > 0) setContentHeight(height);
49
- } });
50
- onMessage = (event) => {
51
- if (!isSandboxFrameMessage(event, rendered)) return;
52
- bridge?.onMessage(event);
20
+ let t2;
21
+ if ($[0] !== content || $[1] !== createBridge || $[2] !== onError || $[3] !== sandbox) {
22
+ t2 = {
23
+ content,
24
+ sandbox,
25
+ createBridge,
26
+ onError
27
+ };
28
+ $[0] = content;
29
+ $[1] = createBridge;
30
+ $[2] = onError;
31
+ $[3] = sandbox;
32
+ $[4] = t2;
33
+ } else t2 = $[4];
34
+ const liveRef = useRef(t2);
35
+ let t3;
36
+ let t4;
37
+ if ($[5] !== content || $[6] !== createBridge || $[7] !== onError || $[8] !== sandbox) {
38
+ t3 = () => {
39
+ liveRef.current = {
40
+ content,
41
+ sandbox,
42
+ createBridge,
43
+ onError
53
44
  };
54
- window.addEventListener("message", onMessage);
55
- }).catch((err) => {
56
- if (cancelled) return;
57
- frame?.dispose();
58
- frame = null;
59
- const error = err instanceof Error ? err : new Error(String(err));
60
- invokeCallbackSafely(() => liveRef.current.onError?.(error), "SandboxHost onError");
61
- });
62
- return () => {
63
- cancelled = true;
64
- if (onMessage) {
65
- window.removeEventListener("message", onMessage);
66
- onMessage = null;
67
- }
68
- bridge?.dispose();
69
- bridge = null;
70
- frame?.dispose();
71
- frame = null;
72
- setContentHeight(void 0);
73
45
  };
74
- }, [contentKey]);
46
+ t4 = [
47
+ content,
48
+ sandbox,
49
+ createBridge,
50
+ onError
51
+ ];
52
+ $[5] = content;
53
+ $[6] = createBridge;
54
+ $[7] = onError;
55
+ $[8] = sandbox;
56
+ $[9] = t3;
57
+ $[10] = t4;
58
+ } else {
59
+ t3 = $[9];
60
+ t4 = $[10];
61
+ }
62
+ useLayoutEffect(t3, t4);
63
+ let t5;
64
+ if ($[11] === Symbol.for("react.memo_cache_sentinel")) {
65
+ t5 = () => {
66
+ const container = containerRef.current;
67
+ if (!container) return;
68
+ let cancelled = false;
69
+ let frame = null;
70
+ let bridge = null;
71
+ let onMessage = null;
72
+ const { content: liveContent, sandbox: sb } = liveRef.current;
73
+ const reportError = (err) => {
74
+ const error = err instanceof Error ? err : new Error(String(err));
75
+ invokeUserCallback("assistant-ui", "SandboxHost onError", liveRef.current.onError?.bind(liveRef.current), error);
76
+ };
77
+ const scf = new SafeContentFrame(sb?.product ?? DEFAULT_PRODUCT, {
78
+ ...sb?.sandbox !== void 0 && { sandbox: sb.sandbox },
79
+ ...sb?.useShadowDom !== void 0 && { useShadowDom: sb.useShadowDom },
80
+ ...sb?.enableBrowserCaching !== void 0 && { enableBrowserCaching: sb.enableBrowserCaching },
81
+ ...sb?.salt !== void 0 && { salt: sb.salt }
82
+ });
83
+ const renderOpts = sb?.unsafeDocumentWrite !== void 0 ? { unsafeDocumentWrite: sb.unsafeDocumentWrite } : void 0;
84
+ scf.renderHtml(liveContent.html, container, renderOpts).then((rendered) => {
85
+ if (cancelled) {
86
+ rendered.dispose();
87
+ return;
88
+ }
89
+ frame = rendered;
90
+ bridge = liveRef.current.createBridge({
91
+ iframe: rendered.iframe,
92
+ origin: rendered.origin,
93
+ sendMessage: rendered.sendMessage
94
+ }, { setHeight: (height) => {
95
+ if (typeof height === "number" && Number.isFinite(height) && height > 0) setContentHeight(height);
96
+ } });
97
+ onMessage = (event) => {
98
+ if (!isSandboxFrameMessage(event, rendered)) return;
99
+ bridge?.onMessage(event);
100
+ };
101
+ window.addEventListener("message", onMessage);
102
+ rendered.fullyLoadedPromiseWithTimeout(LOAD_TIMEOUT_MS).catch((err_0) => {
103
+ if (cancelled) return;
104
+ if (isShimLoadError(err_0) && err_0.code === "render-timeout") return;
105
+ reportError(err_0);
106
+ });
107
+ }).catch((err_1) => {
108
+ if (cancelled) return;
109
+ frame?.dispose();
110
+ frame = null;
111
+ reportError(err_1);
112
+ });
113
+ return () => {
114
+ cancelled = true;
115
+ if (onMessage) {
116
+ window.removeEventListener("message", onMessage);
117
+ onMessage = null;
118
+ }
119
+ bridge?.dispose();
120
+ bridge = null;
121
+ frame?.dispose();
122
+ frame = null;
123
+ setContentHeight(void 0);
124
+ };
125
+ };
126
+ $[11] = t5;
127
+ } else t5 = $[11];
128
+ let t6;
129
+ if ($[12] !== contentKey) {
130
+ t6 = [contentKey];
131
+ $[12] = contentKey;
132
+ $[13] = t6;
133
+ } else t6 = $[13];
134
+ useEffect(t5, t6);
75
135
  const resolvedHeight = contentHeight != null ? Math.min(contentHeight, maxHeight) : void 0;
76
- const mergedStyle = resolvedHeight != null ? {
77
- ...sandbox?.style,
78
- height: resolvedHeight
79
- } : sandbox?.style;
80
- return /* @__PURE__ */ jsx("div", {
81
- ...containerProps,
82
- ref: containerRef,
83
- className: sandbox?.className,
84
- style: mergedStyle
85
- });
136
+ let t7;
137
+ if ($[14] !== resolvedHeight || $[15] !== sandbox?.style) {
138
+ t7 = resolvedHeight != null ? {
139
+ ...sandbox?.style,
140
+ height: resolvedHeight
141
+ } : sandbox?.style;
142
+ $[14] = resolvedHeight;
143
+ $[15] = sandbox?.style;
144
+ $[16] = t7;
145
+ } else t7 = $[16];
146
+ const mergedStyle = t7;
147
+ const t8 = sandbox?.className;
148
+ let t9;
149
+ if ($[17] !== containerProps || $[18] !== mergedStyle || $[19] !== t8) {
150
+ t9 = /* @__PURE__ */ jsx("div", {
151
+ ...containerProps,
152
+ ref: containerRef,
153
+ className: t8,
154
+ style: mergedStyle
155
+ });
156
+ $[17] = containerProps;
157
+ $[18] = mergedStyle;
158
+ $[19] = t8;
159
+ $[20] = t9;
160
+ } else t9 = $[20];
161
+ return t9;
86
162
  }
87
163
  //#endregion
88
164
  export { SandboxHost, isSandboxFrameMessage };
@@ -1 +1 @@
1
- {"version":3,"file":"SandboxHost.js","names":["CSSProperties","useEffect","useRef","useState","RenderedFrame","SafeContentFrame","SandboxOption","invokeCallbackSafely","DEFAULT_PRODUCT","DEFAULT_MAX_HEIGHT","SandboxHostConfig","sandbox","useShadowDom","enableBrowserCaching","salt","product","className","style","unsafeDocumentWrite","SandboxHostFrame","Pick","SandboxHostApi","setHeight","height","SandboxBridge","onMessage","event","MessageEvent","dispose","SandboxContent","html","SandboxHostProps","content","contentKey","maxHeight","createBridge","frame","host","onError","error","Error","containerProps","Record","isSandboxFrameMessage","iframe","HTMLIFrameElement","origin","source","contentWindow","LiveSnapshot","SandboxHost","containerRef","HTMLDivElement","contentHeight","setContentHeight","undefined","liveRef","current","container","cancelled","bridge","liveContent","sb","scf","renderOpts","renderHtml","then","rendered","hostApi","Number","isFinite","sendMessage","window","addEventListener","catch","err","String","removeEventListener","resolvedHeight","Math","min","mergedStyle"],"sources":["../../src/sandbox-host/SandboxHost.tsx"],"sourcesContent":["\"use client\";\n\nimport { type CSSProperties, useEffect, useRef, useState } from \"react\";\nimport {\n type RenderedFrame,\n SafeContentFrame,\n type SandboxOption,\n} from \"safe-content-frame\";\nimport { invokeCallbackSafely } from \"../utils/invokeCallbackSafely\";\n\nconst DEFAULT_PRODUCT = \"assistant-ui-sandbox\";\nconst DEFAULT_MAX_HEIGHT = 800;\n\nexport type SandboxHostConfig = {\n sandbox?: SandboxOption[];\n useShadowDom?: boolean;\n enableBrowserCaching?: boolean;\n salt?: string;\n product?: string;\n className?: string;\n style?: CSSProperties;\n unsafeDocumentWrite?: boolean;\n};\n\nexport type SandboxHostFrame = Pick<\n RenderedFrame,\n \"iframe\" | \"origin\" | \"sendMessage\"\n>;\n\nexport type SandboxHostApi = {\n setHeight: (height: number) => void;\n};\n\nexport type SandboxBridge = {\n onMessage: (event: MessageEvent) => void;\n dispose: () => void;\n};\n\nexport type SandboxContent = { html: string };\n\nexport type SandboxHostProps = {\n content: SandboxContent;\n contentKey: string;\n sandbox?: SandboxHostConfig | undefined;\n maxHeight?: number | undefined;\n createBridge: (\n frame: SandboxHostFrame,\n host: SandboxHostApi,\n ) => SandboxBridge;\n onError?: ((error: Error) => void) | undefined;\n containerProps?: Record<string, string | undefined> | undefined;\n};\n\nexport function isSandboxFrameMessage(\n event: MessageEvent,\n frame: { iframe: HTMLIFrameElement; origin: string },\n): boolean {\n return (\n event.source === frame.iframe.contentWindow && event.origin === frame.origin\n );\n}\n\ntype LiveSnapshot = {\n content: SandboxContent;\n sandbox: SandboxHostConfig | undefined;\n createBridge: SandboxHostProps[\"createBridge\"];\n onError: SandboxHostProps[\"onError\"];\n};\n\nexport function SandboxHost({\n content,\n contentKey,\n sandbox,\n maxHeight = DEFAULT_MAX_HEIGHT,\n createBridge,\n onError,\n containerProps,\n}: SandboxHostProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [contentHeight, setContentHeight] = useState<number | undefined>(\n undefined,\n );\n\n const liveRef = useRef<LiveSnapshot>(null!);\n liveRef.current = { content, sandbox, createBridge, onError };\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n let cancelled = false;\n let frame: RenderedFrame | null = null;\n let bridge: SandboxBridge | null = null;\n let onMessage: ((event: MessageEvent) => void) | null = null;\n\n const { content: liveContent, sandbox: sb } = liveRef.current;\n\n const scf = new SafeContentFrame(sb?.product ?? DEFAULT_PRODUCT, {\n ...(sb?.sandbox !== undefined && { sandbox: sb.sandbox }),\n ...(sb?.useShadowDom !== undefined && { useShadowDom: sb.useShadowDom }),\n ...(sb?.enableBrowserCaching !== undefined && {\n enableBrowserCaching: sb.enableBrowserCaching,\n }),\n ...(sb?.salt !== undefined && { salt: sb.salt }),\n });\n\n const renderOpts =\n sb?.unsafeDocumentWrite !== undefined\n ? { unsafeDocumentWrite: sb.unsafeDocumentWrite }\n : undefined;\n\n scf\n .renderHtml(liveContent.html, container, renderOpts)\n .then((rendered) => {\n if (cancelled) {\n rendered.dispose();\n return;\n }\n frame = rendered;\n\n const hostApi: SandboxHostApi = {\n setHeight: (height) => {\n if (\n typeof height === \"number\" &&\n Number.isFinite(height) &&\n height > 0\n ) {\n setContentHeight(height);\n }\n },\n };\n\n bridge = liveRef.current.createBridge(\n {\n iframe: rendered.iframe,\n origin: rendered.origin,\n sendMessage: rendered.sendMessage,\n },\n hostApi,\n );\n\n // Single owner of the window listener; the cross-origin guard runs\n // here so the bridge only sees frame-validated messages.\n onMessage = (event) => {\n if (!isSandboxFrameMessage(event, rendered)) return;\n bridge?.onMessage(event);\n };\n window.addEventListener(\"message\", onMessage);\n })\n .catch((err) => {\n if (cancelled) return;\n frame?.dispose();\n frame = null;\n const error = err instanceof Error ? err : new Error(String(err));\n invokeCallbackSafely(\n () => liveRef.current.onError?.(error),\n \"SandboxHost onError\",\n );\n });\n\n return () => {\n cancelled = true;\n if (onMessage) {\n window.removeEventListener(\"message\", onMessage);\n onMessage = null;\n }\n bridge?.dispose();\n bridge = null;\n frame?.dispose();\n frame = null;\n setContentHeight(undefined);\n };\n // oxlint-disable-next-line react/exhaustive-deps -- re-init only on contentKey change; live values flow through liveRef\n }, [contentKey]);\n\n const resolvedHeight =\n contentHeight != null ? Math.min(contentHeight, maxHeight) : undefined;\n const mergedStyle =\n resolvedHeight != null\n ? { ...sandbox?.style, height: resolvedHeight }\n : sandbox?.style;\n\n return (\n <div\n {...containerProps}\n ref={containerRef}\n className={sandbox?.className}\n style={mergedStyle}\n />\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SandboxHost.js","names":["c","_c","CSSProperties","useEffect","useLayoutEffect","useRef","useState","isShimLoadError","RenderedFrame","SafeContentFrame","SandboxOption","invokeUserCallback","DEFAULT_PRODUCT","DEFAULT_MAX_HEIGHT","LOAD_TIMEOUT_MS","SandboxHostConfig","sandbox","useShadowDom","enableBrowserCaching","salt","product","className","style","unsafeDocumentWrite","SandboxHostFrame","Pick","SandboxHostApi","setHeight","height","SandboxBridge","onMessage","event","MessageEvent","dispose","SandboxContent","html","SandboxHostProps","content","contentKey","maxHeight","createBridge","frame","host","onError","error","Error","containerProps","Record","isSandboxFrameMessage","iframe","HTMLIFrameElement","origin","source","contentWindow","LiveSnapshot","SandboxHost","t0","$","t1","undefined","containerRef","contentHeight","setContentHeight","t2","liveRef","t3","t4","current","t5","Symbol","for","container","cancelled","bridge","liveContent","sb","reportError","err","String","bind","scf","renderOpts","renderHtml","then","rendered","hostApi","Number","isFinite","sendMessage","window","addEventListener","fullyLoadedPromiseWithTimeout","catch","err_0","code","err_1","removeEventListener","t6","resolvedHeight","Math","min","t7","mergedStyle","t8","t9"],"sources":["../../src/sandbox-host/SandboxHost.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type CSSProperties,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport {\n isShimLoadError,\n type RenderedFrame,\n SafeContentFrame,\n type SandboxOption,\n} from \"safe-content-frame\";\nimport { invokeUserCallback } from \"@assistant-ui/core/internal\";\n\nconst DEFAULT_PRODUCT = \"assistant-ui-sandbox\";\nconst DEFAULT_MAX_HEIGHT = 800;\nconst LOAD_TIMEOUT_MS = 10_000;\n\nexport type SandboxHostConfig = {\n sandbox?: SandboxOption[];\n useShadowDom?: boolean;\n enableBrowserCaching?: boolean;\n salt?: string;\n product?: string;\n className?: string;\n style?: CSSProperties;\n unsafeDocumentWrite?: boolean;\n};\n\nexport type SandboxHostFrame = Pick<\n RenderedFrame,\n \"iframe\" | \"origin\" | \"sendMessage\"\n>;\n\nexport type SandboxHostApi = {\n setHeight: (height: number) => void;\n};\n\nexport type SandboxBridge = {\n onMessage: (event: MessageEvent) => void;\n dispose: () => void;\n};\n\nexport type SandboxContent = { html: string };\n\nexport type SandboxHostProps = {\n content: SandboxContent;\n contentKey: string;\n sandbox?: SandboxHostConfig | undefined;\n maxHeight?: number | undefined;\n createBridge: (\n frame: SandboxHostFrame,\n host: SandboxHostApi,\n ) => SandboxBridge;\n onError?: ((error: Error) => void) | undefined;\n containerProps?: Record<string, string | undefined> | undefined;\n};\n\nexport function isSandboxFrameMessage(\n event: MessageEvent,\n frame: { iframe: HTMLIFrameElement; origin: string },\n): boolean {\n return (\n event.source === frame.iframe.contentWindow && event.origin === frame.origin\n );\n}\n\ntype LiveSnapshot = {\n content: SandboxContent;\n sandbox: SandboxHostConfig | undefined;\n createBridge: SandboxHostProps[\"createBridge\"];\n onError: SandboxHostProps[\"onError\"];\n};\n\nexport function SandboxHost({\n content,\n contentKey,\n sandbox,\n maxHeight = DEFAULT_MAX_HEIGHT,\n createBridge,\n onError,\n containerProps,\n}: SandboxHostProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [contentHeight, setContentHeight] = useState<number | undefined>(\n undefined,\n );\n\n const liveRef = useRef<LiveSnapshot>({\n content,\n sandbox,\n createBridge,\n onError,\n });\n useLayoutEffect(() => {\n liveRef.current = { content, sandbox, createBridge, onError };\n }, [content, sandbox, createBridge, onError]);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n let cancelled = false;\n let frame: RenderedFrame | null = null;\n let bridge: SandboxBridge | null = null;\n let onMessage: ((event: MessageEvent) => void) | null = null;\n\n const { content: liveContent, sandbox: sb } = liveRef.current;\n\n const reportError = (err: unknown) => {\n const error = err instanceof Error ? err : new Error(String(err));\n invokeUserCallback(\n \"assistant-ui\",\n \"SandboxHost onError\",\n liveRef.current.onError?.bind(liveRef.current),\n error,\n );\n };\n\n const scf = new SafeContentFrame(sb?.product ?? DEFAULT_PRODUCT, {\n ...(sb?.sandbox !== undefined && { sandbox: sb.sandbox }),\n ...(sb?.useShadowDom !== undefined && { useShadowDom: sb.useShadowDom }),\n ...(sb?.enableBrowserCaching !== undefined && {\n enableBrowserCaching: sb.enableBrowserCaching,\n }),\n ...(sb?.salt !== undefined && { salt: sb.salt }),\n });\n\n const renderOpts =\n sb?.unsafeDocumentWrite !== undefined\n ? { unsafeDocumentWrite: sb.unsafeDocumentWrite }\n : undefined;\n\n scf\n .renderHtml(liveContent.html, container, renderOpts)\n .then((rendered) => {\n if (cancelled) {\n rendered.dispose();\n return;\n }\n frame = rendered;\n\n const hostApi: SandboxHostApi = {\n setHeight: (height) => {\n if (\n typeof height === \"number\" &&\n Number.isFinite(height) &&\n height > 0\n ) {\n setContentHeight(height);\n }\n },\n };\n\n bridge = liveRef.current.createBridge(\n {\n iframe: rendered.iframe,\n origin: rendered.origin,\n sendMessage: rendered.sendMessage,\n },\n hostApi,\n );\n\n // Single owner of the window listener; the cross-origin guard runs\n // here so the bridge only sees frame-validated messages.\n onMessage = (event) => {\n if (!isSandboxFrameMessage(event, rendered)) return;\n bridge?.onMessage(event);\n };\n window.addEventListener(\"message\", onMessage);\n\n // renderHtml resolves at iframe load, which a shim that was never\n // served also reaches, so a completed render is only known once the\n // frame says so. render-timeout is excluded because the shim is\n // running and the guest may still paint, and reporting it would leave\n // a host that renders an error state showing one over a frame that\n // recovers.\n rendered.fullyLoadedPromiseWithTimeout(LOAD_TIMEOUT_MS).catch((err) => {\n if (cancelled) return;\n if (isShimLoadError(err) && err.code === \"render-timeout\") return;\n reportError(err);\n });\n })\n .catch((err) => {\n if (cancelled) return;\n frame?.dispose();\n frame = null;\n reportError(err);\n });\n\n return () => {\n cancelled = true;\n if (onMessage) {\n window.removeEventListener(\"message\", onMessage);\n onMessage = null;\n }\n bridge?.dispose();\n bridge = null;\n frame?.dispose();\n frame = null;\n setContentHeight(undefined);\n };\n // oxlint-disable-next-line react/exhaustive-deps -- re-init only on contentKey change; live values flow through liveRef\n }, [contentKey]);\n\n const resolvedHeight =\n contentHeight != null ? Math.min(contentHeight, maxHeight) : undefined;\n const mergedStyle =\n resolvedHeight != null\n ? { ...sandbox?.style, height: resolvedHeight }\n : sandbox?.style;\n\n return (\n <div\n {...containerProps}\n ref={containerRef}\n className={sandbox?.className}\n style={mergedStyle}\n />\n );\n}\n"],"mappings":"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"}
@@ -39,7 +39,9 @@ function unstable_useLiveCompletionAdapter(options) {
39
39
  });
40
40
  const [isLoading, setIsLoading] = useState(false);
41
41
  const fetcherRef = useRef(fetcher);
42
- fetcherRef.current = fetcher;
42
+ useLayoutEffect(() => {
43
+ fetcherRef.current = fetcher;
44
+ }, [fetcher]);
43
45
  const timerRef = useRef(null);
44
46
  const tokenRef = useRef(0);
45
47
  const pendingQueryRef = useRef(null);
@@ -1 +1 @@
1
- {"version":3,"file":"useLiveCompletionAdapter.js","names":["useCallback","useEffect","useLayoutEffect","useMemo","useRef","useState","Unstable_TriggerAdapter","Unstable_TriggerItem","Unstable_UseLiveCompletionAdapterOptions","fetcher","query","Promise","cacheKey","debounceMs","enabled","NO_QUERY","unstable_useLiveCompletionAdapter","options","adapter","isLoading","state","setState","items","failed","setIsLoading","fetcherRef","current","timerRef","ReturnType","setTimeout","tokenRef","pendingQueryRef","retryableQueryRef","pendingRetryQueryRef","cancelTimer","clearTimeout","rearmPendingRetry","scheduleFetch","token","resolve","then","invalidatePending","cacheKeyRef","s","categories","categoryItems","search","queueMicrotask"],"sources":["../../src/unstable/useLiveCompletionAdapter.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type {\n Unstable_TriggerAdapter,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\n\nexport type Unstable_UseLiveCompletionAdapterOptions = {\n /**\n * Fetches the items for a query from an async source. Called debounced; the\n * resolved items are cached and returned synchronously to the popover on the\n * next render.\n */\n readonly fetcher: (query: string) => Promise<readonly Unstable_TriggerItem[]>;\n /**\n * Identifies the fetcher's data source. Change this when switching accounts,\n * workspaces, or another boundary that should invalidate cached results.\n */\n readonly cacheKey?: string | number | undefined;\n /** Debounce applied before a fetch fires, in milliseconds. @default 60 */\n readonly debounceMs?: number | undefined;\n /** When `false`, no fetch is scheduled and the adapter stays empty. @default true */\n readonly enabled?: boolean | undefined;\n};\n\n/** Sentinel that no real query (including the empty string) equals, so the first query always fetches. */\nconst NO_QUERY = \"\\u0000\";\n\n/**\n * @deprecated Under active development and may change without notice.\n *\n * Bridges an async completion source (a server search, a gateway RPC) into the\n * synchronous `Unstable_TriggerAdapter` that `ComposerTriggerPopover` consumes.\n * `search(query)` returns the last fetched items synchronously and schedules a\n * debounced fetch when the query changes; when results arrive the returned\n * `adapter` identity changes, which re-runs the popover's lookup so the fresh\n * items render. This is a search-only adapter (`categories` are empty).\n *\n * `isLoading` is `true` while a fetch is in flight. Pass it to the popover's\n * `isLoading` prop to render a loading state.\n *\n * @example\n * ```tsx\n * const mentions = unstable_useLiveCompletionAdapter({\n * fetcher: (query) => searchUsers(query),\n * });\n *\n * <ComposerTriggerPopover\n * char=\"@\"\n * adapter={mentions.adapter}\n * isLoading={mentions.isLoading}\n * directive={{ onInserted }}\n * />\n * ```\n */\nexport function unstable_useLiveCompletionAdapter(\n options: Unstable_UseLiveCompletionAdapterOptions,\n): { adapter: Unstable_TriggerAdapter; isLoading: boolean } {\n const { fetcher, cacheKey, debounceMs = 60, enabled = true } = options;\n\n const [state, setState] = useState<{\n query: string;\n items: readonly Unstable_TriggerItem[];\n failed: boolean;\n }>({ query: NO_QUERY, items: [], failed: false });\n const [isLoading, setIsLoading] = useState(false);\n\n const fetcherRef = useRef(fetcher);\n fetcherRef.current = fetcher;\n\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const tokenRef = useRef(0);\n const pendingQueryRef = useRef<string | null>(null);\n const retryableQueryRef = useRef<string | null>(null);\n const pendingRetryQueryRef = useRef<string | null>(null);\n\n const cancelTimer = useCallback(() => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }, []);\n\n const rearmPendingRetry = useCallback(() => {\n const query = pendingRetryQueryRef.current;\n if (query === null) return;\n retryableQueryRef.current = query;\n pendingRetryQueryRef.current = null;\n }, []);\n\n const scheduleFetch = useCallback(\n (query: string) => {\n if (!enabled) return;\n if (pendingQueryRef.current === query) return;\n rearmPendingRetry();\n if (retryableQueryRef.current === query) {\n retryableQueryRef.current = null;\n pendingRetryQueryRef.current = query;\n }\n pendingQueryRef.current = query;\n cancelTimer();\n const token = ++tokenRef.current;\n setIsLoading(true);\n timerRef.current = setTimeout(() => {\n timerRef.current = null;\n Promise.resolve()\n .then(() => fetcherRef.current(query))\n .then(\n (items) => {\n if (token !== tokenRef.current) return;\n pendingRetryQueryRef.current = null;\n setState({ query, items, failed: false });\n setIsLoading(false);\n },\n () => {\n if (token !== tokenRef.current) return;\n pendingQueryRef.current = null;\n pendingRetryQueryRef.current = null;\n setState({ query, items: [], failed: true });\n setIsLoading(false);\n },\n );\n }, debounceMs);\n },\n [enabled, debounceMs, cancelTimer, rearmPendingRetry],\n );\n\n const invalidatePending = useCallback(() => {\n rearmPendingRetry();\n cancelTimer();\n pendingQueryRef.current = null;\n tokenRef.current += 1;\n setIsLoading(false);\n }, [cancelTimer, rearmPendingRetry]);\n\n const cacheKeyRef = useRef(cacheKey);\n useLayoutEffect(() => {\n if (cacheKeyRef.current === cacheKey) return;\n cacheKeyRef.current = cacheKey;\n invalidatePending();\n retryableQueryRef.current = null;\n pendingRetryQueryRef.current = null;\n setState({ query: NO_QUERY, items: [], failed: false });\n }, [cacheKey, invalidatePending]);\n\n useEffect(() => {\n if (enabled) return;\n invalidatePending();\n setState((s) =>\n s.query === NO_QUERY ? s : { query: NO_QUERY, items: [], failed: false },\n );\n }, [enabled, invalidatePending]);\n\n useEffect(() => cancelTimer, [cancelTimer]);\n\n // Arm retries only after the failed state commits. Arming during rejection\n // would let the failure render immediately schedule another request.\n useEffect(() => {\n retryableQueryRef.current = state.failed ? state.query : null;\n }, [state]);\n\n const adapter = useMemo<Unstable_TriggerAdapter>(\n () => ({\n categories: () => [],\n categoryItems: () => [],\n search: (query: string) => {\n // search() runs inside the popover's render; defer state updates with\n // queueMicrotask so they are not dispatched while another component renders.\n if (query !== state.query || retryableQueryRef.current === query) {\n queueMicrotask(() => scheduleFetch(query));\n } else if (\n pendingQueryRef.current !== null &&\n pendingQueryRef.current !== query\n ) {\n // the query returned to a cached value while a fetch for a different\n // query is in flight; drop it so its result cannot overwrite the cache\n queueMicrotask(invalidatePending);\n }\n return state.items;\n },\n }),\n [state, scheduleFetch, invalidatePending],\n );\n\n return { adapter, isLoading };\n}\n"],"mappings":";;;;AAkCA,MAAMe,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BjB,SAAgBC,kCACdC,SAC0D;CAC1D,MAAM,EAAER,SAASG,UAAUC,aAAa,IAAIC,UAAU,SAASG;CAE/D,MAAM,CAACG,OAAOC,YAAYhB,SAIvB;EAAEK,OAAOK;EAAUO,OAAO,CAAA;EAAIC,QAAQ;CAAM,CAAC;CAChD,MAAM,CAACJ,WAAWK,gBAAgBnB,SAAS,KAAK;CAEhD,MAAMoB,aAAarB,OAAOK,OAAO;CACjCgB,WAAWC,UAAUjB;CAErB,MAAMkB,WAAWvB,OAA6C,IAAI;CAClE,MAAM0B,WAAW1B,OAAO,CAAC;CACzB,MAAM2B,kBAAkB3B,OAAsB,IAAI;CAClD,MAAM4B,oBAAoB5B,OAAsB,IAAI;CACpD,MAAM6B,uBAAuB7B,OAAsB,IAAI;CAEvD,MAAM8B,cAAclC,kBAAkB;EACpC,IAAI2B,SAASD,YAAY,MAAM;GAC7BS,aAAaR,SAASD,OAAO;GAC7BC,SAASD,UAAU;EACrB;CACF,GAAG,CAAA,CAAE;CAEL,MAAMU,oBAAoBpC,kBAAkB;EAC1C,MAAMU,QAAQuB,qBAAqBP;EACnC,IAAIhB,UAAU,MAAM;EACpBsB,kBAAkBN,UAAUhB;EAC5BuB,qBAAqBP,UAAU;CACjC,GAAG,CAAA,CAAE;CAEL,MAAMW,gBAAgBrC,aACnBU,UAAkB;EACjB,IAAI,CAACI,SAAS;EACd,IAAIiB,gBAAgBL,YAAYhB,OAAO;EACvC0B,kBAAkB;EAClB,IAAIJ,kBAAkBN,YAAYhB,OAAO;GACvCsB,kBAAkBN,UAAU;GAC5BO,qBAAqBP,UAAUhB;EACjC;EACAqB,gBAAgBL,UAAUhB;EAC1BwB,YAAY;EACZ,MAAMI,QAAQ,EAAER,SAASJ;EACzBF,aAAa,IAAI;EACjBG,SAASD,UAAUG,iBAAiB;GAClCF,SAASD,UAAU;GACnBf,QAAQ4B,QAAQ,CAAC,CACdC,WAAWf,WAAWC,QAAQhB,KAAK,CAAC,CAAC,CACrC8B,MACElB,UAAU;IACT,IAAIgB,UAAUR,SAASJ,SAAS;IAChCO,qBAAqBP,UAAU;IAC/BL,SAAS;KAAEX;KAAOY;KAAOC,QAAQ;IAAM,CAAC;IACxCC,aAAa,KAAK;GACpB,SACM;IACJ,IAAIc,UAAUR,SAASJ,SAAS;IAChCK,gBAAgBL,UAAU;IAC1BO,qBAAqBP,UAAU;IAC/BL,SAAS;KAAEX;KAAOY,OAAO,CAAA;KAAIC,QAAQ;IAAK,CAAC;IAC3CC,aAAa,KAAK;GACpB,CACF;EACJ,GAAGX,UAAU;CACf,GACA;EAACC;EAASD;EAAYqB;EAAaE;CAAiB,CACtD;CAEA,MAAMK,oBAAoBzC,kBAAkB;EAC1CoC,kBAAkB;EAClBF,YAAY;EACZH,gBAAgBL,UAAU;EAC1BI,SAASJ,WAAW;EACpBF,aAAa,KAAK;CACpB,GAAG,CAACU,aAAaE,iBAAiB,CAAC;CAEnC,MAAMM,cAActC,OAAOQ,QAAQ;CACnCV,sBAAsB;EACpB,IAAIwC,YAAYhB,YAAYd,UAAU;EACtC8B,YAAYhB,UAAUd;EACtB6B,kBAAkB;EAClBT,kBAAkBN,UAAU;EAC5BO,qBAAqBP,UAAU;EAC/BL,SAAS;GAAEX,OAAOK;GAAUO,OAAO,CAAA;GAAIC,QAAQ;EAAM,CAAC;CACxD,GAAG,CAACX,UAAU6B,iBAAiB,CAAC;CAEhCxC,gBAAgB;EACd,IAAIa,SAAS;EACb2B,kBAAkB;EAClBpB,UAAUsB,MACRA,EAAEjC,UAAUK,WAAW4B,IAAI;GAAEjC,OAAOK;GAAUO,OAAO,CAAA;GAAIC,QAAQ;EAAM,CACzE;CACF,GAAG,CAACT,SAAS2B,iBAAiB,CAAC;CAE/BxC,gBAAgBiC,aAAa,CAACA,WAAW,CAAC;CAI1CjC,gBAAgB;EACd+B,kBAAkBN,UAAUN,MAAMG,SAASH,MAAMV,QAAQ;CAC3D,GAAG,CAACU,KAAK,CAAC;CAyBV,OAAO;EAAEF,SAvBOf,eACP;GACLyC,kBAAkB,CAAA;GAClBC,qBAAqB,CAAA;GACrBC,SAASpC,UAAkB;IAGzB,IAAIA,UAAUU,MAAMV,SAASsB,kBAAkBN,YAAYhB,OACzDqC,qBAAqBV,cAAc3B,KAAK,CAAC;SACpC,IACLqB,gBAAgBL,YAAY,QAC5BK,gBAAgBL,YAAYhB,OAI5BqC,eAAeN,iBAAiB;IAElC,OAAOrB,MAAME;GACf;EACF,IACA;GAACF;GAAOiB;GAAeI;EAAiB,CAGjCvB;EAASC;CAAU;AAC9B"}
1
+ {"version":3,"file":"useLiveCompletionAdapter.js","names":["useCallback","useEffect","useLayoutEffect","useMemo","useRef","useState","Unstable_TriggerAdapter","Unstable_TriggerItem","Unstable_UseLiveCompletionAdapterOptions","fetcher","query","Promise","cacheKey","debounceMs","enabled","NO_QUERY","unstable_useLiveCompletionAdapter","options","adapter","isLoading","state","setState","items","failed","setIsLoading","fetcherRef","current","timerRef","ReturnType","setTimeout","tokenRef","pendingQueryRef","retryableQueryRef","pendingRetryQueryRef","cancelTimer","clearTimeout","rearmPendingRetry","scheduleFetch","token","resolve","then","invalidatePending","cacheKeyRef","s","categories","categoryItems","search","queueMicrotask"],"sources":["../../src/unstable/useLiveCompletionAdapter.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type {\n Unstable_TriggerAdapter,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\n\nexport type Unstable_UseLiveCompletionAdapterOptions = {\n /**\n * Fetches the items for a query from an async source. Called debounced; the\n * resolved items are cached and returned synchronously to the popover on the\n * next render.\n */\n readonly fetcher: (query: string) => Promise<readonly Unstable_TriggerItem[]>;\n /**\n * Identifies the fetcher's data source. Change this when switching accounts,\n * workspaces, or another boundary that should invalidate cached results.\n */\n readonly cacheKey?: string | number | undefined;\n /** Debounce applied before a fetch fires, in milliseconds. @default 60 */\n readonly debounceMs?: number | undefined;\n /** When `false`, no fetch is scheduled and the adapter stays empty. @default true */\n readonly enabled?: boolean | undefined;\n};\n\n/** Sentinel that no real query (including the empty string) equals, so the first query always fetches. */\nconst NO_QUERY = \"\\u0000\";\n\n/**\n * @deprecated Under active development and may change without notice.\n *\n * Bridges an async completion source (a server search, a gateway RPC) into the\n * synchronous `Unstable_TriggerAdapter` that `ComposerTriggerPopover` consumes.\n * `search(query)` returns the last fetched items synchronously and schedules a\n * debounced fetch when the query changes; when results arrive the returned\n * `adapter` identity changes, which re-runs the popover's lookup so the fresh\n * items render. This is a search-only adapter (`categories` are empty).\n *\n * `isLoading` is `true` while a fetch is in flight. Pass it to the popover's\n * `isLoading` prop to render a loading state.\n *\n * @example\n * ```tsx\n * const mentions = unstable_useLiveCompletionAdapter({\n * fetcher: (query) => searchUsers(query),\n * });\n *\n * <ComposerTriggerPopover\n * char=\"@\"\n * adapter={mentions.adapter}\n * isLoading={mentions.isLoading}\n * directive={{ onInserted }}\n * />\n * ```\n */\nexport function unstable_useLiveCompletionAdapter(\n options: Unstable_UseLiveCompletionAdapterOptions,\n): { adapter: Unstable_TriggerAdapter; isLoading: boolean } {\n const { fetcher, cacheKey, debounceMs = 60, enabled = true } = options;\n\n const [state, setState] = useState<{\n query: string;\n items: readonly Unstable_TriggerItem[];\n failed: boolean;\n }>({ query: NO_QUERY, items: [], failed: false });\n const [isLoading, setIsLoading] = useState(false);\n\n const fetcherRef = useRef(fetcher);\n // The debounce timer must only observe fetchers from committed renders.\n useLayoutEffect(() => {\n fetcherRef.current = fetcher;\n }, [fetcher]);\n\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const tokenRef = useRef(0);\n const pendingQueryRef = useRef<string | null>(null);\n const retryableQueryRef = useRef<string | null>(null);\n const pendingRetryQueryRef = useRef<string | null>(null);\n\n const cancelTimer = useCallback(() => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }, []);\n\n const rearmPendingRetry = useCallback(() => {\n const query = pendingRetryQueryRef.current;\n if (query === null) return;\n retryableQueryRef.current = query;\n pendingRetryQueryRef.current = null;\n }, []);\n\n const scheduleFetch = useCallback(\n (query: string) => {\n if (!enabled) return;\n if (pendingQueryRef.current === query) return;\n rearmPendingRetry();\n if (retryableQueryRef.current === query) {\n retryableQueryRef.current = null;\n pendingRetryQueryRef.current = query;\n }\n pendingQueryRef.current = query;\n cancelTimer();\n const token = ++tokenRef.current;\n setIsLoading(true);\n timerRef.current = setTimeout(() => {\n timerRef.current = null;\n Promise.resolve()\n .then(() => fetcherRef.current(query))\n .then(\n (items) => {\n if (token !== tokenRef.current) return;\n pendingRetryQueryRef.current = null;\n setState({ query, items, failed: false });\n setIsLoading(false);\n },\n () => {\n if (token !== tokenRef.current) return;\n pendingQueryRef.current = null;\n pendingRetryQueryRef.current = null;\n setState({ query, items: [], failed: true });\n setIsLoading(false);\n },\n );\n }, debounceMs);\n },\n [enabled, debounceMs, cancelTimer, rearmPendingRetry],\n );\n\n const invalidatePending = useCallback(() => {\n rearmPendingRetry();\n cancelTimer();\n pendingQueryRef.current = null;\n tokenRef.current += 1;\n setIsLoading(false);\n }, [cancelTimer, rearmPendingRetry]);\n\n const cacheKeyRef = useRef(cacheKey);\n useLayoutEffect(() => {\n if (cacheKeyRef.current === cacheKey) return;\n cacheKeyRef.current = cacheKey;\n invalidatePending();\n retryableQueryRef.current = null;\n pendingRetryQueryRef.current = null;\n setState({ query: NO_QUERY, items: [], failed: false });\n }, [cacheKey, invalidatePending]);\n\n useEffect(() => {\n if (enabled) return;\n invalidatePending();\n setState((s) =>\n s.query === NO_QUERY ? s : { query: NO_QUERY, items: [], failed: false },\n );\n }, [enabled, invalidatePending]);\n\n useEffect(() => cancelTimer, [cancelTimer]);\n\n // Arm retries only after the failed state commits. Arming during rejection\n // would let the failure render immediately schedule another request.\n useEffect(() => {\n retryableQueryRef.current = state.failed ? state.query : null;\n }, [state]);\n\n const adapter = useMemo<Unstable_TriggerAdapter>(\n () => ({\n categories: () => [],\n categoryItems: () => [],\n search: (query: string) => {\n // search() runs inside the popover's render; defer state updates with\n // queueMicrotask so they are not dispatched while another component renders.\n if (query !== state.query || retryableQueryRef.current === query) {\n queueMicrotask(() => scheduleFetch(query));\n } else if (\n pendingQueryRef.current !== null &&\n pendingQueryRef.current !== query\n ) {\n // the query returned to a cached value while a fetch for a different\n // query is in flight; drop it so its result cannot overwrite the cache\n queueMicrotask(invalidatePending);\n }\n return state.items;\n },\n }),\n [state, scheduleFetch, invalidatePending],\n );\n\n return { adapter, isLoading };\n}\n"],"mappings":"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"}
@@ -21,7 +21,10 @@ type Unstable_MentionCategory = {
21
21
  readonly items: readonly Unstable_Mention[];
22
22
  };
23
23
  type Unstable_ModelContextToolsOptions = {
24
- /** Wrap tools in a dedicated category (drill-down mode). */
24
+ /**
25
+ * Wrap tools in a dedicated category. Selects drill-down mode on its own
26
+ * when `categories` is unset and `items` is omitted.
27
+ */
25
28
  readonly category?: {
26
29
  readonly id: string;
27
30
  readonly label: string;
@@ -32,7 +35,10 @@ type Unstable_ModelContextToolsOptions = {
32
35
  readonly icon?: string;
33
36
  };
34
37
  type Unstable_UseMentionAdapterOptions = {
35
- /** Flat mention list. Ignored when `categories` is set. */
38
+ /**
39
+ * Flat mention list. Ignored when `categories` is set, and keeps the
40
+ * adapter flat when a tool `category` is configured.
41
+ */
36
42
  readonly items?: readonly Unstable_Mention[];
37
43
  /** Categorized mentions for drill-down navigation. */
38
44
  readonly categories?: readonly Unstable_MentionCategory[];
@@ -41,7 +47,7 @@ type Unstable_UseMentionAdapterOptions = {
41
47
  * - `false`: exclude.
42
48
  * - `true`: include (default when no `items`/`categories`; as a category
43
49
  * if `categories` is set, flat otherwise).
44
- * - object: explicit config.
50
+ * - object: explicit config; `category` also selects drill-down mode.
45
51
  *
46
52
  * Omitted → defaults to `true` iff neither `items` nor `categories`.
47
53
  */
@@ -1 +1 @@
1
- {"version":3,"file":"useMentionAdapter.d.ts","names":[],"sources":["../../src/unstable/useMentionAdapter.ts"],"mappings":";;;;;KAcY,yBAAyB;EAAK;;KAE9B;WACD;WACA;WACA;WACA;;WAEA;WACA,WAAW;;KAGV;WACD;WACA;WACA,gBAAgB;;KAGf;;WAED;aAAsB;aAAqB;;;WAE3C,eAAe;;WAEf;;KAGC;;WAED,iBAAiB;;WAEjB,sBAAsB;;;;;;;;;;WAUtB,qCAEL;;WAEK,YAAY;;WAEZ,cAAc,MAAM;;WAEpB,UAAU,eAAe;;WAEzB,eAAe;;KAGd;WACD,WAAW;WACX,eAAe,MAAM;;;;;;;;;;;;;;;iBAgBhB,2BACd,UAAU;EAEV,SAAS;EACT,WAAW;EACX,UAAU,eAAe;EACzB,eAAe"}
1
+ {"version":3,"file":"useMentionAdapter.d.ts","names":[],"sources":["../../src/unstable/useMentionAdapter.ts"],"mappings":";;;;;KAoBY,yBAAyB;EAAK;;KAE9B;WACD;WACA;WACA;WACA;;WAEA;WACA,WAAW;;KAGV;WACD;WACA;WACA,gBAAgB;;KAGf;;;;;WAKD;aAAsB;aAAqB;;;WAE3C,eAAe;;WAEf;;KAGC;;;;;WAKD,iBAAiB;;WAEjB,sBAAsB;;;;;;;;;;WAUtB,qCAEL;;WAEK,YAAY;;WAEZ,cAAc,MAAM;;WAEpB,UAAU,eAAe;;WAEzB,eAAe;;KAGd;WACD,WAAW;WACX,eAAe,MAAM;;;;;;;;;;;;;;;iBAoDhB,2BACd,UAAU;EAEV,SAAS;EACT,WAAW;EACX,UAAU,eAAe;EACzB,eAAe"}
@@ -1,8 +1,33 @@
1
1
  "use client";
2
+ import { matchesTriggerItemQuery } from "../primitives/composer/trigger/matchesTriggerItemQuery.js";
3
+ import { shallowEqualRecords, useModelContextSnapshot } from "./useModelContextSnapshot.js";
2
4
  import { useAui } from "@assistant-ui/store";
3
5
  import { unstable_defaultDirectiveFormatter } from "@assistant-ui/core";
4
6
  import { useMemo } from "@assistant-ui/tap/react-shim";
5
7
  //#region src/unstable/useMentionAdapter.ts
8
+ const EMPTY_TOOL_MENTIONS = Object.freeze({});
9
+ const toolMentionSource = {
10
+ empty: EMPTY_TOOL_MENTIONS,
11
+ read: (aui) => {
12
+ const tools = aui.thread.getModelContext().tools;
13
+ if (!tools) return EMPTY_TOOL_MENTIONS;
14
+ const mentions = {};
15
+ for (const [name, tool] of Object.entries(tools)) mentions[name] = tool.description;
16
+ return mentions;
17
+ },
18
+ subscribe: (aui, onChange) => {
19
+ const unsubscribeContext = aui.on("thread.modelContextUpdate", onChange);
20
+ const unsubscribeSelection = aui.on({
21
+ scope: "*",
22
+ event: "threads.selectionChanged"
23
+ }, onChange);
24
+ return () => {
25
+ unsubscribeContext();
26
+ unsubscribeSelection();
27
+ };
28
+ },
29
+ isEqual: shallowEqualRecords
30
+ };
6
31
  /**
7
32
  * @deprecated Under active development and might change without notice.
8
33
  *
@@ -25,38 +50,30 @@ function unstable_useMentionAdapter(options) {
25
50
  const wantsTools = includeTools !== false;
26
51
  const formatter = options?.formatter;
27
52
  const onInserted = options?.onInserted;
53
+ const isCategorized = categories !== void 0 && categories.length > 0 || toolsConfig?.category !== void 0 && items === void 0;
54
+ const toolMentions = useModelContextSnapshot(aui, wantsTools, toolMentionSource);
28
55
  return {
29
56
  adapter: useMemo(() => {
30
- const getModelContextTools = () => {
31
- if (!wantsTools) return [];
32
- const tools = aui.thread.getModelContext().tools;
33
- if (!tools) return [];
34
- const formatLabel = toolsConfig?.formatLabel;
35
- const defaultIcon = toolsConfig?.icon;
36
- return Object.entries(tools).map(([name, tool]) => toTriggerItem({
37
- id: name,
38
- type: "tool",
39
- label: formatLabel ? formatLabel(name) : name,
40
- description: tool.description ?? void 0,
41
- icon: defaultIcon
42
- }));
43
- };
44
- if (categories && categories.length > 0) {
45
- const groups = categories.map((cat) => ({
57
+ const formatLabel = toolsConfig?.formatLabel;
58
+ const defaultIcon = toolsConfig?.icon;
59
+ const toolItems = wantsTools ? Object.entries(toolMentions).map(([name, description]) => toTriggerItem({
60
+ id: name,
61
+ type: "tool",
62
+ label: formatLabel ? formatLabel(name) : name,
63
+ description,
64
+ icon: defaultIcon
65
+ })) : [];
66
+ if (isCategorized) {
67
+ const groups = (categories ?? []).map((cat) => ({
46
68
  id: cat.id,
47
69
  label: cat.label,
48
70
  items: cat.items.map(toTriggerItem)
49
71
  }));
50
- let toolCategory = null;
51
- if (wantsTools) {
52
- const toolItems = getModelContextTools();
53
- if (toolItems.length > 0) toolCategory = {
54
- id: toolsConfig?.category?.id ?? "tools",
55
- label: toolsConfig?.category?.label ?? "Tools",
56
- items: toolItems
57
- };
58
- }
59
- const allGroups = toolCategory ? [...groups, toolCategory] : groups;
72
+ const allGroups = toolItems.length > 0 ? [...groups, {
73
+ id: toolsConfig?.category?.id ?? "tools",
74
+ label: toolsConfig?.category?.label ?? "Tools",
75
+ items: toolItems
76
+ }] : groups;
60
77
  return {
61
78
  categories: () => allGroups.map(({ id, label }) => ({
62
79
  id,
@@ -65,31 +82,28 @@ function unstable_useMentionAdapter(options) {
65
82
  categoryItems: (id) => allGroups.find((g) => g.id === id)?.items ?? [],
66
83
  search: (query) => {
67
84
  const lower = query.toLowerCase();
68
- return allGroups.flatMap((g) => g.items).filter((item) => matchesQuery(item, lower));
85
+ return allGroups.flatMap((g) => g.items).filter((item) => matchesTriggerItemQuery(item, lower));
69
86
  }
70
87
  };
71
88
  }
72
89
  const flatItems = (items ?? []).map(toTriggerItem);
73
- const getFlatPool = () => {
74
- if (!wantsTools) return flatItems;
75
- const toolItems = getModelContextTools();
76
- const seen = new Set(flatItems.map((i) => i.id));
77
- return [...flatItems, ...toolItems.filter((t) => !seen.has(t.id))];
78
- };
90
+ const seen = new Set(flatItems.map((i) => i.id));
91
+ const flatPool = [...flatItems, ...toolItems.filter((t) => !seen.has(t.id))];
79
92
  return {
80
93
  categories: () => [],
81
94
  categoryItems: () => [],
82
95
  search: (query) => {
83
96
  const lower = query.toLowerCase();
84
- return getFlatPool().filter((item) => matchesQuery(item, lower));
97
+ return flatPool.filter((item) => matchesTriggerItemQuery(item, lower));
85
98
  }
86
99
  };
87
100
  }, [
88
- aui,
89
101
  items,
90
102
  categories,
91
103
  wantsTools,
92
- toolsConfig
104
+ toolsConfig,
105
+ isCategorized,
106
+ toolMentions
93
107
  ]),
94
108
  directive: useMemo(() => ({
95
109
  formatter: formatter ?? unstable_defaultDirectiveFormatter,
@@ -112,13 +126,6 @@ function toTriggerItem(m) {
112
126
  ...metadata !== void 0 ? { metadata } : {}
113
127
  };
114
128
  }
115
- function matchesQuery(item, lower) {
116
- if (!lower) return true;
117
- if (item.id.toLowerCase().includes(lower)) return true;
118
- if (item.label.toLowerCase().includes(lower)) return true;
119
- if (item.description?.toLowerCase().includes(lower)) return true;
120
- return false;
121
- }
122
129
  //#endregion
123
130
  export { unstable_useMentionAdapter };
124
131
 
@@ -1 +1 @@
1
- {"version":3,"file":"useMentionAdapter.js","names":["useMemo","FC","useAui","Unstable_DirectiveFormatter","Unstable_TriggerAdapter","Unstable_TriggerCategory","Unstable_TriggerItem","unstable_defaultDirectiveFormatter","ReadonlyJSONObject","Unstable_IconComponent","className","Unstable_Mention","id","type","label","description","icon","metadata","Unstable_MentionCategory","items","Unstable_ModelContextToolsOptions","category","formatLabel","toolName","Unstable_UseMentionAdapterOptions","categories","includeModelContextTools","formatter","onInserted","item","iconMap","Record","fallbackIcon","Unstable_MentionDirective","unstable_useMentionAdapter","options","adapter","directive","aui","includeTools","toolsConfig","undefined","wantsTools","getModelContextTools","ctx","thread","getModelContext","tools","defaultIcon","Object","entries","map","name","tool","toTriggerItem","length","groups","cat","toolCategory","toolItems","allGroups","categoryItems","find","g","search","query","lower","toLowerCase","flatMap","filter","matchesQuery","flatItems","getFlatPool","seen","Set","i","t","has","m","includes"],"sources":["../../src/unstable/useMentionAdapter.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo, type FC } from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport type {\n Unstable_DirectiveFormatter,\n Unstable_TriggerAdapter,\n Unstable_TriggerCategory,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\nimport { unstable_defaultDirectiveFormatter } from \"@assistant-ui/core\";\nimport type { ReadonlyJSONObject } from \"assistant-stream/utils\";\n\n/** Icon component shape consumed by `ComposerTriggerPopover`'s `iconMap`. */\nexport type Unstable_IconComponent = FC<{ className?: string }>;\n\nexport type Unstable_Mention = {\n readonly id: string;\n readonly type: string;\n readonly label: string;\n readonly description?: string | undefined;\n /** Shortcut for `metadata.icon`; merged with `metadata` if both are given. */\n readonly icon?: string | undefined;\n readonly metadata?: ReadonlyJSONObject | undefined;\n};\n\nexport type Unstable_MentionCategory = {\n readonly id: string;\n readonly label: string;\n readonly items: readonly Unstable_Mention[];\n};\n\nexport type Unstable_ModelContextToolsOptions = {\n /** Wrap tools in a dedicated category (drill-down mode). */\n readonly category?: { readonly id: string; readonly label: string };\n /** Format tool name for display. */\n readonly formatLabel?: (toolName: string) => string;\n /** Default icon key for each tool. */\n readonly icon?: string;\n};\n\nexport type Unstable_UseMentionAdapterOptions = {\n /** Flat mention list. Ignored when `categories` is set. */\n readonly items?: readonly Unstable_Mention[];\n /** Categorized mentions for drill-down navigation. */\n readonly categories?: readonly Unstable_MentionCategory[];\n /**\n * How tools registered in model context integrate.\n * - `false`: exclude.\n * - `true`: include (default when no `items`/`categories`; as a category\n * if `categories` is set, flat otherwise).\n * - object: explicit config.\n *\n * Omitted → defaults to `true` iff neither `items` nor `categories`.\n */\n readonly includeModelContextTools?:\n | boolean\n | Unstable_ModelContextToolsOptions;\n /** Directive formatter. @default unstable_defaultDirectiveFormatter */\n readonly formatter?: Unstable_DirectiveFormatter;\n /** Fires after an item is inserted into the composer. */\n readonly onInserted?: (item: Unstable_TriggerItem) => void;\n /** Maps `metadata.icon` / `category.id` string keys to React components. */\n readonly iconMap?: Record<string, Unstable_IconComponent>;\n /** Fallback icon when no entry in `iconMap` matches. */\n readonly fallbackIcon?: Unstable_IconComponent;\n};\n\nexport type Unstable_MentionDirective = {\n readonly formatter: Unstable_DirectiveFormatter;\n readonly onInserted?: ((item: Unstable_TriggerItem) => void) | undefined;\n};\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * Creates a spreadable `{ adapter, directive }` bundle for `@` mentions.\n * Supports tools registered in model context, explicit items, or both —\n * flat or categorized.\n *\n * @example\n * ```tsx\n * const mention = unstable_useMentionAdapter();\n * <ComposerTriggerPopover char=\"@\" {...mention} />\n * ```\n */\nexport function unstable_useMentionAdapter(\n options?: Unstable_UseMentionAdapterOptions,\n): {\n adapter: Unstable_TriggerAdapter;\n directive: Unstable_MentionDirective;\n iconMap?: Record<string, Unstable_IconComponent>;\n fallbackIcon?: Unstable_IconComponent;\n} {\n const aui = useAui();\n\n const items = options?.items;\n const categories = options?.categories;\n const includeTools =\n options?.includeModelContextTools ?? (!items && !categories);\n const toolsConfig =\n typeof includeTools === \"object\" ? includeTools : undefined;\n const wantsTools = includeTools !== false;\n const formatter = options?.formatter;\n const onInserted = options?.onInserted;\n\n const adapter = useMemo<Unstable_TriggerAdapter>(() => {\n const getModelContextTools = (): Unstable_TriggerItem[] => {\n if (!wantsTools) return [];\n const ctx = aui.thread.getModelContext();\n const tools = ctx.tools;\n if (!tools) return [];\n const formatLabel = toolsConfig?.formatLabel;\n const defaultIcon = toolsConfig?.icon;\n return Object.entries(tools).map(([name, tool]) =>\n toTriggerItem({\n id: name,\n type: \"tool\",\n label: formatLabel ? formatLabel(name) : name,\n description: tool.description ?? undefined,\n icon: defaultIcon,\n }),\n );\n };\n\n // Categorized: drill-down mode\n if (categories && categories.length > 0) {\n const groups = categories.map((cat) => ({\n id: cat.id,\n label: cat.label,\n items: cat.items.map(toTriggerItem),\n }));\n\n let toolCategory: {\n id: string;\n label: string;\n items: Unstable_TriggerItem[];\n } | null = null;\n if (wantsTools) {\n const toolItems = getModelContextTools();\n if (toolItems.length > 0) {\n toolCategory = {\n id: toolsConfig?.category?.id ?? \"tools\",\n label: toolsConfig?.category?.label ?? \"Tools\",\n items: toolItems,\n };\n }\n }\n const allGroups = toolCategory ? [...groups, toolCategory] : groups;\n\n return {\n categories: () => allGroups.map(({ id, label }) => ({ id, label })),\n categoryItems: (id) => allGroups.find((g) => g.id === id)?.items ?? [],\n search: (query) => {\n const lower = query.toLowerCase();\n return allGroups\n .flatMap((g) => g.items)\n .filter((item) => matchesQuery(item, lower));\n },\n };\n }\n\n // Flat: items + (optionally) tools, all in one search pool\n const flatItems = (items ?? []).map(toTriggerItem);\n const getFlatPool = (): Unstable_TriggerItem[] => {\n if (!wantsTools) return flatItems;\n const toolItems = getModelContextTools();\n // Dedupe by id — explicit items win.\n const seen = new Set(flatItems.map((i) => i.id));\n return [...flatItems, ...toolItems.filter((t) => !seen.has(t.id))];\n };\n\n return {\n categories: (): readonly Unstable_TriggerCategory[] => [],\n categoryItems: () => [],\n search: (query) => {\n const lower = query.toLowerCase();\n return getFlatPool().filter((item) => matchesQuery(item, lower));\n },\n };\n }, [aui, items, categories, wantsTools, toolsConfig]);\n\n const directive = useMemo<Unstable_MentionDirective>(\n () => ({\n formatter: formatter ?? unstable_defaultDirectiveFormatter,\n ...(onInserted ? { onInserted } : {}),\n }),\n [formatter, onInserted],\n );\n\n return {\n adapter,\n directive,\n ...(options?.iconMap ? { iconMap: options.iconMap } : {}),\n ...(options?.fallbackIcon ? { fallbackIcon: options.fallbackIcon } : {}),\n };\n}\n\nfunction toTriggerItem(m: Unstable_Mention): Unstable_TriggerItem {\n const metadata =\n m.icon !== undefined ? { ...(m.metadata ?? {}), icon: m.icon } : m.metadata;\n return {\n id: m.id,\n type: m.type,\n label: m.label,\n ...(m.description !== undefined ? { description: m.description } : {}),\n ...(metadata !== undefined ? { metadata } : {}),\n };\n}\n\nfunction matchesQuery(item: Unstable_TriggerItem, lower: string): boolean {\n if (!lower) return true;\n if (item.id.toLowerCase().includes(lower)) return true;\n if (item.label.toLowerCase().includes(lower)) return true;\n if (item.description?.toLowerCase().includes(lower)) return true;\n return false;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useMentionAdapter.js","names":["useMemo","FC","useAui","Unstable_DirectiveFormatter","Unstable_TriggerAdapter","Unstable_TriggerCategory","Unstable_TriggerItem","unstable_defaultDirectiveFormatter","ReadonlyJSONObject","matchesTriggerItemQuery","shallowEqualRecords","useModelContextSnapshot","ModelContextSnapshotSource","Unstable_IconComponent","className","Unstable_Mention","id","type","label","description","icon","metadata","Unstable_MentionCategory","items","Unstable_ModelContextToolsOptions","category","formatLabel","toolName","Unstable_UseMentionAdapterOptions","categories","includeModelContextTools","formatter","onInserted","item","iconMap","Record","fallbackIcon","Unstable_MentionDirective","EMPTY_TOOL_MENTIONS","Readonly","Object","freeze","toolMentionSource","empty","read","aui","tools","thread","getModelContext","mentions","name","tool","entries","subscribe","onChange","unsubscribeContext","on","unsubscribeSelection","scope","event","isEqual","unstable_useMentionAdapter","options","adapter","directive","includeTools","toolsConfig","undefined","wantsTools","isCategorized","length","toolMentions","defaultIcon","toolItems","map","toTriggerItem","groups","cat","allGroups","categoryItems","find","g","search","query","lower","toLowerCase","flatMap","filter","flatItems","seen","Set","i","flatPool","t","has","m"],"sources":["../../src/unstable/useMentionAdapter.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo, type FC } from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport type {\n Unstable_DirectiveFormatter,\n Unstable_TriggerAdapter,\n Unstable_TriggerCategory,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\nimport { unstable_defaultDirectiveFormatter } from \"@assistant-ui/core\";\nimport type { ReadonlyJSONObject } from \"assistant-stream/utils\";\nimport { matchesTriggerItemQuery } from \"../primitives/composer/trigger/matchesTriggerItemQuery\";\nimport {\n shallowEqualRecords,\n useModelContextSnapshot,\n type ModelContextSnapshotSource,\n} from \"./useModelContextSnapshot\";\n\n/** Icon component shape consumed by `ComposerTriggerPopover`'s `iconMap`. */\nexport type Unstable_IconComponent = FC<{ className?: string }>;\n\nexport type Unstable_Mention = {\n readonly id: string;\n readonly type: string;\n readonly label: string;\n readonly description?: string | undefined;\n /** Shortcut for `metadata.icon`; merged with `metadata` if both are given. */\n readonly icon?: string | undefined;\n readonly metadata?: ReadonlyJSONObject | undefined;\n};\n\nexport type Unstable_MentionCategory = {\n readonly id: string;\n readonly label: string;\n readonly items: readonly Unstable_Mention[];\n};\n\nexport type Unstable_ModelContextToolsOptions = {\n /**\n * Wrap tools in a dedicated category. Selects drill-down mode on its own\n * when `categories` is unset and `items` is omitted.\n */\n readonly category?: { readonly id: string; readonly label: string };\n /** Format tool name for display. */\n readonly formatLabel?: (toolName: string) => string;\n /** Default icon key for each tool. */\n readonly icon?: string;\n};\n\nexport type Unstable_UseMentionAdapterOptions = {\n /**\n * Flat mention list. Ignored when `categories` is set, and keeps the\n * adapter flat when a tool `category` is configured.\n */\n readonly items?: readonly Unstable_Mention[];\n /** Categorized mentions for drill-down navigation. */\n readonly categories?: readonly Unstable_MentionCategory[];\n /**\n * How tools registered in model context integrate.\n * - `false`: exclude.\n * - `true`: include (default when no `items`/`categories`; as a category\n * if `categories` is set, flat otherwise).\n * - object: explicit config; `category` also selects drill-down mode.\n *\n * Omitted → defaults to `true` iff neither `items` nor `categories`.\n */\n readonly includeModelContextTools?:\n | boolean\n | Unstable_ModelContextToolsOptions;\n /** Directive formatter. @default unstable_defaultDirectiveFormatter */\n readonly formatter?: Unstable_DirectiveFormatter;\n /** Fires after an item is inserted into the composer. */\n readonly onInserted?: (item: Unstable_TriggerItem) => void;\n /** Maps `metadata.icon` / `category.id` string keys to React components. */\n readonly iconMap?: Record<string, Unstable_IconComponent>;\n /** Fallback icon when no entry in `iconMap` matches. */\n readonly fallbackIcon?: Unstable_IconComponent;\n};\n\nexport type Unstable_MentionDirective = {\n readonly formatter: Unstable_DirectiveFormatter;\n readonly onInserted?: ((item: Unstable_TriggerItem) => void) | undefined;\n};\n\nconst EMPTY_TOOL_MENTIONS: Readonly<Record<string, string | undefined>> =\n Object.freeze({});\n\n// The description is copied out rather than reached through the tool, so a\n// provider that returns a stable tool object and edits it in place is observed.\nconst toolMentionSource: ModelContextSnapshotSource<\n Readonly<Record<string, string | undefined>>\n> = {\n empty: EMPTY_TOOL_MENTIONS,\n read: (aui) => {\n const tools = aui.thread.getModelContext().tools;\n if (!tools) return EMPTY_TOOL_MENTIONS;\n const mentions: Record<string, string | undefined> = {};\n for (const [name, tool] of Object.entries(tools)) {\n mentions[name] = tool.description;\n }\n return mentions;\n },\n subscribe: (aui, onChange) => {\n const unsubscribeContext = aui.on(\"thread.modelContextUpdate\", onChange);\n // Rebinding the thread event subject to a new thread does not replay it,\n // so a switch between threads carrying different providers is its own\n // refresh trigger. Subscribed globally because a composer can render\n // without a thread list scope.\n const unsubscribeSelection = aui.on(\n { scope: \"*\", event: \"threads.selectionChanged\" },\n onChange,\n );\n return () => {\n unsubscribeContext();\n unsubscribeSelection();\n };\n },\n isEqual: shallowEqualRecords,\n};\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * Creates a spreadable `{ adapter, directive }` bundle for `@` mentions.\n * Supports tools registered in model context, explicit items, or both —\n * flat or categorized.\n *\n * @example\n * ```tsx\n * const mention = unstable_useMentionAdapter();\n * <ComposerTriggerPopover char=\"@\" {...mention} />\n * ```\n */\nexport function unstable_useMentionAdapter(\n options?: Unstable_UseMentionAdapterOptions,\n): {\n adapter: Unstable_TriggerAdapter;\n directive: Unstable_MentionDirective;\n iconMap?: Record<string, Unstable_IconComponent>;\n fallbackIcon?: Unstable_IconComponent;\n} {\n const aui = useAui();\n\n const items = options?.items;\n const categories = options?.categories;\n const includeTools =\n options?.includeModelContextTools ?? (!items && !categories);\n const toolsConfig =\n typeof includeTools === \"object\" ? includeTools : undefined;\n const wantsTools = includeTools !== false;\n const formatter = options?.formatter;\n const onInserted = options?.onInserted;\n const isCategorized =\n (categories !== undefined && categories.length > 0) ||\n (toolsConfig?.category !== undefined && items === undefined);\n const toolMentions = useModelContextSnapshot(\n aui,\n wantsTools,\n toolMentionSource,\n );\n\n const adapter = useMemo<Unstable_TriggerAdapter>(() => {\n const formatLabel = toolsConfig?.formatLabel;\n const defaultIcon = toolsConfig?.icon;\n const toolItems = wantsTools\n ? Object.entries(toolMentions).map(([name, description]) =>\n toTriggerItem({\n id: name,\n type: \"tool\",\n label: formatLabel ? formatLabel(name) : name,\n description,\n icon: defaultIcon,\n }),\n )\n : [];\n\n // Categorized: drill-down mode\n if (isCategorized) {\n const groups = (categories ?? []).map((cat) => ({\n id: cat.id,\n label: cat.label,\n items: cat.items.map(toTriggerItem),\n }));\n const allGroups =\n toolItems.length > 0\n ? [\n ...groups,\n {\n id: toolsConfig?.category?.id ?? \"tools\",\n label: toolsConfig?.category?.label ?? \"Tools\",\n items: toolItems,\n },\n ]\n : groups;\n\n return {\n categories: () => allGroups.map(({ id, label }) => ({ id, label })),\n categoryItems: (id) => allGroups.find((g) => g.id === id)?.items ?? [],\n search: (query) => {\n const lower = query.toLowerCase();\n return allGroups\n .flatMap((g) => g.items)\n .filter((item) => matchesTriggerItemQuery(item, lower));\n },\n };\n }\n\n // Flat: items + (optionally) tools, all in one search pool\n const flatItems = (items ?? []).map(toTriggerItem);\n // Dedupe by id — explicit items win.\n const seen = new Set(flatItems.map((i) => i.id));\n const flatPool = [\n ...flatItems,\n ...toolItems.filter((t) => !seen.has(t.id)),\n ];\n\n return {\n categories: (): readonly Unstable_TriggerCategory[] => [],\n categoryItems: () => [],\n search: (query) => {\n const lower = query.toLowerCase();\n return flatPool.filter((item) => matchesTriggerItemQuery(item, lower));\n },\n };\n }, [items, categories, wantsTools, toolsConfig, isCategorized, toolMentions]);\n\n const directive = useMemo<Unstable_MentionDirective>(\n () => ({\n formatter: formatter ?? unstable_defaultDirectiveFormatter,\n ...(onInserted ? { onInserted } : {}),\n }),\n [formatter, onInserted],\n );\n\n return {\n adapter,\n directive,\n ...(options?.iconMap ? { iconMap: options.iconMap } : {}),\n ...(options?.fallbackIcon ? { fallbackIcon: options.fallbackIcon } : {}),\n };\n}\n\nfunction toTriggerItem(m: Unstable_Mention): Unstable_TriggerItem {\n const metadata =\n m.icon !== undefined ? { ...(m.metadata ?? {}), icon: m.icon } : m.metadata;\n return {\n id: m.id,\n type: m.type,\n label: m.label,\n ...(m.description !== undefined ? { description: m.description } : {}),\n ...(metadata !== undefined ? { metadata } : {}),\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import { AssistantClient } from "@assistant-ui/store";
2
+ import { Unsubscribe } from "@assistant-ui/core";
3
+ //#region src/unstable/useModelContextSnapshot.d.ts
4
+ /**
5
+ * Where a snapshot comes from and what counts as a change to it. Declared once
6
+ * per consumer at module scope: the subscription effect keys on this object, so
7
+ * an inline source without an `isEqual` re-reads itself into a render loop.
8
+ */
9
+ type ModelContextSnapshotSource<T> = {
10
+ /** Seeds the snapshot when it starts disabled. */
11
+ readonly empty: T;
12
+ /** Projects the fields the consumer observes out of the live model context. */
13
+ read(aui: AssistantClient): T;
14
+ subscribe(aui: AssistantClient, onChange: () => void): Unsubscribe;
15
+ /**
16
+ * An equal pair keeps the previous reference. Omitted by a consumer that
17
+ * re-derives on every notification, where a caller republishing an unchanged
18
+ * tool set is how it asks to be re-run.
19
+ */
20
+ isEqual?(previous: T, next: T): boolean;
21
+ };
22
+ /**
23
+ * `getModelContext()` rebuilds its result on every call, so a projection of the
24
+ * fields a consumer observes is held in state and refreshed on notification
25
+ * rather than read during render. Keeping the previous reference for an equal
26
+ * projection is what stops a notification carrying no observable change from
27
+ * invalidating everything derived from it.
28
+ */
29
+ declare const useModelContextSnapshot: <T>(aui: AssistantClient, enabled: boolean, source: ModelContextSnapshotSource<T>) => T;
30
+ /** Shallow own-key comparison, for a projection whose values are records. */
31
+ declare const shallowEqualRecords: (previous: Readonly<Record<string, unknown>>, next: Readonly<Record<string, unknown>>) => boolean;
32
+ //#endregion
33
+ export { ModelContextSnapshotSource, shallowEqualRecords, useModelContextSnapshot };
34
+ //# sourceMappingURL=useModelContextSnapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useModelContextSnapshot.d.ts","names":[],"sources":["../../src/unstable/useModelContextSnapshot.ts"],"mappings":";;;;;;;;KAWY,2BAA2B;;WAE5B,OAAO;;EAEhB,KAAK,KAAK,kBAAkB;EAC5B,UAAU,KAAK,iBAAiB,uBAAuB;;;;;;EAMvD,SAAS,UAAU,GAAG,MAAM;;;;;;;;;cAUjB,0BAA2B,GACtC,KAAK,iBACL,kBACA,QAAQ,2BAA2B,OAClC;;cAqBU,sBACX,UAAU,SAAS,0BACnB,MAAM,SAAS"}