@assistant-ui/react 0.14.13 → 0.14.15

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 (214) hide show
  1. package/README.md +5 -1
  2. package/dist/client/ExternalThread.d.ts +2 -12
  3. package/dist/client/ExternalThread.d.ts.map +1 -1
  4. package/dist/client/ExternalThread.js +30 -29
  5. package/dist/client/ExternalThread.js.map +1 -1
  6. package/dist/client/InMemoryThreadList.d.ts.map +1 -1
  7. package/dist/client/InMemoryThreadList.js +24 -13
  8. package/dist/client/InMemoryThreadList.js.map +1 -1
  9. package/dist/client/SingleThreadList.d.ts.map +1 -1
  10. package/dist/client/SingleThreadList.js +12 -8
  11. package/dist/client/SingleThreadList.js.map +1 -1
  12. package/dist/context/providers/ThreadViewportProvider.js +1 -1
  13. package/dist/context/providers/ThreadViewportProvider.js.map +1 -1
  14. package/dist/context/react/ThreadViewportContext.js +1 -1
  15. package/dist/context/react/utils/createContextHook.js +1 -1
  16. package/dist/context/react/utils/ensureBinding.js.map +1 -1
  17. package/dist/context/react/utils/useRuntimeState.js +1 -1
  18. package/dist/context/stores/ThreadViewport.js.map +1 -1
  19. package/dist/devtools/DevToolsHooks.js.map +1 -1
  20. package/dist/index.d.ts +4 -4
  21. package/dist/index.js +3 -3
  22. package/dist/legacy-runtime/AssistantRuntimeProvider.js +1 -1
  23. package/dist/legacy-runtime/cloud/auiV0.js +1 -1
  24. package/dist/legacy-runtime/hooks/AssistantContext.js.map +1 -1
  25. package/dist/legacy-runtime/hooks/AttachmentContext.js.map +1 -1
  26. package/dist/legacy-runtime/hooks/ComposerContext.js.map +1 -1
  27. package/dist/legacy-runtime/hooks/MessageContext.js.map +1 -1
  28. package/dist/legacy-runtime/hooks/MessagePartContext.js.map +1 -1
  29. package/dist/legacy-runtime/hooks/ThreadContext.js +1 -1
  30. package/dist/legacy-runtime/hooks/ThreadContext.js.map +1 -1
  31. package/dist/legacy-runtime/hooks/ThreadListItemContext.js.map +1 -1
  32. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js +1 -1
  33. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.d.ts +14 -0
  34. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.d.ts.map +1 -0
  35. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js +101 -0
  36. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js.map +1 -0
  37. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js +1 -1
  38. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
  39. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +13 -2
  40. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
  41. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js +1 -1
  42. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js +1 -1
  43. package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  44. package/dist/mcp-apps/McpAppRenderer.js +7 -7
  45. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  46. package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
  47. package/dist/mcp-apps/McpAppsRemoteHost.js +5 -4
  48. package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
  49. package/dist/mcp-apps/app-frame.d.ts +1 -1
  50. package/dist/mcp-apps/app-frame.d.ts.map +1 -1
  51. package/dist/mcp-apps/app-frame.js +82 -104
  52. package/dist/mcp-apps/app-frame.js.map +1 -1
  53. package/dist/mcp-apps/bridge.d.ts +3 -3
  54. package/dist/mcp-apps/bridge.d.ts.map +1 -1
  55. package/dist/mcp-apps/bridge.js +35 -10
  56. package/dist/mcp-apps/bridge.js.map +1 -1
  57. package/dist/mcp-apps/types.d.ts +2 -12
  58. package/dist/mcp-apps/types.d.ts.map +1 -1
  59. package/dist/mcp-apps/types.js.map +1 -1
  60. package/dist/model-context/frame/useAssistantFrameHost.js +1 -1
  61. package/dist/model-context/makeAssistantVisible.js +1 -1
  62. package/dist/model-context/makeAssistantVisible.js.map +1 -1
  63. package/dist/primitives/actionBar/ActionBarCopy.js +1 -1
  64. package/dist/primitives/actionBar/ActionBarExportMarkdown.js +1 -1
  65. package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
  66. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +1 -1
  67. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +1 -1
  68. package/dist/primitives/actionBar/ActionBarInteractionContext.js +1 -1
  69. package/dist/primitives/actionBar/ActionBarRoot.js +1 -1
  70. package/dist/primitives/actionBar/ActionBarStopSpeaking.js +1 -1
  71. package/dist/primitives/actionBarMore/ActionBarMoreContent.js +1 -1
  72. package/dist/primitives/actionBarMore/ActionBarMoreItem.js +1 -1
  73. package/dist/primitives/actionBarMore/ActionBarMoreRoot.js +1 -1
  74. package/dist/primitives/actionBarMore/ActionBarMoreSeparator.js +1 -1
  75. package/dist/primitives/actionBarMore/ActionBarMoreTrigger.js +1 -1
  76. package/dist/primitives/assistantModal/AssistantModalAnchor.js +1 -1
  77. package/dist/primitives/assistantModal/AssistantModalContent.js +1 -1
  78. package/dist/primitives/assistantModal/AssistantModalRoot.js +1 -1
  79. package/dist/primitives/assistantModal/AssistantModalTrigger.js +1 -1
  80. package/dist/primitives/attachment/AttachmentRemove.js +1 -1
  81. package/dist/primitives/attachment/AttachmentRemove.js.map +1 -1
  82. package/dist/primitives/attachment/AttachmentRoot.js +1 -1
  83. package/dist/primitives/attachment/AttachmentThumb.js +1 -1
  84. package/dist/primitives/branchPicker/BranchPickerRoot.js +1 -1
  85. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +1 -1
  86. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js.map +1 -1
  87. package/dist/primitives/chainOfThought/ChainOfThoughtRoot.js +1 -1
  88. package/dist/primitives/composer/ComposerAddAttachment.js +1 -1
  89. package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
  90. package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -1
  91. package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
  92. package/dist/primitives/composer/ComposerDictationTranscript.js +1 -1
  93. package/dist/primitives/composer/ComposerInput.js +1 -1
  94. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  95. package/dist/primitives/composer/ComposerInputPluginContext.js +1 -1
  96. package/dist/primitives/composer/ComposerQuote.js +1 -1
  97. package/dist/primitives/composer/ComposerQuote.js.map +1 -1
  98. package/dist/primitives/composer/ComposerRoot.js +1 -1
  99. package/dist/primitives/composer/ComposerSend.js +1 -1
  100. package/dist/primitives/composer/ComposerStopDictation.js +1 -1
  101. package/dist/primitives/composer/ComposerStopDictation.js.map +1 -1
  102. package/dist/primitives/composer/trigger/TriggerPopover.js +2 -2
  103. package/dist/primitives/composer/trigger/TriggerPopover.js.map +1 -1
  104. package/dist/primitives/composer/trigger/TriggerPopoverAction.js +1 -1
  105. package/dist/primitives/composer/trigger/TriggerPopoverBack.js +1 -1
  106. package/dist/primitives/composer/trigger/TriggerPopoverCategories.js +1 -1
  107. package/dist/primitives/composer/trigger/TriggerPopoverDirective.js +1 -1
  108. package/dist/primitives/composer/trigger/TriggerPopoverItems.js +1 -1
  109. package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts.map +1 -1
  110. package/dist/primitives/composer/trigger/TriggerPopoverResource.js +8 -7
  111. package/dist/primitives/composer/trigger/TriggerPopoverResource.js.map +1 -1
  112. package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js +1 -1
  113. package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts.map +1 -1
  114. package/dist/primitives/composer/trigger/triggerDetectionResource.js +5 -4
  115. package/dist/primitives/composer/trigger/triggerDetectionResource.js.map +1 -1
  116. package/dist/primitives/composer/trigger/triggerKeyboardResource.d.ts.map +1 -1
  117. package/dist/primitives/composer/trigger/triggerKeyboardResource.js +8 -7
  118. package/dist/primitives/composer/trigger/triggerKeyboardResource.js.map +1 -1
  119. package/dist/primitives/composer/trigger/triggerNavigationResource.d.ts.map +1 -1
  120. package/dist/primitives/composer/trigger/triggerNavigationResource.js +13 -12
  121. package/dist/primitives/composer/trigger/triggerNavigationResource.js.map +1 -1
  122. package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
  123. package/dist/primitives/composer/trigger/triggerSelectionResource.js +7 -6
  124. package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
  125. package/dist/primitives/error/ErrorMessage.js +1 -1
  126. package/dist/primitives/error/ErrorRoot.js +1 -1
  127. package/dist/primitives/message/MessagePartsGrouped.js +1 -1
  128. package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
  129. package/dist/primitives/message/MessageRoot.js +1 -1
  130. package/dist/primitives/message/MessageRoot.js.map +1 -1
  131. package/dist/primitives/messagePart/MessagePartImage.js +1 -1
  132. package/dist/primitives/messagePart/MessagePartText.js +1 -1
  133. package/dist/primitives/queueItem/QueueItemRemove.js +1 -1
  134. package/dist/primitives/queueItem/QueueItemRemove.js.map +1 -1
  135. package/dist/primitives/queueItem/QueueItemSteer.js +1 -1
  136. package/dist/primitives/queueItem/QueueItemSteer.js.map +1 -1
  137. package/dist/primitives/queueItem/QueueItemText.js +1 -1
  138. package/dist/primitives/reasoning/useScrollLock.js +1 -1
  139. package/dist/primitives/reasoning/useScrollLock.js.map +1 -1
  140. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +1 -1
  141. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js.map +1 -1
  142. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +1 -1
  143. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
  144. package/dist/primitives/suggestion/SuggestionDescription.js +1 -1
  145. package/dist/primitives/suggestion/SuggestionTitle.js +1 -1
  146. package/dist/primitives/suggestion/SuggestionTrigger.js +1 -1
  147. package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -1
  148. package/dist/primitives/thread/ThreadRoot.js +1 -1
  149. package/dist/primitives/thread/ThreadScrollToBottom.js +1 -1
  150. package/dist/primitives/thread/ThreadScrollToBottom.js.map +1 -1
  151. package/dist/primitives/thread/ThreadViewport.js +1 -1
  152. package/dist/primitives/thread/ThreadViewport.js.map +1 -1
  153. package/dist/primitives/thread/ThreadViewportFooter.js +1 -1
  154. package/dist/primitives/thread/ThreadViewportFooter.js.map +1 -1
  155. package/dist/primitives/thread/topAnchor/topAnchorTurn.js.map +1 -1
  156. package/dist/primitives/thread/topAnchor/topAnchorUtils.js.map +1 -1
  157. package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js +1 -1
  158. package/dist/primitives/thread/useThreadViewportAutoScroll.js +1 -1
  159. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  160. package/dist/primitives/threadList/ThreadListNew.js +1 -1
  161. package/dist/primitives/threadList/ThreadListRoot.js +1 -1
  162. package/dist/primitives/threadListItem/ThreadListItemRoot.js +1 -1
  163. package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js +1 -1
  164. package/dist/primitives/threadListItemMore/ThreadListItemMoreItem.js +1 -1
  165. package/dist/primitives/threadListItemMore/ThreadListItemMoreSeparator.js +1 -1
  166. package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js +1 -1
  167. package/dist/sandbox-host/SandboxHost.d.ts +50 -0
  168. package/dist/sandbox-host/SandboxHost.d.ts.map +1 -0
  169. package/dist/sandbox-host/SandboxHost.js +85 -0
  170. package/dist/sandbox-host/SandboxHost.js.map +1 -0
  171. package/dist/unstable/useMentionAdapter.d.ts +2 -2
  172. package/dist/unstable/useMentionAdapter.js +2 -2
  173. package/dist/unstable/useMentionAdapter.js.map +1 -1
  174. package/dist/unstable/useSlashCommandAdapter.js +1 -1
  175. package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
  176. package/dist/utils/Primitive.js +1 -1
  177. package/dist/utils/createActionButton.js +1 -1
  178. package/dist/utils/createActionButton.js.map +1 -1
  179. package/dist/utils/hooks/useManagedRef.js +1 -1
  180. package/dist/utils/hooks/useMediaQuery.js +1 -1
  181. package/dist/utils/hooks/useMediaQuery.js.map +1 -1
  182. package/dist/utils/hooks/useOnResizeContent.js +1 -1
  183. package/dist/utils/hooks/useOnScrollToBottom.js +1 -1
  184. package/dist/utils/hooks/useSizeHandle.js +1 -1
  185. package/dist/utils/json/is-json.js.map +1 -1
  186. package/dist/utils/smooth/SmoothContext.js +1 -1
  187. package/dist/utils/smooth/SmoothContext.js.map +1 -1
  188. package/dist/utils/smooth/useSmooth.js +1 -1
  189. package/dist/utils/smooth/useSmooth.js.map +1 -1
  190. package/package.json +21 -20
  191. package/src/client/ExternalThread.ts +484 -515
  192. package/src/client/InMemoryThreadList.ts +154 -142
  193. package/src/client/SingleThreadList.ts +88 -81
  194. package/src/context/providers/ThreadViewportProvider.tsx +2 -2
  195. package/src/index.ts +18 -3
  196. package/src/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.test.ts +426 -0
  197. package/src/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.ts +146 -0
  198. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +16 -1
  199. package/src/mcp-apps/McpAppRenderer.tsx +28 -35
  200. package/src/mcp-apps/McpAppsRemoteHost.ts +25 -24
  201. package/src/mcp-apps/app-frame.tsx +100 -141
  202. package/src/mcp-apps/bridge.test.ts +100 -60
  203. package/src/mcp-apps/bridge.ts +43 -21
  204. package/src/mcp-apps/types.ts +2 -12
  205. package/src/primitives/composer/trigger/TriggerPopover.tsx +1 -1
  206. package/src/primitives/composer/trigger/TriggerPopoverResource.ts +75 -76
  207. package/src/primitives/composer/trigger/triggerDetectionResource.ts +6 -5
  208. package/src/primitives/composer/trigger/triggerKeyboardResource.ts +9 -13
  209. package/src/primitives/composer/trigger/triggerNavigationResource.ts +14 -19
  210. package/src/primitives/composer/trigger/triggerSelectionResource.ts +8 -7
  211. package/src/sandbox-host/SandboxHost.test.tsx +231 -0
  212. package/src/sandbox-host/SandboxHost.tsx +185 -0
  213. package/src/tests/local-runtime-queue.test.tsx +305 -0
  214. package/src/unstable/useMentionAdapter.ts +2 -2
