@assistant-ui/react 0.15.25 → 0.15.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/dist/hooks/useToolCallElapsed.d.ts.map +1 -1
  2. package/dist/hooks/useToolCallElapsed.js +3 -2
  3. package/dist/hooks/useToolCallElapsed.js.map +1 -1
  4. package/dist/index.d.ts +30 -4
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  7. package/dist/mcp-apps/McpAppRenderer.js +3 -2
  8. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  9. package/dist/mcp-apps/app-frame.d.ts.map +1 -1
  10. package/dist/mcp-apps/app-frame.js +56 -46
  11. package/dist/mcp-apps/app-frame.js.map +1 -1
  12. package/dist/mcp-apps/csp.d.ts +4 -0
  13. package/dist/mcp-apps/csp.d.ts.map +1 -0
  14. package/dist/mcp-apps/csp.js +93 -0
  15. package/dist/mcp-apps/csp.js.map +1 -0
  16. package/dist/mcp-apps/types.d.ts +1 -0
  17. package/dist/mcp-apps/types.d.ts.map +1 -1
  18. package/dist/mcp-apps/types.js.map +1 -1
  19. package/dist/primitives/assistantModal/AssistantModalRoot.d.ts +1 -0
  20. package/dist/primitives/assistantModal/AssistantModalRoot.d.ts.map +1 -1
  21. package/dist/primitives/assistantModal/AssistantModalRoot.js.map +1 -1
  22. package/dist/primitives/attachment.d.ts +3 -1
  23. package/dist/primitives/attachment.d.ts.map +1 -1
  24. package/dist/primitives/attachment.js.map +1 -1
  25. package/dist/primitives/composer/ComposerCancel.d.ts +5 -1
  26. package/dist/primitives/composer/ComposerCancel.d.ts.map +1 -1
  27. package/dist/primitives/composer/ComposerCancel.js +14 -1
  28. package/dist/primitives/composer/ComposerCancel.js.map +1 -1
  29. package/dist/primitives/composer/ComposerInput.d.ts +14 -0
  30. package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
  31. package/dist/primitives/composer/ComposerInput.js +4 -3
  32. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  33. package/dist/primitives/composer/ComposerRoot.d.ts.map +1 -1
  34. package/dist/primitives/composer/ComposerRoot.js +24 -19
  35. package/dist/primitives/composer/ComposerRoot.js.map +1 -1
  36. package/dist/primitives/composer/composerInputSelector.d.ts +3 -0
  37. package/dist/primitives/composer/composerInputSelector.d.ts.map +1 -0
  38. package/dist/primitives/composer/composerInputSelector.js +7 -0
  39. package/dist/primitives/composer/composerInputSelector.js.map +1 -0
  40. package/dist/primitives/composer/useComposerCancelWithFocus.d.ts +2 -0
  41. package/dist/primitives/composer/useComposerCancelWithFocus.d.ts.map +1 -0
  42. package/dist/primitives/composer/useComposerCancelWithFocus.js +42 -0
  43. package/dist/primitives/composer/useComposerCancelWithFocus.js.map +1 -0
  44. package/dist/primitives/composer.d.ts +35 -14
  45. package/dist/primitives/composer.d.ts.map +1 -1
  46. package/dist/primitives/composer.js.map +1 -1
  47. package/dist/primitives/message/MessagePartsGrouped.d.ts +8 -1
  48. package/dist/primitives/message/MessagePartsGrouped.d.ts.map +1 -1
  49. package/dist/primitives/message/MessagePartsGrouped.js +58 -278
  50. package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
  51. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.d.ts.map +1 -1
  52. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +5 -2
  53. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
  54. package/dist/primitives/thread/ThreadViewport.d.ts.map +1 -1
  55. package/dist/primitives/thread/ThreadViewport.js +4 -3
  56. package/dist/primitives/thread/ThreadViewport.js.map +1 -1
  57. package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js +2 -2
  58. package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js.map +1 -1
  59. package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
  60. package/dist/primitives/thread/useThreadViewportAutoScroll.js +90 -21
  61. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  62. package/dist/sandbox-host/SandboxHost.d.ts.map +1 -1
  63. package/dist/sandbox-host/SandboxHost.js +3 -2
  64. package/dist/sandbox-host/SandboxHost.js.map +1 -1
  65. package/dist/unstable/useComposerInput.d.ts +7 -4
  66. package/dist/unstable/useComposerInput.d.ts.map +1 -1
  67. package/dist/unstable/useComposerInput.js +4 -4
  68. package/dist/unstable/useComposerInput.js.map +1 -1
  69. package/dist/unstable/useComposerInputHistory.d.ts +3 -2
  70. package/dist/unstable/useComposerInputHistory.d.ts.map +1 -1
  71. package/dist/unstable/useComposerInputHistory.js +2 -2
  72. package/dist/unstable/useComposerInputHistory.js.map +1 -1
  73. package/dist/unstable/useLiveCompletionAdapter.d.ts +3 -2
  74. package/dist/unstable/useLiveCompletionAdapter.d.ts.map +1 -1
  75. package/dist/unstable/useLiveCompletionAdapter.js +8 -7
  76. package/dist/unstable/useLiveCompletionAdapter.js.map +1 -1
  77. package/dist/unstable/useMentionAdapter.d.ts +12 -3
  78. package/dist/unstable/useMentionAdapter.d.ts.map +1 -1
  79. package/dist/unstable/useMentionAdapter.js +2 -2
  80. package/dist/unstable/useMentionAdapter.js.map +1 -1
  81. package/dist/unstable/useMessageStallDetection.d.ts +4 -2
  82. package/dist/unstable/useMessageStallDetection.d.ts.map +1 -1
  83. package/dist/unstable/useMessageStallDetection.js +2 -2
  84. package/dist/unstable/useMessageStallDetection.js.map +1 -1
  85. package/dist/unstable/useSlashCommandAdapter.d.ts +5 -2
  86. package/dist/unstable/useSlashCommandAdapter.d.ts.map +1 -1
  87. package/dist/unstable/useSlashCommandAdapter.js +5 -4
  88. package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
  89. package/dist/unstable/webmcp/useWebMcpProvider.d.ts +4 -0
  90. package/dist/unstable/webmcp/useWebMcpProvider.d.ts.map +1 -1
  91. package/dist/unstable/webmcp/useWebMcpProvider.js +2 -0
  92. package/dist/unstable/webmcp/useWebMcpProvider.js.map +1 -1
  93. package/dist/utils/getSelectionMessageId.d.ts.map +1 -1
  94. package/dist/utils/getSelectionMessageId.js +31 -8
  95. package/dist/utils/getSelectionMessageId.js.map +1 -1
  96. package/dist/utils/hooks/useOnResizeContent.d.ts.map +1 -1
  97. package/dist/utils/hooks/useOnResizeContent.js +2 -22
  98. package/dist/utils/hooks/useOnResizeContent.js.map +1 -1
  99. package/dist/utils/useIsomorphicLayoutEffect.d.ts +3 -0
  100. package/dist/utils/useIsomorphicLayoutEffect.d.ts.map +1 -0
  101. package/dist/utils/useIsomorphicLayoutEffect.js +6 -0
  102. package/dist/utils/useIsomorphicLayoutEffect.js.map +1 -0
  103. package/package.json +10 -10
  104. package/src/hooks/useToolCallElapsed.test.tsx +4 -2
  105. package/src/hooks/useToolCallElapsed.ts +3 -2
  106. package/src/index.ts +19 -2
  107. package/src/mcp-apps/McpAppRenderer.tsx +3 -9
  108. package/src/mcp-apps/app-frame.test.tsx +38 -0
  109. package/src/mcp-apps/app-frame.tsx +5 -8
  110. package/src/mcp-apps/csp.test.ts +112 -0
  111. package/src/mcp-apps/csp.ts +124 -0
  112. package/src/mcp-apps/types.ts +1 -0
  113. package/src/model-context/frame/AssistantFrame.test.ts +45 -0
  114. package/src/model-context/frame/SPEC_AssistantFrame.md +1 -0
  115. package/src/primitives/assistantModal/AssistantModalRoot.tsx +1 -0
  116. package/src/primitives/attachment.ts +4 -1
  117. package/src/primitives/composer/ComposerCancel.focus.test.tsx +307 -0
  118. package/src/primitives/composer/ComposerCancel.ts +8 -1
  119. package/src/primitives/composer/ComposerInput.tsx +19 -1
  120. package/src/primitives/composer/ComposerRoot.tsx +6 -5
  121. package/src/primitives/composer/composerInputSelector.ts +4 -0
  122. package/src/primitives/composer/useComposerCancelWithFocus.ts +57 -0
  123. package/src/primitives/composer.ts +56 -21
  124. package/src/primitives/message/MessagePartsGrouped.test.tsx +106 -2
  125. package/src/primitives/message/MessagePartsGrouped.tsx +36 -134
  126. package/src/primitives/selectionToolbar/SelectionToolbarRoot.test.tsx +20 -12
  127. package/src/primitives/selectionToolbar/SelectionToolbarRoot.tsx +6 -2
  128. package/src/primitives/thread/ThreadViewport.tsx +3 -3
  129. package/src/primitives/thread/topAnchor/useTopAnchorReserve.ts +2 -2
  130. package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +666 -63
  131. package/src/primitives/thread/useThreadViewportAutoScroll.ts +201 -73
  132. package/src/sandbox-host/SandboxHost.tsx +3 -8
  133. package/src/unstable/useComposerInput.ts +7 -4
  134. package/src/unstable/useComposerInputHistory.ts +3 -2
  135. package/src/unstable/useLiveCompletionAdapter.ts +9 -14
  136. package/src/unstable/useMentionAdapter.ts +12 -3
  137. package/src/unstable/useMessageStallDetection.ts +4 -2
  138. package/src/unstable/useSlashCommandAdapter.ts +8 -4
  139. package/src/unstable/webmcp/useWebMcpProvider.ts +4 -0
  140. package/src/utils/getSelectionMessageId.test.ts +231 -0
  141. package/src/utils/getSelectionMessageId.ts +45 -7
  142. package/src/utils/hooks/useOnResizeContent.ts +2 -29
  143. package/src/utils/useIsomorphicLayoutEffect.test.tsx +21 -0
  144. package/src/utils/useIsomorphicLayoutEffect.ts +9 -0
