@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
@@ -1,6 +1,10 @@
1
1
  // @vitest-environment jsdom
2
2
  import { describe, expect, it, vi } from "vitest";
3
- import { createMcpAppBridge, type McpAppBridgeFrame } from "./bridge";
3
+ import {
4
+ createMcpAppBridge,
5
+ type McpAppBridge,
6
+ type McpAppBridgeFrame,
7
+ } from "./bridge";
4
8
  import type {
5
9
  McpAppJsonRpcMessage,
6
10
  McpAppJsonRpcRequest,
@@ -24,13 +28,8 @@ function makeFrame() {
24
28
  return { frame, captured };
25
29
  }
26
30
 
27
- function dispatch(frame: McpAppBridgeFrame, message: McpAppJsonRpcMessage) {
28
- const event = new MessageEvent("message", {
29
- data: message,
30
- origin: frame.origin,
31
- source: frame.iframe.contentWindow,
32
- });
33
- window.dispatchEvent(event);
31
+ function deliver(bridge: McpAppBridge, message: McpAppJsonRpcMessage) {
32
+ bridge.onMessage(new MessageEvent("message", { data: message }));
34
33
  }
35
34
 
36
35
  async function flush() {
@@ -55,7 +54,7 @@ describe("createMcpAppBridge", () => {
55
54
  id: 1,
56
55
  method: "ui/initialize",
57
56
  };
58
- dispatch(frame, req);
57
+ deliver(bridge, req);
59
58
  await flush();
60
59
 
61
60
  expect(captured).toHaveLength(1);
@@ -69,6 +68,34 @@ describe("createMcpAppBridge", () => {
69
68
  expect(result["capabilities"]["ui"]["sendMessage"]).toBe(true);
70
69
  expect(result["capabilities"]["ui"]["openLink"]).toBe(false);
71
70
 
71
+ expect(result["hostInfo"]).toEqual({ name: "test-host", version: "9.9.9" });
72
+ expect(result["hostCapabilities"]).toEqual({
73
+ serverTools: {},
74
+ message: { text: {} },
75
+ });
76
+
77
+ bridge.dispose();
78
+ });
79
+
80
+ it("echoes the requested protocolVersion in the ui/initialize result", async () => {
81
+ const { frame, captured } = makeFrame();
82
+ const bridge = createMcpAppBridge({ frame });
83
+
84
+ deliver(bridge, {
85
+ jsonrpc: "2.0",
86
+ id: 1,
87
+ method: "ui/initialize",
88
+ params: { protocolVersion: "2026-01-26" },
89
+ });
90
+ await flush();
91
+
92
+ const result = (captured[0] as McpAppJsonRpcResponse).result as Record<
93
+ string,
94
+ any
95
+ >;
96
+ expect(result["protocolVersion"]).toBe("2026-01-26");
97
+ expect(result["hostCapabilities"]).toEqual({});
98
+
72
99
  bridge.dispose();
73
100
  });
74
101
 
@@ -77,7 +104,7 @@ describe("createMcpAppBridge", () => {
77
104
  const callTool = vi.fn().mockResolvedValue({ ok: true });
78
105
  const bridge = createMcpAppBridge({ frame, handlers: { callTool } });
79
106
 
80
- dispatch(frame, {
107
+ deliver(bridge, {
81
108
  jsonrpc: "2.0",
82
109
  id: 7,
83
110
  method: "tools/call",
@@ -105,7 +132,7 @@ describe("createMcpAppBridge", () => {
105
132
  handlers: { callTool, allowedTools: ["search"] },
106
133
  });
107
134
 
108
- dispatch(frame, {
135
+ deliver(bridge, {
109
136
  jsonrpc: "2.0",
110
137
  id: 2,
111
138
  method: "tools/call",
@@ -123,7 +150,7 @@ describe("createMcpAppBridge", () => {
123
150
  const { frame, captured } = makeFrame();
124
151
  const bridge = createMcpAppBridge({ frame });
125
152
 
126
- dispatch(frame, {
153
+ deliver(bridge, {
127
154
  jsonrpc: "2.0",
128
155
  id: 3,
129
156
  method: "tools/call",
@@ -141,7 +168,7 @@ describe("createMcpAppBridge", () => {
141
168
  const callTool = vi.fn();
142
169
  const bridge = createMcpAppBridge({ frame, handlers: { callTool } });
143
170
 
144
- dispatch(frame, {
171
+ deliver(bridge, {
145
172
  jsonrpc: "2.0",
146
173
  id: 11,
147
174
  method: "tools/call",
@@ -162,7 +189,7 @@ describe("createMcpAppBridge", () => {
162
189
  handlers: { requestDisplayMode },
163
190
  });
164
191
 
165
- dispatch(frame, {
192
+ deliver(bridge, {
166
193
  jsonrpc: "2.0",
167
194
  id: 13,
168
195
  method: "requestDisplayMode",
@@ -180,7 +207,7 @@ describe("createMcpAppBridge", () => {
180
207
  const openLink = vi.fn();
181
208
  const bridge = createMcpAppBridge({ frame, handlers: { openLink } });
182
209
 
183
- dispatch(frame, {
210
+ deliver(bridge, {
184
211
  jsonrpc: "2.0",
185
212
  id: 12,
186
213
  method: "openLink",
@@ -202,12 +229,12 @@ describe("createMcpAppBridge", () => {
202
229
  handlers: { onSizeChange, onInitialized },
203
230
  });
204
231
 
205
- dispatch(frame, {
232
+ deliver(bridge, {
206
233
  jsonrpc: "2.0",
207
234
  method: "notifications/size_changed",
208
235
  params: { width: 320, height: 240 },
209
236
  });
210
- dispatch(frame, {
237
+ deliver(bridge, {
211
238
  jsonrpc: "2.0",
212
239
  method: "notifications/initialized",
213
240
  });
@@ -217,7 +244,7 @@ describe("createMcpAppBridge", () => {
217
244
  bridge.dispose();
218
245
  });
219
246
 
220
- it("notifyToolInput / notifyToolResult / notifyHostContextChanged post correct notifications", () => {
247
+ it("notifyToolInput / notifyToolResult / notifyHostContextChanged post both legacy and 2026-01-26 notifications", () => {
221
248
  const { frame, captured } = makeFrame();
222
249
  const bridge = createMcpAppBridge({ frame });
223
250
 
@@ -231,16 +258,62 @@ describe("createMcpAppBridge", () => {
231
258
  method: "notifications/tools/call/input",
232
259
  params: { input: { a: 1 } },
233
260
  },
261
+ {
262
+ jsonrpc: "2.0",
263
+ method: "ui/notifications/tool-input",
264
+ params: { arguments: { a: 1 } },
265
+ },
234
266
  {
235
267
  jsonrpc: "2.0",
236
268
  method: "notifications/tools/call/result",
237
269
  params: { result: { ok: 1 } },
238
270
  },
271
+ {
272
+ jsonrpc: "2.0",
273
+ method: "ui/notifications/tool-result",
274
+ params: { ok: 1 },
275
+ },
239
276
  {
240
277
  jsonrpc: "2.0",
241
278
  method: "notifications/host_context/changed",
242
279
  params: { theme: "light" },
243
280
  },
281
+ {
282
+ jsonrpc: "2.0",
283
+ method: "ui/notifications/host-context-changed",
284
+ params: { theme: "light" },
285
+ },
286
+ ]);
287
+ bridge.dispose();
288
+ });
289
+
290
+ it("wraps non-object and array tool results in a valid content block for the spec dialect", () => {
291
+ const { frame, captured } = makeFrame();
292
+ const bridge = createMcpAppBridge({ frame });
293
+
294
+ bridge.notifyToolResult("done");
295
+ bridge.notifyToolResult([1, 2]);
296
+ bridge.notifyToolInput(null);
297
+
298
+ const spec = captured.filter((c) =>
299
+ (c as { method?: string }).method?.startsWith("ui/notifications/"),
300
+ );
301
+ expect(spec).toEqual([
302
+ {
303
+ jsonrpc: "2.0",
304
+ method: "ui/notifications/tool-result",
305
+ params: { content: [{ type: "text", text: "done" }] },
306
+ },
307
+ {
308
+ jsonrpc: "2.0",
309
+ method: "ui/notifications/tool-result",
310
+ params: { content: [{ type: "text", text: "1,2" }] },
311
+ },
312
+ {
313
+ jsonrpc: "2.0",
314
+ method: "ui/notifications/tool-input",
315
+ params: {},
316
+ },
244
317
  ]);
245
318
  bridge.dispose();
246
319
  });
@@ -254,13 +327,13 @@ describe("createMcpAppBridge", () => {
254
327
  handlers: { readResource, listResources },
255
328
  });
256
329
 
257
- dispatch(frame, {
330
+ deliver(bridge, {
258
331
  jsonrpc: "2.0",
259
332
  id: 20,
260
333
  method: "resources/read",
261
334
  params: { uri: "ui://app/x" },
262
335
  });
263
- dispatch(frame, {
336
+ deliver(bridge, {
264
337
  jsonrpc: "2.0",
265
338
  id: 21,
266
339
  method: "resources/list",
@@ -279,13 +352,13 @@ describe("createMcpAppBridge", () => {
279
352
  const { frame, captured } = makeFrame();
280
353
  const bridge = createMcpAppBridge({ frame });
281
354
 
282
- dispatch(frame, {
355
+ deliver(bridge, {
283
356
  jsonrpc: "2.0",
284
357
  id: 22,
285
358
  method: "resources/read",
286
359
  params: { uri: "ui://x" },
287
360
  });
288
- dispatch(frame, { jsonrpc: "2.0", id: 23, method: "resources/list" });
361
+ deliver(bridge, { jsonrpc: "2.0", id: 23, method: "resources/list" });
289
362
  await flush();
290
363
 
291
364
  expect((captured[0] as McpAppJsonRpcResponse).error?.code).toBe(-32601);
@@ -302,13 +375,13 @@ describe("createMcpAppBridge", () => {
302
375
  handlers: { sendMessage, updateModelContext },
303
376
  });
304
377
 
305
- dispatch(frame, {
378
+ deliver(bridge, {
306
379
  jsonrpc: "2.0",
307
380
  id: 30,
308
381
  method: "sendMessage",
309
382
  params: { text: "hi" },
310
383
  });
311
- dispatch(frame, {
384
+ deliver(bridge, {
312
385
  jsonrpc: "2.0",
313
386
  id: 31,
314
387
  method: "updateModelContext",
@@ -334,17 +407,17 @@ describe("createMcpAppBridge", () => {
334
407
  handlers: { onLog, onError, onRequestTeardown },
335
408
  });
336
409
 
337
- dispatch(frame, {
410
+ deliver(bridge, {
338
411
  jsonrpc: "2.0",
339
412
  method: "notifications/log",
340
413
  params: { level: "info", message: "hello" },
341
414
  });
342
- dispatch(frame, {
415
+ deliver(bridge, {
343
416
  jsonrpc: "2.0",
344
417
  method: "notifications/error",
345
418
  params: { message: "kaboom" },
346
419
  });
347
- dispatch(frame, {
420
+ deliver(bridge, {
348
421
  jsonrpc: "2.0",
349
422
  method: "notifications/request_teardown",
350
423
  params: { reason: "done" },
@@ -355,37 +428,4 @@ describe("createMcpAppBridge", () => {
355
428
  expect(onRequestTeardown).toHaveBeenCalledWith({ reason: "done" });
356
429
  bridge.dispose();
357
430
  });
358
-
359
- it("ignores messages from wrong origin or wrong source", async () => {
360
- const { frame, captured } = makeFrame();
361
- const callTool = vi.fn();
362
- const bridge = createMcpAppBridge({ frame, handlers: { callTool } });
363
-
364
- const msg: McpAppJsonRpcMessage = {
365
- jsonrpc: "2.0",
366
- id: 1,
367
- method: "tools/call",
368
- params: { name: "search" },
369
- };
370
-
371
- window.dispatchEvent(
372
- new MessageEvent("message", {
373
- data: msg,
374
- origin: "https://attacker.example",
375
- source: frame.iframe.contentWindow,
376
- }),
377
- );
378
- window.dispatchEvent(
379
- new MessageEvent("message", {
380
- data: msg,
381
- origin: frame.origin,
382
- source: window,
383
- }),
384
- );
385
- await flush();
386
-
387
- expect(callTool).not.toHaveBeenCalled();
388
- expect(captured).toHaveLength(0);
389
- bridge.dispose();
390
- });
391
431
  });
@@ -1,4 +1,4 @@
1
- import type { RenderedFrame } from "safe-content-frame";
1
+ import type { SandboxHostFrame } from "../sandbox-host/SandboxHost";
2
2
  import {
3
3
  MCP_APP_PROTOCOL_VERSION,
4
4
  type McpAppBridgeHandlers,
@@ -10,6 +10,7 @@ import {
10
10
  type McpAppJsonRpcRequest,
11
11
  type McpAppJsonRpcResponse,
12
12
  } from "./types";
13
+ import { isRecord } from "../utils/json/is-json";
13
14
 
14
15
  const VALID_DISPLAY_MODES = [
15
16
  "inline",
@@ -17,20 +18,17 @@ const VALID_DISPLAY_MODES = [
17
18
  "pip",
18
19
  ] as const satisfies readonly McpAppDisplayMode[];
19
20
 
20
- export type McpAppBridgeFrame = Pick<
21
- RenderedFrame,
22
- "iframe" | "origin" | "sendMessage"
23
- >;
21
+ export type McpAppBridgeFrame = SandboxHostFrame;
24
22
 
25
23
  export type CreateMcpAppBridgeOptions = {
26
24
  frame: McpAppBridgeFrame;
27
25
  handlers?: McpAppBridgeHandlers | undefined;
28
26
  hostInfo?: McpAppHostInfo | undefined;
29
27
  hostContext?: McpAppHostContext | undefined;
30
- targetWindow?: Window | undefined;
31
28
  };
32
29
 
33
30
  export type McpAppBridge = {
31
+ onMessage: (event: MessageEvent) => void;
34
32
  dispose: () => void;
35
33
  notifyToolInput: (input: unknown) => void;
36
34
  notifyToolResult: (result: unknown) => void;
@@ -90,13 +88,8 @@ export function createMcpAppBridge(
90
88
  handlers = {},
91
89
  hostInfo = DEFAULT_HOST_INFO,
92
90
  hostContext = {},
93
- targetWindow = typeof window !== "undefined" ? window : undefined,
94
91
  } = opts;
95
92
 
96
- if (!targetWindow) {
97
- throw new Error("createMcpAppBridge requires a window context");
98
- }
99
-
100
93
  const post = (msg: McpAppJsonRpcMessage) => {
101
94
  frame.sendMessage(msg);
102
95
  };
@@ -136,10 +129,15 @@ export function createMcpAppBridge(
136
129
 
137
130
  switch (normalizeMethod(req.method)) {
138
131
  case "ui/initialize": {
132
+ const requestedProtocolVersion =
133
+ isRecord(params) && typeof params.protocolVersion === "string"
134
+ ? params.protocolVersion
135
+ : MCP_APP_PROTOCOL_VERSION;
139
136
  respond(req.id, {
140
137
  result: {
141
- protocolVersion: MCP_APP_PROTOCOL_VERSION,
138
+ protocolVersion: requestedProtocolVersion,
142
139
  host: hostInfo,
140
+ hostInfo,
143
141
  hostContext,
144
142
  capabilities: {
145
143
  tools: handlers.callTool ? {} : undefined,
@@ -154,6 +152,18 @@ export function createMcpAppBridge(
154
152
  updateModelContext: !!handlers.updateModelContext,
155
153
  },
156
154
  },
155
+ hostCapabilities: {
156
+ ...(handlers.openLink ? { openLinks: {} } : {}),
157
+ ...(handlers.callTool ? { serverTools: {} } : {}),
158
+ ...(handlers.readResource || handlers.listResources
159
+ ? { serverResources: {} }
160
+ : {}),
161
+ ...(handlers.updateModelContext
162
+ ? { updateModelContext: { text: {} } }
163
+ : {}),
164
+ ...(handlers.sendMessage ? { message: { text: {} } } : {}),
165
+ ...(handlers.onLog ? { logging: {} } : {}),
166
+ },
157
167
  },
158
168
  });
159
169
  return;
@@ -405,11 +415,9 @@ export function createMcpAppBridge(
405
415
  }
406
416
  };
407
417
 
408
- // Cross-origin guard: ignore any postMessage not originating from this
409
- // app's iframe contentWindow at the SafeContentFrame-issued origin.
418
+ // The host applies the cross-origin guard before delegating; this only
419
+ // validates the JSON-RPC envelope.
410
420
  const onMessage = (event: MessageEvent) => {
411
- if (event.source !== frame.iframe.contentWindow) return;
412
- if (event.origin !== frame.origin) return;
413
421
  if (!isJsonRpcMessage(event.data)) return;
414
422
 
415
423
  const msg = event.data;
@@ -420,18 +428,20 @@ export function createMcpAppBridge(
420
428
  }
421
429
  };
422
430
 
423
- targetWindow.addEventListener("message", onMessage);
424
-
425
431
  return {
426
- dispose: () => {
427
- targetWindow.removeEventListener("message", onMessage);
428
- },
432
+ onMessage,
433
+ dispose: () => {},
429
434
  notifyToolInput: (input: unknown) => {
430
435
  post({
431
436
  jsonrpc: "2.0",
432
437
  method: "notifications/tools/call/input",
433
438
  params: { input },
434
439
  });
440
+ post({
441
+ jsonrpc: "2.0",
442
+ method: "ui/notifications/tool-input",
443
+ params: isRecord(input) ? { arguments: input } : {},
444
+ });
435
445
  },
436
446
  notifyToolResult: (result: unknown) => {
437
447
  post({
@@ -439,6 +449,13 @@ export function createMcpAppBridge(
439
449
  method: "notifications/tools/call/result",
440
450
  params: { result },
441
451
  });
452
+ post({
453
+ jsonrpc: "2.0",
454
+ method: "ui/notifications/tool-result",
455
+ params: isRecord(result)
456
+ ? result
457
+ : { content: [{ type: "text", text: String(result) }] },
458
+ });
442
459
  },
443
460
  notifyHostContextChanged: (ctx: McpAppHostContext) => {
444
461
  post({
@@ -446,6 +463,11 @@ export function createMcpAppBridge(
446
463
  method: "notifications/host_context/changed",
447
464
  params: ctx,
448
465
  });
466
+ post({
467
+ jsonrpc: "2.0",
468
+ method: "ui/notifications/host-context-changed",
469
+ params: ctx,
470
+ });
449
471
  },
450
472
  };
451
473
  }
@@ -1,9 +1,8 @@
1
- import type { CSSProperties } from "react";
2
1
  import type {
3
2
  McpAppMetadata,
4
3
  ToolCallMessagePartMcpMetadata,
5
4
  } from "@assistant-ui/core";
6
- import type { SandboxOption } from "safe-content-frame";
5
+ import type { SandboxHostConfig } from "../sandbox-host/SandboxHost";
7
6
 
8
7
  export type { McpAppMetadata, ToolCallMessagePartMcpMetadata };
9
8
 
@@ -97,16 +96,7 @@ export type McpAppBridgeHandlers = {
97
96
  onError?: (error: Error) => void;
98
97
  };
99
98
 
100
- export type McpAppSandboxConfig = {
101
- sandbox?: SandboxOption[];
102
- useShadowDom?: boolean;
103
- enableBrowserCaching?: boolean;
104
- salt?: string;
105
- product?: string;
106
- className?: string;
107
- style?: CSSProperties;
108
- unsafeDocumentWrite?: boolean;
109
- };
99
+ export type McpAppSandboxConfig = SandboxHostConfig;
110
100
 
111
101
  export type McpAppFrameProps = {
112
102
  app: McpAppMetadata;
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useAui, useAuiState } from "@assistant-ui/store";
4
- import { useResource } from "@assistant-ui/tap/react";
4
+ import { useResource } from "@assistant-ui/tap";
5
5
  import type { Unstable_TriggerAdapter } from "@assistant-ui/core";
6
6
  import {
7
7
  createContext,
@@ -1,4 +1,5 @@
1
- import { resource, tapEffectEvent, tapResource } from "@assistant-ui/tap";
1
+ import { useEffectEvent } from "react";
2
+ import { useResource, resource } from "@assistant-ui/tap";
2
3
  import type {
3
4
  Unstable_TriggerAdapter,
4
5
  Unstable_TriggerCategory,
@@ -50,88 +51,86 @@ export type TriggerPopoverResourceOutput = {
50
51
  };
51
52
 
52
53
  /** Composes detection, navigation, keyboard, and selection sub-resources. */
53
- export const TriggerPopoverResource = resource(
54
- ({
55
- adapter,
56
- text,
57
- triggerChar,
58
- behavior,
59
- aui,
60
- popoverId,
61
- }: {
62
- adapter: Unstable_TriggerAdapter | undefined;
63
- text: string;
64
- triggerChar: string;
65
- behavior: TriggerBehavior | undefined;
66
- aui: AssistantClient;
67
- /** Stable ID for accessible element IDs (pass React's useId() from component layer). */
68
- popoverId: string;
69
- }): TriggerPopoverResourceOutput => {
70
- const detection = tapResource(
71
- TriggerDetectionResource({ text, triggerChar }),
72
- );
73
-
74
- const open =
75
- detection.trigger !== null &&
76
- adapter !== undefined &&
77
- behavior !== undefined;
54
+ export const TriggerPopoverResource = resource(function TriggerPopoverResource({
55
+ adapter,
56
+ text,
57
+ triggerChar,
58
+ behavior,
59
+ aui,
60
+ popoverId,
61
+ }: {
62
+ adapter: Unstable_TriggerAdapter | undefined;
63
+ text: string;
64
+ triggerChar: string;
65
+ behavior: TriggerBehavior | undefined;
66
+ aui: AssistantClient;
67
+ /** Stable ID for accessible element IDs (pass React's useId() from component layer). */
68
+ popoverId: string;
69
+ }): TriggerPopoverResourceOutput {
70
+ const detection = useResource(
71
+ TriggerDetectionResource({ text, triggerChar }),
72
+ );
78
73
 
79
- const navigation = tapResource(
80
- TriggerNavigationResource({
81
- adapter,
82
- query: detection.query,
83
- open,
84
- }),
85
- );
74
+ const open =
75
+ detection.trigger !== null &&
76
+ adapter !== undefined &&
77
+ behavior !== undefined;
86
78
 
87
- const onSelected = tapEffectEvent(() => {
88
- navigation.goBack();
89
- });
79
+ const navigation = useResource(
80
+ TriggerNavigationResource({
81
+ adapter,
82
+ query: detection.query,
83
+ open,
84
+ }),
85
+ );
90
86
 
91
- const selection = tapResource(
92
- TriggerSelectionResource({
93
- behavior,
94
- trigger: detection.trigger,
95
- aui,
96
- triggerChar,
97
- setCursorPosition: detection.setCursorPosition,
98
- onSelected,
99
- }),
100
- );
87
+ const onSelected = useEffectEvent(() => {
88
+ navigation.goBack();
89
+ });
101
90
 
102
- const keyboard = tapResource(
103
- TriggerKeyboardResource({
104
- navigableList: navigation.navigableList,
105
- isSearchMode: navigation.isSearchMode,
106
- activeCategoryId: navigation.activeCategoryId,
107
- query: detection.query,
108
- popoverId,
109
- open,
110
- selectItem: selection.selectItem,
111
- selectCategory: navigation.selectCategory,
112
- goBack: navigation.goBack,
113
- close: selection.close,
114
- }),
115
- );
91
+ const selection = useResource(
92
+ TriggerSelectionResource({
93
+ behavior,
94
+ trigger: detection.trigger,
95
+ aui,
96
+ triggerChar,
97
+ setCursorPosition: detection.setCursorPosition,
98
+ onSelected,
99
+ }),
100
+ );
116
101
 
117
- return {
118
- open,
119
- query: detection.query,
120
- activeCategoryId: navigation.activeCategoryId,
121
- categories: navigation.categories,
122
- items: navigation.items,
123
- highlightedIndex: keyboard.highlightedIndex,
102
+ const keyboard = useResource(
103
+ TriggerKeyboardResource({
104
+ navigableList: navigation.navigableList,
124
105
  isSearchMode: navigation.isSearchMode,
106
+ activeCategoryId: navigation.activeCategoryId,
107
+ query: detection.query,
125
108
  popoverId,
126
- highlightedItemId: keyboard.highlightedItemId,
109
+ open,
110
+ selectItem: selection.selectItem,
127
111
  selectCategory: navigation.selectCategory,
128
112
  goBack: navigation.goBack,
129
- selectItem: selection.selectItem,
130
113
  close: selection.close,
131
- highlightIndex: keyboard.highlightIndex,
132
- handleKeyDown: keyboard.handleKeyDown,
133
- setCursorPosition: detection.setCursorPosition,
134
- registerSelectItemOverride: selection.registerSelectItemOverride,
135
- };
136
- },
137
- );
114
+ }),
115
+ );
116
+
117
+ return {
118
+ open,
119
+ query: detection.query,
120
+ activeCategoryId: navigation.activeCategoryId,
121
+ categories: navigation.categories,
122
+ items: navigation.items,
123
+ highlightedIndex: keyboard.highlightedIndex,
124
+ isSearchMode: navigation.isSearchMode,
125
+ popoverId,
126
+ highlightedItemId: keyboard.highlightedItemId,
127
+ selectCategory: navigation.selectCategory,
128
+ goBack: navigation.goBack,
129
+ selectItem: selection.selectItem,
130
+ close: selection.close,
131
+ highlightIndex: keyboard.highlightIndex,
132
+ handleKeyDown: keyboard.handleKeyDown,
133
+ setCursorPosition: detection.setCursorPosition,
134
+ registerSelectItemOverride: selection.registerSelectItemOverride,
135
+ };
136
+ });