@@ -14,13 +14,8 @@ import type {
14
14
  ToolCallMessagePartProps,
15
15
  } from "@assistant-ui/core/react";
16
16
  import { useAui } from "@assistant-ui/store";
17
- import {
18
- resource,
19
- tapConst,
20
- tapRef,
21
- tapResource,
22
- type ResourceElement,
23
- } from "@assistant-ui/tap";
17
+
18
+ import { useResource, resource, type ResourceElement } from "@assistant-ui/tap";
24
19
  import { McpAppFrame } from "./app-frame";
25
20
  import type {
26
21
  McpAppBridgeHandlers,
@@ -136,7 +131,7 @@ function InlineRenderer({
136
131
  return () => {
137
132
  cancelled = true;
138
133
  };
139
- // oxlint-disable-next-line tap-hooks/exhaustive-deps -- re-fetch only when URI changes; appForRender identity is unstable and internalsRef is a stable ref
134
+ // oxlint-disable-next-line react/exhaustive-deps -- re-fetch only when URI changes; appForRender identity is unstable and internalsRef is a stable ref
140
135
  }, [resourceUri]);
141
136
 
142
137
  const bridgeHandlers = useMemo<McpAppBridgeHandlers>(
@@ -200,30 +195,28 @@ function InlineRenderer({
200
195
  * renderer loads that resource from the configured host and displays it in a
201
196
  * sandboxed frame.
202
197
  */
203
- export const McpAppRenderer = resource(
204
- (
205
- options: McpAppRendererOptions,
206
- ): { readonly render: ToolCallMessagePartComponent } => {
207
- const host = tapResource(options.host);
208
-
209
- const optionsRef = tapRef<McpAppRendererOptions>(options);
210
- optionsRef.current = options;
211
-
212
- const internalsRef = tapRef<{ host: McpAppsHost }>({ host });
213
- internalsRef.current = { host };
214
-
215
- const render = tapConst((): ToolCallMessagePartComponent => {
216
- const Render: ToolCallMessagePartComponent = (props) => (
217
- <InlineRenderer
218
- part={props}
219
- internalsRef={internalsRef}
220
- optionsRef={optionsRef}
221
- />
222
- );
223
- Render.displayName = "McpAppRenderer";
224
- return Render;
225
- }, []);
226
-
227
- return { render };
228
- },
229
- );
198
+ export const McpAppRenderer = resource(function McpAppRenderer(
199
+ options: McpAppRendererOptions,
200
+ ): { readonly render: ToolCallMessagePartComponent } {
201
+ const host = useResource(options.host);
202
+
203
+ const optionsRef = useRef<McpAppRendererOptions>(options);
204
+ optionsRef.current = options;
205
+
206
+ const internalsRef = useRef<{ host: McpAppsHost }>({ host });
207
+ internalsRef.current = { host };
208
+
209
+ const render = useMemo((): ToolCallMessagePartComponent => {
210
+ const Render: ToolCallMessagePartComponent = (props) => (
211
+ <InlineRenderer
212
+ part={props}
213
+ internalsRef={internalsRef}
214
+ optionsRef={optionsRef}
215
+ />
216
+ );
217
+ Render.displayName = "McpAppRenderer";
218
+ return Render;
219
+ }, []);
220
+
221
+ return { render };
222
+ });
@@ -1,4 +1,5 @@
1
- import { resource, tapConst, tapRef } from "@assistant-ui/tap";
1
+ import { useMemo, useRef } from "react";
2
+ import { resource } from "@assistant-ui/tap";
2
3
  import type {
3
4
  McpAppResource,
4
5
  McpAppsHost,
@@ -33,27 +34,27 @@ async function postToHost(
33
34
  * params }`, using the method names expected by the assistant-ui MCP Apps
34
35
  * guide.
35
36
  */
36
- export const McpAppsRemoteHost = resource(
37
- (options: McpAppsRemoteHostOptions): McpAppsHost => {
38
- const optionsRef = tapRef(options);
39
- optionsRef.current = options;
37
+ export const McpAppsRemoteHost = resource(function McpAppsRemoteHost(
38
+ options: McpAppsRemoteHostOptions,
39
+ ): McpAppsHost {
40
+ const optionsRef = useRef(options);
41
+ optionsRef.current = options;
40
42
 
41
- return tapConst(
42
- (): McpAppsHost => ({
43
- loadResource: (params) =>
44
- postToHost(
45
- optionsRef.current,
46
- "mcp-apps/read-resource",
47
- params,
48
- ) as Promise<McpAppResource>,
49
- callTool: (params) =>
50
- postToHost(optionsRef.current, "tools/call", params),
51
- readResource: (params) =>
52
- postToHost(optionsRef.current, "resources/read", params),
53
- listResources: (params) =>
54
- postToHost(optionsRef.current, "resources/list", params),
55
- }),
56
- [],
57
- );
58
- },
59
- );
43
+ return useMemo(
44
+ (): McpAppsHost => ({
45
+ loadResource: (params) =>
46
+ postToHost(
47
+ optionsRef.current,
48
+ "mcp-apps/read-resource",
49
+ params,
50
+ ) as Promise<McpAppResource>,
51
+ callTool: (params) =>
52
+ postToHost(optionsRef.current, "tools/call", params),
53
+ readResource: (params) =>
54
+ postToHost(optionsRef.current, "resources/read", params),
55
+ listResources: (params) =>
56
+ postToHost(optionsRef.current, "resources/list", params),
57
+ }),
58
+ [],
59
+ );
60
+ });
@@ -1,8 +1,13 @@
1
1
  "use client";
2
2
 
3
- import { type MutableRefObject, useEffect, useRef, useState } from "react";
4
- import { type RenderedFrame, SafeContentFrame } from "safe-content-frame";
3
+ import { type MutableRefObject, useEffect, useRef } from "react";
5
4
  import { type McpAppBridge, createMcpAppBridge } from "./bridge";
5
+ import {
6
+ SandboxHost,
7
+ type SandboxBridge,
8
+ type SandboxHostApi,
9
+ type SandboxHostFrame,
10
+ } from "../sandbox-host/SandboxHost";
6
11
  import type {
7
12
  McpAppBridgeHandlers,
8
13
  McpAppFrameProps,
@@ -31,7 +36,7 @@ function useBridgeNotify<T>(
31
36
  }
32
37
  notify(bridgeRef.current, value);
33
38
  lastSentRef.current = value;
34
- // oxlint-disable-next-line tap-hooks/exhaustive-deps -- refs are stable; notify is assumed stable; re-run only when value changes
39
+ // oxlint-disable-next-line react/exhaustive-deps -- refs are stable; notify is assumed stable; re-run only when value changes
35
40
  }, [value]);
36
41
  }
37
42
 
@@ -100,10 +105,6 @@ export function McpAppFrame({
100
105
  hostContext,
101
106
  maxHeight = DEFAULT_MAX_HEIGHT,
102
107
  }: McpAppFrameProps) {
103
- const containerRef = useRef<HTMLDivElement>(null);
104
- const [contentHeight, setContentHeight] = useState<number | undefined>(
105
- undefined,
106
- );
107
108
  const bridgeRef = useRef<McpAppBridge | null>(null);
108
109
  const lastSentInputRef = useRef<unknown>(undefined);
109
110
  const lastSentOutputRef = useRef<unknown>(undefined);
@@ -129,133 +130,93 @@ export function McpAppFrame({
129
130
  output,
130
131
  };
131
132
 
132
- const resourceUri = resource.uri;
133
+ const createBridge = (
134
+ frame: SandboxHostFrame,
135
+ host: SandboxHostApi,
136
+ ): SandboxBridge => {
137
+ const current = liveRef.current;
138
+ let initTimeoutId: ReturnType<typeof setTimeout> | null = null;
133
139
 
134
- useEffect(() => {
135
- const container = containerRef.current;
136
- if (!container) return;
140
+ const flushPending = () => {
141
+ if (widgetReadyRef.current) return;
142
+ widgetReadyRef.current = true;
143
+ const b = bridgeRef.current;
144
+ if (!b) return;
145
+ if (pendingInputRef.current !== undefined) {
146
+ b.notifyToolInput(pendingInputRef.current);
147
+ lastSentInputRef.current = pendingInputRef.current;
148
+ pendingInputRef.current = undefined;
149
+ }
150
+ if (pendingOutputRef.current !== undefined) {
151
+ b.notifyToolResult(pendingOutputRef.current);
152
+ lastSentOutputRef.current = pendingOutputRef.current;
153
+ pendingOutputRef.current = undefined;
154
+ }
155
+ if (pendingHostContextRef.current !== undefined) {
156
+ b.notifyHostContextChanged(pendingHostContextRef.current);
157
+ lastSentHostContextRef.current = pendingHostContextRef.current;
158
+ pendingHostContextRef.current = undefined;
159
+ }
160
+ };
137
161
 
138
- let cancelled = false;
139
- let initTimeoutId: ReturnType<typeof setTimeout> | null = null;
140
- let frame: RenderedFrame | null = null;
141
- const sb = sandbox;
142
- const html = resource.html;
162
+ const liveHandlers = buildLiveHandlers(current.handlers, liveRef);
163
+ const liveOnInitialized = liveHandlers.onInitialized;
164
+ const wrappedHandlers: McpAppBridgeHandlers = {
165
+ ...liveHandlers,
166
+ onInitialized: () => {
167
+ if (initTimeoutId !== null) {
168
+ clearTimeout(initTimeoutId);
169
+ initTimeoutId = null;
170
+ }
171
+ flushPending();
172
+ liveOnInitialized?.();
173
+ },
174
+ onSizeChange: (p) => {
175
+ if (p.height != null) host.setHeight(p.height);
176
+ liveHandlers.onSizeChange?.(p);
177
+ },
178
+ };
179
+
180
+ // Safety net: if the widget never sends notifications/initialized (broken
181
+ // or non-spec-compliant), flush the queue anyway so the host doesn't
182
+ // appear hung.
183
+ initTimeoutId = setTimeout(() => {
184
+ initTimeoutId = null;
185
+ flushPending();
186
+ }, INIT_TIMEOUT_MS);
143
187
 
144
- const scf = new SafeContentFrame(sb?.product ?? DEFAULT_PRODUCT, {
145
- ...(sb?.sandbox !== undefined && { sandbox: sb.sandbox }),
146
- ...(sb?.useShadowDom !== undefined && { useShadowDom: sb.useShadowDom }),
147
- ...(sb?.enableBrowserCaching !== undefined && {
148
- enableBrowserCaching: sb.enableBrowserCaching,
149
- }),
150
- ...(sb?.salt !== undefined && { salt: sb.salt }),
188
+ const bridge = createMcpAppBridge({
189
+ frame,
190
+ handlers: wrappedHandlers,
191
+ hostInfo: current.hostInfo,
192
+ hostContext: current.hostContext,
151
193
  });
194
+ bridgeRef.current = bridge;
152
195
 
153
- const renderOpts =
154
- sb?.unsafeDocumentWrite !== undefined
155
- ? { unsafeDocumentWrite: sb.unsafeDocumentWrite }
156
- : undefined;
196
+ if (current.input !== undefined) pendingInputRef.current = current.input;
197
+ if (current.output !== undefined) pendingOutputRef.current = current.output;
198
+ // hostContext is delivered inside the ui/initialize response; subsequent
199
+ // changes flow through useBridgeNotify's pending path.
157
200
 
158
- scf
159
- .renderHtml(html, container, renderOpts)
160
- .then((rendered) => {
161
- if (cancelled) {
162
- rendered.dispose();
163
- return;
164
- }
165
- frame = rendered;
166
- const current = liveRef.current;
167
- const liveHandlers = buildLiveHandlers(current.handlers, liveRef);
168
- const liveOnInitialized = liveHandlers.onInitialized;
169
- const flushPending = () => {
170
- if (widgetReadyRef.current) return;
171
- widgetReadyRef.current = true;
172
- const b = bridgeRef.current;
173
- if (!b) return;
174
- if (pendingInputRef.current !== undefined) {
175
- b.notifyToolInput(pendingInputRef.current);
176
- lastSentInputRef.current = pendingInputRef.current;
177
- pendingInputRef.current = undefined;
178
- }
179
- if (pendingOutputRef.current !== undefined) {
180
- b.notifyToolResult(pendingOutputRef.current);
181
- lastSentOutputRef.current = pendingOutputRef.current;
182
- pendingOutputRef.current = undefined;
183
- }
184
- if (pendingHostContextRef.current !== undefined) {
185
- b.notifyHostContextChanged(pendingHostContextRef.current);
186
- lastSentHostContextRef.current = pendingHostContextRef.current;
187
- pendingHostContextRef.current = undefined;
188
- }
189
- };
190
- const wrappedHandlers: McpAppBridgeHandlers = {
191
- ...liveHandlers,
192
- onInitialized: () => {
193
- if (initTimeoutId !== null) {
194
- clearTimeout(initTimeoutId);
195
- initTimeoutId = null;
196
- }
197
- flushPending();
198
- liveOnInitialized?.();
199
- },
200
- onSizeChange: (p) => {
201
- if (
202
- typeof p.height === "number" &&
203
- Number.isFinite(p.height) &&
204
- p.height > 0
205
- ) {
206
- setContentHeight(p.height);
207
- }
208
- liveHandlers.onSizeChange?.(p);
209
- },
210
- };
211
- // Safety net: if the widget never sends notifications/initialized
212
- // (broken or non-spec-compliant), flush the queue anyway so the host
213
- // doesn't appear hung.
214
- initTimeoutId = setTimeout(() => {
201
+ return {
202
+ onMessage: bridge.onMessage,
203
+ dispose: () => {
204
+ if (initTimeoutId !== null) {
205
+ clearTimeout(initTimeoutId);
215
206
  initTimeoutId = null;
216
- flushPending();
217
- }, INIT_TIMEOUT_MS);
218
- bridgeRef.current = createMcpAppBridge({
219
- frame: rendered,
220
- handlers: wrappedHandlers,
221
- hostInfo: current.hostInfo,
222
- hostContext: current.hostContext,
223
- });
224
-
225
- if (current.input !== undefined)
226
- pendingInputRef.current = current.input;
227
- if (current.output !== undefined)
228
- pendingOutputRef.current = current.output;
229
- // hostContext is delivered inside the ui/initialize response; subsequent
230
- // changes flow through useBridgeNotify's pending path.
231
- })
232
- .catch((err) => {
233
- liveRef.current.handlers?.onError?.(
234
- err instanceof Error ? err : new Error(String(err)),
235
- );
236
- });
237
-
238
- return () => {
239
- cancelled = true;
240
- if (initTimeoutId !== null) {
241
- clearTimeout(initTimeoutId);
242
- initTimeoutId = null;
243
- }
244
- bridgeRef.current?.dispose();
245
- bridgeRef.current = null;
246
- frame?.dispose();
247
- frame = null;
248
- lastSentInputRef.current = undefined;
249
- lastSentOutputRef.current = undefined;
250
- lastSentHostContextRef.current = undefined;
251
- widgetReadyRef.current = false;
252
- pendingInputRef.current = undefined;
253
- pendingOutputRef.current = undefined;
254
- pendingHostContextRef.current = undefined;
255
- setContentHeight(undefined);
207
+ }
208
+ bridge.dispose();
209
+ bridgeRef.current = null;
210
+ lastSentInputRef.current = undefined;
211
+ lastSentOutputRef.current = undefined;
212
+ lastSentHostContextRef.current = undefined;
213
+ widgetReadyRef.current = false;
214
+ pendingInputRef.current = undefined;
215
+ pendingOutputRef.current = undefined;
216
+ pendingHostContextRef.current = undefined;
217
+ },
256
218
  };
257
- // oxlint-disable-next-line tap-hooks/exhaustive-deps -- re-mount only on resource URI change; live values flow through liveRef
258
- }, [resourceUri]);
219
+ };
259
220
 
260
221
  useBridgeNotify(
261
222
  input,
@@ -282,22 +243,20 @@ export function McpAppFrame({
282
243
  (b, v) => b.notifyHostContextChanged(v),
283
244
  );
284
245
 
285
- const resolvedHeight =
286
- contentHeight != null ? Math.min(contentHeight, maxHeight) : undefined;
287
- const mergedStyle =
288
- resolvedHeight != null
289
- ? { ...sandbox?.style, height: resolvedHeight }
290
- : sandbox?.style;
291
-
292
246
  return (
293
- <div
294
- ref={containerRef}
295
- className={sandbox?.className}
296
- style={mergedStyle}
297
- data-mcp-app-resource={app.resourceUri}
298
- data-mcp-app-prefers-border={
299
- resource.meta?.prefersBorder ? "" : undefined
300
- }
247
+ <SandboxHost
248
+ content={{ html: resource.html }}
249
+ contentKey={resource.uri}
250
+ sandbox={{ ...sandbox, product: sandbox?.product ?? DEFAULT_PRODUCT }}
251
+ maxHeight={maxHeight}
252
+ createBridge={createBridge}
253
+ onError={(err) => liveRef.current.handlers?.onError?.(err)}
254
+ containerProps={{
255
+ "data-mcp-app-resource": app.resourceUri,
256
+ "data-mcp-app-prefers-border": resource.meta?.prefersBorder
257
+ ? ""
258
+ : undefined,
259
+ }}
301
260
  />
302
261
  );
303
262
  }