@@ -1 +1 @@
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","renderController","AbortController","liveContent","sb","reportError","err","String","bind","scf","renderOpts","signal","renderHtml","then","rendered","hostApi","Number","isFinite","sendMessage","window","addEventListener","fullyLoadedPromiseWithTimeout","catch","err_0","code","err_1","cleanupFailed","cleanupError","runCleanup","cleanup","t6","console","listener","removeEventListener","bridgeToDispose","abort","frameToDispose","resolvedHeight","Math","min","t7","mergedStyle","t8","t9","_c"],"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 const renderController = new AbortController();\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 signal: renderController.signal,\n ...(sb?.unsafeDocumentWrite !== undefined && {\n unsafeDocumentWrite: sb.unsafeDocumentWrite,\n }),\n };\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 let cleanupFailed = false;\n let cleanupError: unknown;\n const runCleanup = (cleanup: () => void) => {\n try {\n cleanup();\n } catch (error) {\n if (cleanupFailed) {\n console.error(error);\n } else {\n cleanupFailed = true;\n cleanupError = error;\n }\n }\n };\n\n if (onMessage) {\n const listener = onMessage;\n onMessage = null;\n runCleanup(() => window.removeEventListener(\"message\", listener));\n }\n const bridgeToDispose = bridge;\n bridge = null;\n if (bridgeToDispose) runCleanup(() => bridgeToDispose.dispose());\n runCleanup(() => renderController.abort());\n const frameToDispose = frame;\n frame = null;\n if (frameToDispose) runCleanup(() => frameToDispose.dispose());\n runCleanup(() => setContentHeight(undefined));\n\n if (cleanupFailed) throw cleanupError;\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","useRef","useState","useIsomorphicLayoutEffect","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","renderController","AbortController","liveContent","sb","reportError","err","String","bind","scf","renderOpts","signal","renderHtml","then","rendered","hostApi","Number","isFinite","sendMessage","window","addEventListener","fullyLoadedPromiseWithTimeout","catch","err_0","code","err_1","cleanupFailed","cleanupError","runCleanup","cleanup","t6","console","listener","removeEventListener","bridgeToDispose","abort","frameToDispose","resolvedHeight","Math","min","t7","mergedStyle","t8","t9","_c"],"sources":["../../src/sandbox-host/SandboxHost.tsx"],"sourcesContent":["\"use client\";\n\nimport { type CSSProperties, useEffect, useRef, useState } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"../utils/useIsomorphicLayoutEffect\";\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 useIsomorphicLayoutEffect(() => {\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 const renderController = new AbortController();\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 signal: renderController.signal,\n ...(sb?.unsafeDocumentWrite !== undefined && {\n unsafeDocumentWrite: sb.unsafeDocumentWrite,\n }),\n };\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 let cleanupFailed = false;\n let cleanupError: unknown;\n const runCleanup = (cleanup: () => void) => {\n try {\n cleanup();\n } catch (error) {\n if (cleanupFailed) {\n console.error(error);\n } else {\n cleanupFailed = true;\n cleanupError = error;\n }\n }\n };\n\n if (onMessage) {\n const listener = onMessage;\n onMessage = null;\n runCleanup(() => window.removeEventListener(\"message\", listener));\n }\n const bridgeToDispose = bridge;\n bridge = null;\n if (bridgeToDispose) runCleanup(() => bridgeToDispose.dispose());\n runCleanup(() => renderController.abort());\n const frameToDispose = frame;\n frame = null;\n if (frameToDispose) runCleanup(() => frameToDispose.dispose());\n runCleanup(() => setContentHeight(undefined));\n\n if (cleanupFailed) throw cleanupError;\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,5 +1,6 @@
1
1
  import type { ComposerSendOptions } from "@assistant-ui/core/store";
2
2
  import { type TriggerPopoverAriaProps } from "../primitives/composer/useComposerInputState.js";
3
+ /** @deprecated Experimental since 2026-06-23. Not scheduled for removal; the API may change in any release. */
3
4
  export type Unstable_UseComposerInputOptions = {
4
5
  /**
5
6
  * Disables the input in addition to the composer's own disabled sources
@@ -8,6 +9,7 @@ export type Unstable_UseComposerInputOptions = {
8
9
  */
9
10
  disabled?: boolean | undefined;
10
11
  };
12
+ /** @deprecated Experimental since 2026-06-23. Not scheduled for removal; the API may change in any release. */
11
13
  export type Unstable_ComposerInput = {
12
14
  /** Current composer text, or `""` when the composer is not editing. */
13
15
  value: string;
@@ -35,8 +37,6 @@ export type Unstable_ComposerInput = {
35
37
  canSend: boolean;
36
38
  };
37
39
  /**
38
- * @deprecated Under active development and might change without notice.
39
- *
40
40
  * Headless bridge to the composer's text value and send action, for building a
41
41
  * custom composer input without `ComposerPrimitive.Input`. It is a thin bridge,
42
42
  * not a second input: it does not own keyboard behavior, autosize, IME or
@@ -44,6 +44,8 @@ export type Unstable_ComposerInput = {
44
44
  * state. Spread `unstable_useTriggerPopoverAriaProps()` onto your element for
45
45
  * trigger-popover combobox semantics.
46
46
  *
47
+ * @deprecated Experimental since 2026-06-23. Not scheduled for removal; the API may change in any release.
48
+ *
47
49
  * @example
48
50
  * ```tsx
49
51
  * const { value, setText, send, isDisabled, canSend } = unstable_useComposerInput();
@@ -61,16 +63,17 @@ export type Unstable_ComposerInput = {
61
63
  * ```
62
64
  */
63
65
  export declare function unstable_useComposerInput(options?: Unstable_UseComposerInputOptions): Unstable_ComposerInput;
66
+ /** @deprecated Experimental since 2026-06-23. Not scheduled for removal; the API may change in any release. */
64
67
  export type Unstable_TriggerPopoverAriaProps = TriggerPopoverAriaProps;
65
68
  /**
66
- * @deprecated Under active development and might change without notice.
67
- *
68
69
  * ARIA combobox attributes for the focused element (typically the composer
69
70
  * input) describing the open trigger popover, per the WAI-ARIA editable
70
71
  * combobox pattern. Returns an empty object outside a `TriggerPopoverRoot` or
71
72
  * when no popover is open. Spread these last so they take precedence over any
72
73
  * matching ARIA props you set yourself, mirroring `ComposerPrimitive.Input`.
73
74
  *
75
+ * @deprecated Experimental since 2026-06-23. Not scheduled for removal; the API may change in any release.
76
+ *
74
77
  * @example
75
78
  * ```tsx
76
79
  * const aria = unstable_useTriggerPopoverAriaProps();
@@ -1 +1 @@
1
- {"version":3,"file":"useComposerInput.d.ts","sourceRoot":"","sources":["../../src/unstable/useComposerInput.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EACL,KAAK,uBAAuB,EAI7B,wDAAqD;AAEtD,MAAM,MAAM,gCAAgC,GAAG;IAC7C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;;OAGG;IACH,IAAI,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC1C;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,CAAC,EAAE,gCAAgC,GACzC,sBAAsB,CA0BxB;AAED,MAAM,MAAM,gCAAgC,GAAG,uBAAuB,CAAC;AAEvE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mCAAmC,IAAI,gCAAgC,CAEtF"}
1
+ {"version":3,"file":"useComposerInput.d.ts","sourceRoot":"","sources":["../../src/unstable/useComposerInput.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EACL,KAAK,uBAAuB,EAI7B,wDAAqD;AAEtD,+GAA+G;AAC/G,MAAM,MAAM,gCAAgC,GAAG;IAC7C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAChC,CAAC;AAEF,+GAA+G;AAC/G,MAAM,MAAM,sBAAsB,GAAG;IACnC,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;;OAGG;IACH,IAAI,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC1C;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,CAAC,EAAE,gCAAgC,GACzC,sBAAsB,CA0BxB;AAED,+GAA+G;AAC/G,MAAM,MAAM,gCAAgC,GAAG,uBAAuB,CAAC;AAEvE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mCAAmC,IAAI,gCAAgC,CAEtF"}
@@ -6,8 +6,6 @@ import { useAui } from "@assistant-ui/store";
6
6
  import { flushTapSync } from "@assistant-ui/tap";
7
7
  //#region src/unstable/useComposerInput.ts
8
8
  /**
9
- * @deprecated Under active development and might change without notice.
10
- *
11
9
  * Headless bridge to the composer's text value and send action, for building a
12
10
  * custom composer input without `ComposerPrimitive.Input`. It is a thin bridge,
13
11
  * not a second input: it does not own keyboard behavior, autosize, IME or
@@ -15,6 +13,8 @@ import { flushTapSync } from "@assistant-ui/tap";
15
13
  * state. Spread `unstable_useTriggerPopoverAriaProps()` onto your element for
16
14
  * trigger-popover combobox semantics.
17
15
  *
16
+ * @deprecated Experimental since 2026-06-23. Not scheduled for removal; the API may change in any release.
17
+ *
18
18
  * @example
19
19
  * ```tsx
20
20
  * const { value, setText, send, isDisabled, canSend } = unstable_useComposerInput();
@@ -55,14 +55,14 @@ function unstable_useComposerInput(options) {
55
55
  };
56
56
  }
57
57
  /**
58
- * @deprecated Under active development and might change without notice.
59
- *
60
58
  * ARIA combobox attributes for the focused element (typically the composer
61
59
  * input) describing the open trigger popover, per the WAI-ARIA editable
62
60
  * combobox pattern. Returns an empty object outside a `TriggerPopoverRoot` or
63
61
  * when no popover is open. Spread these last so they take precedence over any
64
62
  * matching ARIA props you set yourself, mirroring `ComposerPrimitive.Input`.
65
63
  *
64
+ * @deprecated Experimental since 2026-06-23. Not scheduled for removal; the API may change in any release.
65
+ *
66
66
  * @example
67
67
  * ```tsx
68
68
  * const aria = unstable_useTriggerPopoverAriaProps();
@@ -1 +1 @@
1
- {"version":3,"file":"useComposerInput.js","names":["useCallback","useAui","flushTapSync","useComposerSend","ComposerSendOptions","TriggerPopoverAriaProps","useComposerInputDisabled","useComposerInputValue","useTriggerPopoverAriaProps","Unstable_UseComposerInputOptions","disabled","Unstable_ComposerInput","value","setText","text","send","options","isDisabled","canSend","unstable_useComposerInput","aui","composer","getState","isEditing","rawSend","sendDisabled","sendOptions","Unstable_TriggerPopoverAriaProps","unstable_useTriggerPopoverAriaProps"],"sources":["../../src/unstable/useComposerInput.ts"],"sourcesContent":["\"use client\";\n\nimport { useCallback } from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport { flushTapSync } from \"@assistant-ui/tap\";\nimport { useComposerSend } from \"@assistant-ui/core/react\";\nimport type { ComposerSendOptions } from \"@assistant-ui/core/store\";\nimport {\n type TriggerPopoverAriaProps,\n useComposerInputDisabled,\n useComposerInputValue,\n useTriggerPopoverAriaProps,\n} from \"../primitives/composer/useComposerInputState\";\n\nexport type Unstable_UseComposerInputOptions = {\n /**\n * Disables the input in addition to the composer's own disabled sources\n * (thread disabled, active dictation). When disabled, `isDisabled` is `true`\n * and `canSend` is `false`.\n */\n disabled?: boolean | undefined;\n};\n\nexport type Unstable_ComposerInput = {\n /** Current composer text, or `\"\"` when the composer is not editing. */\n value: string;\n /**\n * Writes `text` into the composer, mirroring `ComposerPrimitive.Input`:\n * a no-op unless the composer is editing; the controlled value stays in\n * sync within the same tick.\n */\n setText(text: string): void;\n /**\n * Sends the current message when `canSend` is `true`; otherwise a no-op.\n * Accepts the same options as the composer send action.\n */\n send(options?: ComposerSendOptions): void;\n /**\n * Whether the input is disabled, combining the `disabled` option with the\n * composer's own disabled sources (thread disabled, active dictation).\n */\n isDisabled: boolean;\n /**\n * Whether a send is currently available. Matches `ComposerPrimitive.Send`\n * gating (non-empty editing composer, not running without queue support) and\n * is additionally `false` while `isDisabled`.\n */\n canSend: boolean;\n};\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * Headless bridge to the composer's text value and send action, for building a\n * custom composer input without `ComposerPrimitive.Input`. It is a thin bridge,\n * not a second input: it does not own keyboard behavior, autosize, IME or\n * contentEditable sync, paste/drop attachments, focus management, or rich-text\n * state. Spread `unstable_useTriggerPopoverAriaProps()` onto your element for\n * trigger-popover combobox semantics.\n *\n * @example\n * ```tsx\n * const { value, setText, send, isDisabled, canSend } = unstable_useComposerInput();\n * <textarea\n * value={value}\n * disabled={isDisabled}\n * onChange={(e) => setText(e.target.value)}\n * onKeyDown={(e) => {\n * if (e.key === \"Enter\" && !e.shiftKey && canSend) {\n * e.preventDefault();\n * send();\n * }\n * }}\n * />\n * ```\n */\nexport function unstable_useComposerInput(\n options?: Unstable_UseComposerInputOptions,\n): Unstable_ComposerInput {\n const aui = useAui();\n const value = useComposerInputValue();\n const isDisabled = useComposerInputDisabled(options?.disabled);\n\n const setText = useCallback(\n (text: string) => {\n if (!aui.composer.getState().isEditing) return;\n flushTapSync(() => {\n aui.composer.setText(text);\n });\n },\n [aui],\n );\n\n const { send: rawSend, disabled: sendDisabled } = useComposerSend();\n const canSend = !sendDisabled && !isDisabled;\n const send = useCallback(\n (sendOptions?: ComposerSendOptions) => {\n if (!canSend) return;\n rawSend(sendOptions);\n },\n [canSend, rawSend],\n );\n\n return { value, setText, send, isDisabled, canSend };\n}\n\nexport type Unstable_TriggerPopoverAriaProps = TriggerPopoverAriaProps;\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * ARIA combobox attributes for the focused element (typically the composer\n * input) describing the open trigger popover, per the WAI-ARIA editable\n * combobox pattern. Returns an empty object outside a `TriggerPopoverRoot` or\n * when no popover is open. Spread these last so they take precedence over any\n * matching ARIA props you set yourself, mirroring `ComposerPrimitive.Input`.\n *\n * @example\n * ```tsx\n * const aria = unstable_useTriggerPopoverAriaProps();\n * <textarea {...aria} />\n * ```\n */\nexport function unstable_useTriggerPopoverAriaProps(): Unstable_TriggerPopoverAriaProps {\n return useTriggerPopoverAriaProps();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EA,SAAgBmB,0BACdH,SACwB;CACxB,MAAMI,MAAMnB,OAAO;CACnB,MAAMW,QAAQL,sBAAsB;CACpC,MAAMU,aAAaX,yBAAyBU,SAASN,QAAQ;CAE7D,MAAMG,UAAUb,aACbc,SAAiB;EAChB,IAAI,CAACM,IAAIC,SAASC,SAAS,CAAC,CAACC,WAAW;EACxCrB,mBAAmB;GACjBkB,IAAIC,SAASR,QAAQC,IAAI;EAC3B,CAAC;CACH,GACA,CAACM,GAAG,CACN;CAEA,MAAM,EAAEL,MAAMS,SAASd,UAAUe,iBAAiBtB,gBAAgB;CAClE,MAAMe,UAAU,CAACO,gBAAgB,CAACR;CASlC,OAAO;EAAEL;EAAOC;EAASE,MARZf,aACV0B,gBAAsC;GACrC,IAAI,CAACR,SAAS;GACdM,QAAQE,WAAW;EACrB,GACA,CAACR,SAASM,OAAO,CAGMT;EAAME;EAAYC;CAAQ;AACrD;;;;;;;;;;;;;;;;AAmBA,SAAgBU,sCAAwE;CACtF,OAAOpB,2BAA2B;AACpC"}
1
+ {"version":3,"file":"useComposerInput.js","names":["useCallback","useAui","flushTapSync","useComposerSend","ComposerSendOptions","TriggerPopoverAriaProps","useComposerInputDisabled","useComposerInputValue","useTriggerPopoverAriaProps","Unstable_UseComposerInputOptions","disabled","Unstable_ComposerInput","value","setText","text","send","options","isDisabled","canSend","unstable_useComposerInput","aui","composer","getState","isEditing","rawSend","sendDisabled","sendOptions","Unstable_TriggerPopoverAriaProps","unstable_useTriggerPopoverAriaProps"],"sources":["../../src/unstable/useComposerInput.ts"],"sourcesContent":["\"use client\";\n\nimport { useCallback } from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport { flushTapSync } from \"@assistant-ui/tap\";\nimport { useComposerSend } from \"@assistant-ui/core/react\";\nimport type { ComposerSendOptions } from \"@assistant-ui/core/store\";\nimport {\n type TriggerPopoverAriaProps,\n useComposerInputDisabled,\n useComposerInputValue,\n useTriggerPopoverAriaProps,\n} from \"../primitives/composer/useComposerInputState\";\n\n/** @deprecated Experimental since 2026-06-23. Not scheduled for removal; the API may change in any release. */\nexport type Unstable_UseComposerInputOptions = {\n /**\n * Disables the input in addition to the composer's own disabled sources\n * (thread disabled, active dictation). When disabled, `isDisabled` is `true`\n * and `canSend` is `false`.\n */\n disabled?: boolean | undefined;\n};\n\n/** @deprecated Experimental since 2026-06-23. Not scheduled for removal; the API may change in any release. */\nexport type Unstable_ComposerInput = {\n /** Current composer text, or `\"\"` when the composer is not editing. */\n value: string;\n /**\n * Writes `text` into the composer, mirroring `ComposerPrimitive.Input`:\n * a no-op unless the composer is editing; the controlled value stays in\n * sync within the same tick.\n */\n setText(text: string): void;\n /**\n * Sends the current message when `canSend` is `true`; otherwise a no-op.\n * Accepts the same options as the composer send action.\n */\n send(options?: ComposerSendOptions): void;\n /**\n * Whether the input is disabled, combining the `disabled` option with the\n * composer's own disabled sources (thread disabled, active dictation).\n */\n isDisabled: boolean;\n /**\n * Whether a send is currently available. Matches `ComposerPrimitive.Send`\n * gating (non-empty editing composer, not running without queue support) and\n * is additionally `false` while `isDisabled`.\n */\n canSend: boolean;\n};\n\n/**\n * Headless bridge to the composer's text value and send action, for building a\n * custom composer input without `ComposerPrimitive.Input`. It is a thin bridge,\n * not a second input: it does not own keyboard behavior, autosize, IME or\n * contentEditable sync, paste/drop attachments, focus management, or rich-text\n * state. Spread `unstable_useTriggerPopoverAriaProps()` onto your element for\n * trigger-popover combobox semantics.\n *\n * @deprecated Experimental since 2026-06-23. Not scheduled for removal; the API may change in any release.\n *\n * @example\n * ```tsx\n * const { value, setText, send, isDisabled, canSend } = unstable_useComposerInput();\n * <textarea\n * value={value}\n * disabled={isDisabled}\n * onChange={(e) => setText(e.target.value)}\n * onKeyDown={(e) => {\n * if (e.key === \"Enter\" && !e.shiftKey && canSend) {\n * e.preventDefault();\n * send();\n * }\n * }}\n * />\n * ```\n */\nexport function unstable_useComposerInput(\n options?: Unstable_UseComposerInputOptions,\n): Unstable_ComposerInput {\n const aui = useAui();\n const value = useComposerInputValue();\n const isDisabled = useComposerInputDisabled(options?.disabled);\n\n const setText = useCallback(\n (text: string) => {\n if (!aui.composer.getState().isEditing) return;\n flushTapSync(() => {\n aui.composer.setText(text);\n });\n },\n [aui],\n );\n\n const { send: rawSend, disabled: sendDisabled } = useComposerSend();\n const canSend = !sendDisabled && !isDisabled;\n const send = useCallback(\n (sendOptions?: ComposerSendOptions) => {\n if (!canSend) return;\n rawSend(sendOptions);\n },\n [canSend, rawSend],\n );\n\n return { value, setText, send, isDisabled, canSend };\n}\n\n/** @deprecated Experimental since 2026-06-23. Not scheduled for removal; the API may change in any release. */\nexport type Unstable_TriggerPopoverAriaProps = TriggerPopoverAriaProps;\n\n/**\n * ARIA combobox attributes for the focused element (typically the composer\n * input) describing the open trigger popover, per the WAI-ARIA editable\n * combobox pattern. Returns an empty object outside a `TriggerPopoverRoot` or\n * when no popover is open. Spread these last so they take precedence over any\n * matching ARIA props you set yourself, mirroring `ComposerPrimitive.Input`.\n *\n * @deprecated Experimental since 2026-06-23. Not scheduled for removal; the API may change in any release.\n *\n * @example\n * ```tsx\n * const aria = unstable_useTriggerPopoverAriaProps();\n * <textarea {...aria} />\n * ```\n */\nexport function unstable_useTriggerPopoverAriaProps(): Unstable_TriggerPopoverAriaProps {\n return useTriggerPopoverAriaProps();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8EA,SAAgBmB,0BACdH,SACwB;CACxB,MAAMI,MAAMnB,OAAO;CACnB,MAAMW,QAAQL,sBAAsB;CACpC,MAAMU,aAAaX,yBAAyBU,SAASN,QAAQ;CAE7D,MAAMG,UAAUb,aACbc,SAAiB;EAChB,IAAI,CAACM,IAAIC,SAASC,SAAS,CAAC,CAACC,WAAW;EACxCrB,mBAAmB;GACjBkB,IAAIC,SAASR,QAAQC,IAAI;EAC3B,CAAC;CACH,GACA,CAACM,GAAG,CACN;CAEA,MAAM,EAAEL,MAAMS,SAASd,UAAUe,iBAAiBtB,gBAAgB;CAClE,MAAMe,UAAU,CAACO,gBAAgB,CAACR;CASlC,OAAO;EAAEL;EAAOC;EAASE,MARZf,aACV0B,gBAAsC;GACrC,IAAI,CAACR,SAAS;GACdM,QAAQE,WAAW;EACrB,GACA,CAACR,SAASM,OAAO,CAGMT;EAAME;EAAYC;CAAQ;AACrD;;;;;;;;;;;;;;;;AAoBA,SAAgBU,sCAAwE;CACtF,OAAOpB,2BAA2B;AACpC"}
@@ -1,11 +1,10 @@
1
1
  import { type KeyboardEventHandler } from "react";
2
+ /** @deprecated Experimental since 2026-06-11. Not scheduled for removal; the API may change in any release. */
2
3
  export type Unstable_ComposerInputHistory = {
3
4
  /** Keydown handler to spread onto `ComposerPrimitive.Input`. */
4
5
  onKeyDown: KeyboardEventHandler<HTMLTextAreaElement>;
5
6
  };
6
7
  /**
7
- * @deprecated Under active development and might change without notice.
8
- *
9
8
  * Terminal-style input history for the thread composer: ArrowUp on an
10
9
  * empty draft recalls previously sent user messages (newest first),
11
10
  * ArrowDown steps back toward the newest and finally restores the draft
@@ -17,6 +16,8 @@ export type Unstable_ComposerInputHistory = {
17
16
  * to modifier keys, and to consumer handlers that already called
18
17
  * `preventDefault`. It is inert on edit composers.
19
18
  *
19
+ * @deprecated Experimental since 2026-06-11. Not scheduled for removal; the API may change in any release.
20
+ *
20
21
  * @example
21
22
  * ```tsx
22
23
  * const history = unstable_useComposerInputHistory();
@@ -1 +1 @@
1
- {"version":3,"file":"useComposerInputHistory.d.ts","sourceRoot":"","sources":["../../src/unstable/useComposerInputHistory.ts"],"names":[],"mappings":"AAEA,OAAO,EAML,KAAK,oBAAoB,EAC1B,MAAM,OAAO,CAAC;AAOf,MAAM,MAAM,6BAA6B,GAAG;IAC1C,gEAAgE;IAChE,SAAS,EAAE,oBAAoB,CAAC,mBAAmB,CAAC,CAAC;CACtD,CAAC;AA2BF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gCAAgC,IAAI,6BAA6B,CA6FhF"}
1
+ {"version":3,"file":"useComposerInputHistory.d.ts","sourceRoot":"","sources":["../../src/unstable/useComposerInputHistory.ts"],"names":[],"mappings":"AAEA,OAAO,EAML,KAAK,oBAAoB,EAC1B,MAAM,OAAO,CAAC;AAOf,+GAA+G;AAC/G,MAAM,MAAM,6BAA6B,GAAG;IAC1C,gEAAgE;IAChE,SAAS,EAAE,oBAAoB,CAAC,mBAAmB,CAAC,CAAC;CACtD,CAAC;AA2BF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gCAAgC,IAAI,6BAA6B,CA6FhF"}
@@ -20,8 +20,6 @@ const deriveHistory = (messages) => {
20
20
  const isOnFirstLine = (value, caret) => !value.slice(0, caret).includes("\n");
21
21
  const isOnLastLine = (value, caret) => !value.slice(caret).includes("\n");
22
22
  /**
23
- * @deprecated Under active development and might change without notice.
24
- *
25
23
  * Terminal-style input history for the thread composer: ArrowUp on an
26
24
  * empty draft recalls previously sent user messages (newest first),
27
25
  * ArrowDown steps back toward the newest and finally restores the draft
@@ -33,6 +31,8 @@ const isOnLastLine = (value, caret) => !value.slice(caret).includes("\n");
33
31
  * to modifier keys, and to consumer handlers that already called
34
32
  * `preventDefault`. It is inert on edit composers.
35
33
  *
34
+ * @deprecated Experimental since 2026-06-11. Not scheduled for removal; the API may change in any release.
35
+ *
36
36
  * @example
37
37
  * ```tsx
38
38
  * const history = unstable_useComposerInputHistory();
@@ -1 +1 @@
1
- {"version":3,"file":"useComposerInputHistory.js","names":["useCallback","useEffect","useMemo","useRef","KeyboardEvent","KeyboardEventHandler","useAui","flushTapSync","ThreadMessage","getThreadMessageText","useTriggerPopoverRootContextOptional","Unstable_ComposerInputHistory","onKeyDown","HTMLTextAreaElement","BrowseState","cursor","draftSnapshot","lastRecalledText","deriveHistory","messages","entries","i","length","message","role","text","trim","push","isOnFirstLine","value","caret","slice","includes","isOnLastLine","unstable_useComposerInputHistory","aui","popoverCtx","browseRef","composer","getState","type","undefined","on","current","e","defaultPrevented","key","nativeEvent","isComposing","shiftKey","ctrlKey","metaKey","altKey","getActiveAria","textarea","currentTarget","selectionStart","selectionEnd","browse","commitText","setText","requestAnimationFrame","setSelectionRange","preventDefault","recall","history","entry","thread","next"],"sources":["../../src/unstable/useComposerInputHistory.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n type KeyboardEvent,\n type KeyboardEventHandler,\n} from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport { flushTapSync } from \"@assistant-ui/tap\";\nimport type { ThreadMessage } from \"@assistant-ui/core\";\nimport { getThreadMessageText } from \"@assistant-ui/core/internal\";\nimport { useTriggerPopoverRootContextOptional } from \"../primitives/composer/trigger/TriggerPopoverRootContext\";\n\nexport type Unstable_ComposerInputHistory = {\n /** Keydown handler to spread onto `ComposerPrimitive.Input`. */\n onKeyDown: KeyboardEventHandler<HTMLTextAreaElement>;\n};\n\ntype BrowseState = {\n cursor: number;\n draftSnapshot: string;\n lastRecalledText: string;\n};\n\nconst deriveHistory = (messages: readonly ThreadMessage[]): string[] => {\n const entries: string[] = [];\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages[i]!;\n if (message.role !== \"user\") continue;\n const text = getThreadMessageText(message).trim();\n if (!text) continue;\n if (entries[entries.length - 1] === text) continue;\n entries.push(text);\n }\n return entries;\n};\n\nconst isOnFirstLine = (value: string, caret: number): boolean =>\n !value.slice(0, caret).includes(\"\\n\");\n\nconst isOnLastLine = (value: string, caret: number): boolean =>\n !value.slice(caret).includes(\"\\n\");\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * Terminal-style input history for the thread composer: ArrowUp on an\n * empty draft recalls previously sent user messages (newest first),\n * ArrowDown steps back toward the newest and finally restores the draft\n * that was being typed when browsing started.\n *\n * Recall only triggers when the caret is on the first/last line with no\n * selection, so multi-line editing keeps native arrow behavior. The\n * handler yields to an open mention/slash popover, to IME composition,\n * to modifier keys, and to consumer handlers that already called\n * `preventDefault`. It is inert on edit composers.\n *\n * @example\n * ```tsx\n * const history = unstable_useComposerInputHistory();\n * <ComposerPrimitive.Input {...history} />\n * ```\n */\nexport function unstable_useComposerInputHistory(): Unstable_ComposerInputHistory {\n const aui = useAui();\n const popoverCtx = useTriggerPopoverRootContextOptional();\n const browseRef = useRef<BrowseState | null>(null);\n\n useEffect(() => {\n if (aui.composer.getState().type !== \"thread\") return undefined;\n\n return aui.on(\"threads.selectionChanged\", () => {\n browseRef.current = null;\n });\n }, [aui]);\n\n const onKeyDown = useCallback(\n (e: KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.defaultPrevented) return;\n if (e.key !== \"ArrowUp\" && e.key !== \"ArrowDown\") return;\n if (e.nativeEvent.isComposing) return;\n if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) return;\n if (popoverCtx && popoverCtx.getActiveAria() !== null) return;\n if (aui.composer.getState().type !== \"thread\") return;\n\n const textarea = e.currentTarget;\n const { selectionStart, selectionEnd, value } = textarea;\n if (selectionStart !== selectionEnd) return;\n\n if (browseRef.current && value !== browseRef.current.lastRecalledText) {\n browseRef.current = null;\n }\n const browse = browseRef.current;\n\n const commitText = (text: string): void => {\n flushTapSync(() => aui.composer.setText(text));\n // React's controlled-value commit restores the pre-recall caret;\n // reposition after the commit, before paint.\n requestAnimationFrame(() => {\n textarea.setSelectionRange(text.length, text.length);\n });\n e.preventDefault();\n };\n\n const recall = (\n history: readonly string[],\n cursor: number,\n draftSnapshot: string,\n ): void => {\n const entry = history[cursor];\n if (entry === undefined) {\n e.preventDefault();\n return;\n }\n browseRef.current = { cursor, draftSnapshot, lastRecalledText: entry };\n commitText(entry);\n };\n\n if (e.key === \"ArrowUp\") {\n if (!isOnFirstLine(value, selectionStart)) return;\n\n if (!browse) {\n if (value.trim() !== \"\") return;\n const history = deriveHistory(aui.thread.getState().messages);\n if (history.length === 0) return;\n recall(history, 0, value);\n return;\n }\n\n const history = deriveHistory(aui.thread.getState().messages);\n const next = browse.cursor + 1;\n if (next >= history.length) {\n e.preventDefault();\n return;\n }\n recall(history, next, browse.draftSnapshot);\n return;\n }\n\n if (!browse) return;\n if (!isOnLastLine(value, selectionEnd)) return;\n\n const next = browse.cursor - 1;\n if (next < 0) {\n browseRef.current = null;\n commitText(browse.draftSnapshot);\n return;\n }\n\n const history = deriveHistory(aui.thread.getState().messages);\n recall(history, next, browse.draftSnapshot);\n },\n [aui, popoverCtx],\n );\n\n return useMemo(() => ({ onKeyDown }), [onKeyDown]);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useComposerInputHistory.js","names":["useCallback","useEffect","useMemo","useRef","KeyboardEvent","KeyboardEventHandler","useAui","flushTapSync","ThreadMessage","getThreadMessageText","useTriggerPopoverRootContextOptional","Unstable_ComposerInputHistory","onKeyDown","HTMLTextAreaElement","BrowseState","cursor","draftSnapshot","lastRecalledText","deriveHistory","messages","entries","i","length","message","role","text","trim","push","isOnFirstLine","value","caret","slice","includes","isOnLastLine","unstable_useComposerInputHistory","aui","popoverCtx","browseRef","composer","getState","type","undefined","on","current","e","defaultPrevented","key","nativeEvent","isComposing","shiftKey","ctrlKey","metaKey","altKey","getActiveAria","textarea","currentTarget","selectionStart","selectionEnd","browse","commitText","setText","requestAnimationFrame","setSelectionRange","preventDefault","recall","history","entry","thread","next"],"sources":["../../src/unstable/useComposerInputHistory.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n type KeyboardEvent,\n type KeyboardEventHandler,\n} from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport { flushTapSync } from \"@assistant-ui/tap\";\nimport type { ThreadMessage } from \"@assistant-ui/core\";\nimport { getThreadMessageText } from \"@assistant-ui/core/internal\";\nimport { useTriggerPopoverRootContextOptional } from \"../primitives/composer/trigger/TriggerPopoverRootContext\";\n\n/** @deprecated Experimental since 2026-06-11. Not scheduled for removal; the API may change in any release. */\nexport type Unstable_ComposerInputHistory = {\n /** Keydown handler to spread onto `ComposerPrimitive.Input`. */\n onKeyDown: KeyboardEventHandler<HTMLTextAreaElement>;\n};\n\ntype BrowseState = {\n cursor: number;\n draftSnapshot: string;\n lastRecalledText: string;\n};\n\nconst deriveHistory = (messages: readonly ThreadMessage[]): string[] => {\n const entries: string[] = [];\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages[i]!;\n if (message.role !== \"user\") continue;\n const text = getThreadMessageText(message).trim();\n if (!text) continue;\n if (entries[entries.length - 1] === text) continue;\n entries.push(text);\n }\n return entries;\n};\n\nconst isOnFirstLine = (value: string, caret: number): boolean =>\n !value.slice(0, caret).includes(\"\\n\");\n\nconst isOnLastLine = (value: string, caret: number): boolean =>\n !value.slice(caret).includes(\"\\n\");\n\n/**\n * Terminal-style input history for the thread composer: ArrowUp on an\n * empty draft recalls previously sent user messages (newest first),\n * ArrowDown steps back toward the newest and finally restores the draft\n * that was being typed when browsing started.\n *\n * Recall only triggers when the caret is on the first/last line with no\n * selection, so multi-line editing keeps native arrow behavior. The\n * handler yields to an open mention/slash popover, to IME composition,\n * to modifier keys, and to consumer handlers that already called\n * `preventDefault`. It is inert on edit composers.\n *\n * @deprecated Experimental since 2026-06-11. Not scheduled for removal; the API may change in any release.\n *\n * @example\n * ```tsx\n * const history = unstable_useComposerInputHistory();\n * <ComposerPrimitive.Input {...history} />\n * ```\n */\nexport function unstable_useComposerInputHistory(): Unstable_ComposerInputHistory {\n const aui = useAui();\n const popoverCtx = useTriggerPopoverRootContextOptional();\n const browseRef = useRef<BrowseState | null>(null);\n\n useEffect(() => {\n if (aui.composer.getState().type !== \"thread\") return undefined;\n\n return aui.on(\"threads.selectionChanged\", () => {\n browseRef.current = null;\n });\n }, [aui]);\n\n const onKeyDown = useCallback(\n (e: KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.defaultPrevented) return;\n if (e.key !== \"ArrowUp\" && e.key !== \"ArrowDown\") return;\n if (e.nativeEvent.isComposing) return;\n if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) return;\n if (popoverCtx && popoverCtx.getActiveAria() !== null) return;\n if (aui.composer.getState().type !== \"thread\") return;\n\n const textarea = e.currentTarget;\n const { selectionStart, selectionEnd, value } = textarea;\n if (selectionStart !== selectionEnd) return;\n\n if (browseRef.current && value !== browseRef.current.lastRecalledText) {\n browseRef.current = null;\n }\n const browse = browseRef.current;\n\n const commitText = (text: string): void => {\n flushTapSync(() => aui.composer.setText(text));\n // React's controlled-value commit restores the pre-recall caret;\n // reposition after the commit, before paint.\n requestAnimationFrame(() => {\n textarea.setSelectionRange(text.length, text.length);\n });\n e.preventDefault();\n };\n\n const recall = (\n history: readonly string[],\n cursor: number,\n draftSnapshot: string,\n ): void => {\n const entry = history[cursor];\n if (entry === undefined) {\n e.preventDefault();\n return;\n }\n browseRef.current = { cursor, draftSnapshot, lastRecalledText: entry };\n commitText(entry);\n };\n\n if (e.key === \"ArrowUp\") {\n if (!isOnFirstLine(value, selectionStart)) return;\n\n if (!browse) {\n if (value.trim() !== \"\") return;\n const history = deriveHistory(aui.thread.getState().messages);\n if (history.length === 0) return;\n recall(history, 0, value);\n return;\n }\n\n const history = deriveHistory(aui.thread.getState().messages);\n const next = browse.cursor + 1;\n if (next >= history.length) {\n e.preventDefault();\n return;\n }\n recall(history, next, browse.draftSnapshot);\n return;\n }\n\n if (!browse) return;\n if (!isOnLastLine(value, selectionEnd)) return;\n\n const next = browse.cursor - 1;\n if (next < 0) {\n browseRef.current = null;\n commitText(browse.draftSnapshot);\n return;\n }\n\n const history = deriveHistory(aui.thread.getState().messages);\n recall(history, next, browse.draftSnapshot);\n },\n [aui, popoverCtx],\n );\n\n return useMemo(() => ({ onKeyDown }), [onKeyDown]);\n}\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import type { Unstable_TriggerAdapter, Unstable_TriggerItem } from "@assistant-ui/core";
2
+ /** @deprecated Experimental since 2026-06-15. Not scheduled for removal; the API may change in any release. */
2
3
  export type Unstable_UseLiveCompletionAdapterOptions = {
3
4
  /**
4
5
  * Fetches the items for a query from an async source. Called debounced; the
@@ -17,8 +18,6 @@ export type Unstable_UseLiveCompletionAdapterOptions = {
17
18
  readonly enabled?: boolean | undefined;
18
19
  };
19
20
  /**
20
- * @deprecated Under active development and may change without notice.
21
- *
22
21
  * Bridges an async completion source (a server search, a gateway RPC) into the
23
22
  * synchronous `Unstable_TriggerAdapter` that `ComposerTriggerPopover` consumes.
24
23
  * `search(query)` returns the last fetched items synchronously and schedules a
@@ -29,6 +28,8 @@ export type Unstable_UseLiveCompletionAdapterOptions = {
29
28
  * `isLoading` is `true` while a fetch is in flight. Pass it to the popover's
30
29
  * `isLoading` prop to render a loading state.
31
30
  *
31
+ * @deprecated Experimental since 2026-06-15. Not scheduled for removal; the API may change in any release.
32
+ *
32
33
  * @example
33
34
  * ```tsx
34
35
  * const mentions = unstable_useLiveCompletionAdapter({
@@ -1 +1 @@
1
- {"version":3,"file":"useLiveCompletionAdapter.d.ts","sourceRoot":"","sources":["../../src/unstable/useLiveCompletionAdapter.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,uBAAuB,EACvB,oBAAoB,EACrB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,wCAAwC,GAAG;IACrD;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,oBAAoB,EAAE,CAAC,CAAC;IAC9E;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAChD,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,qFAAqF;IACrF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACxC,CAAC;AAKF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,wCAAwC,GAChD;IAAE,OAAO,EAAE,uBAAuB,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAiK1D"}
1
+ {"version":3,"file":"useLiveCompletionAdapter.d.ts","sourceRoot":"","sources":["../../src/unstable/useLiveCompletionAdapter.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,uBAAuB,EACvB,oBAAoB,EACrB,MAAM,oBAAoB,CAAC;AAE5B,+GAA+G;AAC/G,MAAM,MAAM,wCAAwC,GAAG;IACrD;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,oBAAoB,EAAE,CAAC,CAAC;IAC9E;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAChD,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,qFAAqF;IACrF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACxC,CAAC;AAKF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,wCAAwC,GAChD;IAAE,OAAO,EAAE,uBAAuB,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAiK1D"}
@@ -1,11 +1,10 @@
1
1
  "use client";
2
- import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "@assistant-ui/tap/react-shim";
2
+ import { useIsomorphicLayoutEffect } from "../utils/useIsomorphicLayoutEffect.js";
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from "@assistant-ui/tap/react-shim";
3
4
  //#region src/unstable/useLiveCompletionAdapter.ts
4
5
  /** Sentinel that no real query (including the empty string) equals, so the first query always fetches. */
5
6
  const NO_QUERY = "\0";
6
7
  /**
7
- * @deprecated Under active development and may change without notice.
8
- *
9
8
  * Bridges an async completion source (a server search, a gateway RPC) into the
10
9
  * synchronous `Unstable_TriggerAdapter` that `ComposerTriggerPopover` consumes.
11
10
  * `search(query)` returns the last fetched items synchronously and schedules a
@@ -16,6 +15,8 @@ const NO_QUERY = "\0";
16
15
  * `isLoading` is `true` while a fetch is in flight. Pass it to the popover's
17
16
  * `isLoading` prop to render a loading state.
18
17
  *
18
+ * @deprecated Experimental since 2026-06-15. Not scheduled for removal; the API may change in any release.
19
+ *
19
20
  * @example
20
21
  * ```tsx
21
22
  * const mentions = unstable_useLiveCompletionAdapter({
@@ -39,7 +40,7 @@ function unstable_useLiveCompletionAdapter(options) {
39
40
  });
40
41
  const [isLoading, setIsLoading] = useState(false);
41
42
  const fetcherRef = useRef(fetcher);
42
- useLayoutEffect(() => {
43
+ useIsomorphicLayoutEffect(() => {
43
44
  fetcherRef.current = fetcher;
44
45
  }, [fetcher]);
45
46
  const timerRef = useRef(null);
@@ -107,7 +108,7 @@ function unstable_useLiveCompletionAdapter(options) {
107
108
  rearmPendingRetry
108
109
  ]);
109
110
  const scheduleFetchRef = useRef(scheduleFetch);
110
- useLayoutEffect(() => {
111
+ useIsomorphicLayoutEffect(() => {
111
112
  scheduleFetchRef.current = scheduleFetch;
112
113
  }, [scheduleFetch]);
113
114
  const invalidatePending = useCallback(() => {
@@ -118,7 +119,7 @@ function unstable_useLiveCompletionAdapter(options) {
118
119
  setIsLoading(false);
119
120
  }, [cancelTimer, rearmPendingRetry]);
120
121
  const cacheKeyRef = useRef(cacheKey);
121
- useLayoutEffect(() => {
122
+ useIsomorphicLayoutEffect(() => {
122
123
  if (cacheKeyRef.current === cacheKey) return;
123
124
  cacheKeyRef.current = cacheKey;
124
125
  invalidatePending();
@@ -139,7 +140,7 @@ function unstable_useLiveCompletionAdapter(options) {
139
140
  failed: false
140
141
  });
141
142
  }, [enabled, invalidatePending]);
142
- useLayoutEffect(() => {
143
+ useIsomorphicLayoutEffect(() => {
143
144
  inactiveRef.current = false;
144
145
  const deferredQuery = deferredQueryRef.current;
145
146
  deferredQueryRef.current = 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","inactiveRef","deferredQueryRef","cancelTimer","clearTimeout","rearmPendingRetry","scheduleFetch","token","resolve","then","scheduleFetchRef","invalidatePending","cacheKeyRef","s","deferredQuery","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 const inactiveRef = useRef(true);\n const deferredQueryRef = 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 (inactiveRef.current) {\n deferredQueryRef.current = query;\n return;\n }\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 scheduleFetchRef = useRef(scheduleFetch);\n useLayoutEffect(() => {\n scheduleFetchRef.current = scheduleFetch;\n }, [scheduleFetch]);\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 // Render-time searches can outlive an abandoned render, so they only arm\n // request work after this hook commits.\n useLayoutEffect(() => {\n inactiveRef.current = false;\n const deferredQuery = deferredQueryRef.current;\n deferredQueryRef.current = null;\n if (deferredQuery !== null) scheduleFetchRef.current(deferredQuery);\n return () => {\n inactiveRef.current = true;\n invalidatePending();\n };\n }, [invalidatePending]);\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 {\n queueMicrotask(() => {\n if (\n deferredQueryRef.current !== null &&\n deferredQueryRef.current !== query\n ) {\n deferredQueryRef.current = null;\n }\n if (\n pendingQueryRef.current !== null &&\n pendingQueryRef.current !== query\n ) {\n invalidatePending();\n }\n });\n }\n return state.items;\n },\n }),\n [state, scheduleFetch, invalidatePending],\n );\n\n return { adapter, isLoading };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useLiveCompletionAdapter.js","names":["useCallback","useEffect","useMemo","useRef","useState","useIsomorphicLayoutEffect","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","inactiveRef","deferredQueryRef","cancelTimer","clearTimeout","rearmPendingRetry","scheduleFetch","token","resolve","then","scheduleFetchRef","invalidatePending","cacheKeyRef","s","deferredQuery","categories","categoryItems","search","queueMicrotask"],"sources":["../../src/unstable/useLiveCompletionAdapter.ts"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"../utils/useIsomorphicLayoutEffect\";\nimport type {\n Unstable_TriggerAdapter,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\n\n/** @deprecated Experimental since 2026-06-15. Not scheduled for removal; the API may change in any release. */\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 * 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 * @deprecated Experimental since 2026-06-15. Not scheduled for removal; the API may change in any release.\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 useIsomorphicLayoutEffect(() => {\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 const inactiveRef = useRef(true);\n const deferredQueryRef = 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 (inactiveRef.current) {\n deferredQueryRef.current = query;\n return;\n }\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 scheduleFetchRef = useRef(scheduleFetch);\n useIsomorphicLayoutEffect(() => {\n scheduleFetchRef.current = scheduleFetch;\n }, [scheduleFetch]);\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 useIsomorphicLayoutEffect(() => {\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 // Render-time searches can outlive an abandoned render, so they only arm\n // request work after this hook commits.\n useIsomorphicLayoutEffect(() => {\n inactiveRef.current = false;\n const deferredQuery = deferredQueryRef.current;\n deferredQueryRef.current = null;\n if (deferredQuery !== null) scheduleFetchRef.current(deferredQuery);\n return () => {\n inactiveRef.current = true;\n invalidatePending();\n };\n }, [invalidatePending]);\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 {\n queueMicrotask(() => {\n if (\n deferredQueryRef.current !== null &&\n deferredQueryRef.current !== query\n ) {\n deferredQueryRef.current = null;\n }\n if (\n pendingQueryRef.current !== null &&\n pendingQueryRef.current !== query\n ) {\n invalidatePending();\n }\n });\n }\n return state.items;\n },\n }),\n [state, scheduleFetch, invalidatePending],\n );\n\n return { adapter, isLoading };\n}\n"],"mappings":"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"}
@@ -1,10 +1,15 @@
1
1
  import { type FC } from "react";
2
2
  import type { Unstable_DirectiveFormatter, Unstable_TriggerAdapter, Unstable_TriggerItem } from "@assistant-ui/core";
3
3
  import type { ReadonlyJSONObject } from "assistant-stream/utils";
4
- /** Icon component shape consumed by `ComposerTriggerPopover`'s `iconMap`. */
4
+ /**
5
+ * Icon component shape consumed by `ComposerTriggerPopover`'s `iconMap`.
6
+ *
7
+ * @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release.
8
+ */
5
9
  export type Unstable_IconComponent = FC<{
6
10
  className?: string;
7
11
  }>;
12
+ /** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */
8
13
  export type Unstable_Mention = {
9
14
  readonly id: string;
10
15
  readonly type: string;
@@ -14,11 +19,13 @@ export type Unstable_Mention = {
14
19
  readonly icon?: string | undefined;
15
20
  readonly metadata?: ReadonlyJSONObject | undefined;
16
21
  };
22
+ /** @deprecated Experimental since 2026-03-16. Not scheduled for removal; the API may change in any release. */
17
23
  export type Unstable_MentionCategory = {
18
24
  readonly id: string;
19
25
  readonly label: string;
20
26
  readonly items: readonly Unstable_Mention[];
21
27
  };
28
+ /** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */
22
29
  export type Unstable_ModelContextToolsOptions = {
23
30
  /**
24
31
  * Wrap tools in a dedicated category. Selects drill-down mode on its own
@@ -33,6 +40,7 @@ export type Unstable_ModelContextToolsOptions = {
33
40
  /** Default icon key for each tool. */
34
41
  readonly icon?: string;
35
42
  };
43
+ /** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */
36
44
  export type Unstable_UseMentionAdapterOptions = {
37
45
  /**
38
46
  * Flat mention list. Ignored when `categories` is set, and keeps the
@@ -60,17 +68,18 @@ export type Unstable_UseMentionAdapterOptions = {
60
68
  /** Fallback icon when no entry in `iconMap` matches. */
61
69
  readonly fallbackIcon?: Unstable_IconComponent;
62
70
  };
71
+ /** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */
63
72
  export type Unstable_MentionDirective = {
64
73
  readonly formatter: Unstable_DirectiveFormatter;
65
74
  readonly onInserted?: ((item: Unstable_TriggerItem) => void) | undefined;
66
75
  };
67
76
  /**
68
- * @deprecated Under active development and might change without notice.
69
- *
70
77
  * Creates a spreadable `{ adapter, directive }` bundle for `@` mentions.
71
78
  * Supports tools registered in model context, explicit items, or both —
72
79
  * flat or categorized.
73
80
  *
81
+ * @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release.
82
+ *
74
83
  * @example
75
84
  * ```tsx
76
85
  * const mention = unstable_useMentionAdapter();
@@ -1 +1 @@
1
- {"version":3,"file":"useMentionAdapter.d.ts","sourceRoot":"","sources":["../../src/unstable/useMentionAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAW,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AAEzC,OAAO,KAAK,EACV,2BAA2B,EAC3B,uBAAuB,EAEvB,oBAAoB,EACrB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAQjE,6EAA6E;AAC7E,MAAM,MAAM,sBAAsB,GAAG,EAAE,CAAC;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEhE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;CACpD,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC7C,CAAC;AAEF,MAAM,MAAM,iCAAiC,GAAG;IAC9C;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACpE,oCAAoC;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;IACpD,sCAAsC;IACtC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,iCAAiC,GAAG;IAC9C;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC7C,sDAAsD;IACtD,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,wBAAwB,EAAE,CAAC;IAC1D;;;;;;;;OAQG;IACH,QAAQ,CAAC,wBAAwB,CAAC,EAC9B,OAAO,GACP,iCAAiC,CAAC;IACtC,uEAAuE;IACvE,QAAQ,CAAC,SAAS,CAAC,EAAE,2BAA2B,CAAC;IACjD,yDAAyD;IACzD,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC3D,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;IAC1D,wDAAwD;IACxD,QAAQ,CAAC,YAAY,CAAC,EAAE,sBAAsB,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,SAAS,EAAE,2BAA2B,CAAC;IAChD,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,oBAAoB,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CAC1E,CAAC;AAsCF;;;;;;;;;;;;GAYG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,CAAC,EAAE,iCAAiC,GAC1C;IACD,OAAO,EAAE,uBAAuB,CAAC;IACjC,SAAS,EAAE,yBAAyB,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;IACjD,YAAY,CAAC,EAAE,sBAAsB,CAAC;CACvC,CAoGA"}
1
+ {"version":3,"file":"useMentionAdapter.d.ts","sourceRoot":"","sources":["../../src/unstable/useMentionAdapter.ts"],"names":[],"mappings":"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"}
@@ -29,12 +29,12 @@ const toolMentionSource = {
29
29
  isEqual: shallowEqualRecords
30
30
  };
31
31
  /**
32
- * @deprecated Under active development and might change without notice.
33
- *
34
32
  * Creates a spreadable `{ adapter, directive }` bundle for `@` mentions.
35
33
  * Supports tools registered in model context, explicit items, or both —
36
34
  * flat or categorized.
37
35
  *
36
+ * @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release.
37
+ *
38
38
  * @example
39
39
  * ```tsx
40
40
  * const mention = unstable_useMentionAdapter();
@@ -1 +1 @@
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","create","name","tool","entries","subscribe","onChange","unsubscribeContext","on","unsubscribeSelection","scope","event","isEqual","unstable_useMentionAdapter","options","adapter","directive","includeTools","toolsConfig","undefined","wantsTools","isCategorized","toolMentions","defaultIcon","toolItems","map","toTriggerItem","groups","cat","allGroups","length","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 = Object.create(null) as 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 ||\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":";;;;;;;AAqFA,MAAMsC,sBACJE,OAAOC,OAAO,CAAC,CAAC;AAIlB,MAAMC,oBAEF;CACFC,OAAOL;CACPM,OAAOC,QAAQ;EACb,MAAMC,QAAQD,IAAIE,OAAOC,gBAAgB,CAAC,CAACF;EAC3C,IAAI,CAACA,OAAO,OAAOR;EACnB,MAAMW,WAAWT,OAAOU,OAAO,IAAI;EACnC,KAAK,MAAM,CAACC,MAAMC,SAASZ,OAAOa,QAAQP,KAAK,GAC7CG,SAASE,QAAQC,KAAKjC;EAExB,OAAO8B;CACT;CACAK,YAAYT,KAAKU,aAAa;EAC5B,MAAMC,qBAAqBX,IAAIY,GAAG,6BAA6BF,QAAQ;EAKvE,MAAMG,uBAAuBb,IAAIY,GAC/B;GAAEE,OAAO;GAAKC,OAAO;EAA2B,GAChDL,QACF;EACA,aAAa;GACXC,mBAAmB;GACnBE,qBAAqB;EACvB;CACF;CACAG,SAASnD;AACX;;;;;;;;;;;;;;AAeA,SAAgBoD,2BACdC,SAMA;CACA,MAAMlB,MAAM3C,OAAO;CAEnB,MAAMqB,QAAQwC,SAASxC;CACvB,MAAMM,aAAakC,SAASlC;CAC5B,MAAMqC,eACJH,SAASjC,6BAA6B,CAACP,SAAS,CAACM;CACnD,MAAMsC,cACJ,OAAOD,iBAAiB,WAAWA,eAAeE,KAAAA;CACpD,MAAMC,aAAaH,iBAAiB;CACpC,MAAMnC,YAAYgC,SAAShC;CAC3B,MAAMC,aAAa+B,SAAS/B;CAC5B,MAAMsC,gBACJzC,eAAeuC,KAAAA,KACdD,aAAa1C,aAAa2C,KAAAA,KAAa7C,UAAU6C,KAAAA;CACpD,MAAMG,eAAe5D,wBACnBkC,KACAwB,YACA3B,iBACF;CA2EA,OAAO;EACLsB,SA1EchE,cAAuC;GACrD,MAAM0B,cAAcyC,aAAazC;GACjC,MAAM8C,cAAcL,aAAa/C;GACjC,MAAMqD,YAAYJ,aACd7B,OAAOa,QAAQkB,YAAY,CAAC,CAACG,KAAK,CAACvB,MAAMhC,iBACvCwD,cAAc;IACZ3D,IAAImC;IACJlC,MAAM;IACNC,OAAOQ,cAAcA,YAAYyB,IAAI,IAAIA;IACzChC;IACAC,MAAMoD;GACR,CAAC,CACH,IACA,CAAA;GAGJ,IAAIF,eAAe;IACjB,MAAMM,UAAU/C,cAAc,CAAA,EAAA,CAAI6C,KAAKG,SAAS;KAC9C7D,IAAI6D,IAAI7D;KACRE,OAAO2D,IAAI3D;KACXK,OAAOsD,IAAItD,MAAMmD,IAAIC,aAAa;IACpC,EAAE;IACF,MAAMG,YACJL,UAAUM,SAAS,IACf,CACE,GAAGH,QACH;KACE5D,IAAImD,aAAa1C,UAAUT,MAAM;KACjCE,OAAOiD,aAAa1C,UAAUP,SAAS;KACvCK,OAAOkD;IACT,CAAC,IAEHG;IAEN,OAAO;KACL/C,kBAAkBiD,UAAUJ,KAAK,EAAE1D,IAAIE,aAAa;MAAEF;MAAIE;KAAM,EAAE;KAClE8D,gBAAgBhE,OAAO8D,UAAUG,MAAMC,MAAMA,EAAElE,OAAOA,EAAE,CAAC,EAAEO,SAAS,CAAA;KACpE4D,SAASC,UAAU;MACjB,MAAMC,QAAQD,MAAME,YAAY;MAChC,OAAOR,UACJS,SAASL,MAAMA,EAAE3D,KAAK,CAAC,CACvBiE,QAAQvD,SAASxB,wBAAwBwB,MAAMoD,KAAK,CAAC;KAC1D;IACF;GACF;GAGA,MAAMI,aAAalE,SAAS,CAAA,EAAA,CAAImD,IAAIC,aAAa;GAEjD,MAAMe,OAAO,IAAIC,IAAIF,UAAUf,KAAKkB,MAAMA,EAAE5E,EAAE,CAAC;GAC/C,MAAM6E,WAAW,CACf,GAAGJ,WACH,GAAGhB,UAAUe,QAAQM,MAAM,CAACJ,KAAKK,IAAID,EAAE9E,EAAE,CAAC,CAAC;GAG7C,OAAO;IACLa,kBAAuD,CAAA;IACvDmD,qBAAqB,CAAA;IACrBG,SAASC,UAAU;KACjB,MAAMC,QAAQD,MAAME,YAAY;KAChC,OAAOO,SAASL,QAAQvD,SAASxB,wBAAwBwB,MAAMoD,KAAK,CAAC;IACvE;GACF;EACF,GAAG;GAAC9D;GAAOM;GAAYwC;GAAYF;GAAaG;GAAeC;EAAY,CAWzEP;EACAC,WAVgBjE,eACT;GACL+B,WAAWA,aAAaxB;GACxB,GAAIyB,aAAa,EAAEA,WAAW,IAAI,CAAC;EACrC,IACA,CAACD,WAAWC,UAAU,CAKtBiC;EACA,GAAIF,SAAS7B,UAAU,EAAEA,SAAS6B,QAAQ7B,QAAQ,IAAI,CAAC;EACvD,GAAI6B,SAAS3B,eAAe,EAAEA,cAAc2B,QAAQ3B,aAAa,IAAI,CAAC;CACxE;AACF;AAEA,SAASuC,cAAcqB,GAA2C;CAChE,MAAM3E,WACJ2E,EAAE5E,SAASgD,KAAAA,IAAY;EAAE,GAAI4B,EAAE3E,YAAY,CAAC;EAAID,MAAM4E,EAAE5E;CAAK,IAAI4E,EAAE3E;CACrE,OAAO;EACLL,IAAIgF,EAAEhF;EACNC,MAAM+E,EAAE/E;EACRC,OAAO8E,EAAE9E;EACT,GAAI8E,EAAE7E,gBAAgBiD,KAAAA,IAAY,EAAEjD,aAAa6E,EAAE7E,YAAY,IAAI,CAAC;EACpE,GAAIE,aAAa+C,KAAAA,IAAY,EAAE/C,SAAS,IAAI,CAAC;CAC/C;AACF"}
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","create","name","tool","entries","subscribe","onChange","unsubscribeContext","on","unsubscribeSelection","scope","event","isEqual","unstable_useMentionAdapter","options","adapter","directive","includeTools","toolsConfig","undefined","wantsTools","isCategorized","toolMentions","defaultIcon","toolItems","map","toTriggerItem","groups","cat","allGroups","length","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/**\n * Icon component shape consumed by `ComposerTriggerPopover`'s `iconMap`.\n *\n * @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release.\n */\nexport type Unstable_IconComponent = FC<{ className?: string }>;\n\n/** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */\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\n/** @deprecated Experimental since 2026-03-16. Not scheduled for removal; the API may change in any release. */\nexport type Unstable_MentionCategory = {\n readonly id: string;\n readonly label: string;\n readonly items: readonly Unstable_Mention[];\n};\n\n/** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */\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\n/** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */\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\n/** @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release. */\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 = Object.create(null) as 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 * 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 * @deprecated Experimental since 2026-04-15. Not scheduled for removal; the API may change in any release.\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 ||\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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"}