@assistant-ui/react 0.15.16 → 0.15.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/index.d.ts +8 -4
- package/dist/index.js +4 -2
- package/dist/mcp-apps/McpAppRenderer.d.ts +36 -9
- package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppRenderer.js +108 -57
- package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppsRemoteHost.js +20 -17
- package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
- package/dist/mcp-apps/app-frame.d.ts.map +1 -1
- package/dist/mcp-apps/app-frame.js +229 -115
- package/dist/mcp-apps/app-frame.js.map +1 -1
- package/dist/mcp-apps/bridge.js +2 -3
- package/dist/mcp-apps/bridge.js.map +1 -1
- package/dist/mcp-apps/index.d.ts +3 -3
- package/dist/model-context/frame/useAssistantFrameHost.d.ts +1 -1
- package/dist/model-context/frame/useAssistantFrameHost.js +10 -11
- package/dist/model-context/frame/useAssistantFrameHost.js.map +1 -1
- package/dist/model-context/makeAssistantVisible.js +1 -1
- package/dist/model-context/makeAssistantVisible.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarCopy.js +3 -3
- package/dist/primitives/actionBar/ActionBarCopy.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js +3 -3
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +3 -3
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +3 -3
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarRoot.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarStopSpeaking.js +3 -3
- package/dist/primitives/actionBar/ActionBarStopSpeaking.js.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreContent.js.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreItem.js.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreRoot.js.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreSeparator.js.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreTrigger.js.map +1 -1
- package/dist/primitives/actionBarMore/scope.d.ts +2 -2
- package/dist/primitives/actionBarMore/scope.d.ts.map +1 -1
- package/dist/primitives/actionBarMore/scope.js.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalAnchor.js.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalContent.js +1 -1
- package/dist/primitives/assistantModal/AssistantModalContent.js.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.js.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalTrigger.js.map +1 -1
- package/dist/primitives/assistantModal/scope.d.ts +2 -2
- package/dist/primitives/assistantModal/scope.d.ts.map +1 -1
- package/dist/primitives/assistantModal/scope.js.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerRoot.js.map +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
- package/dist/primitives/composer/ComposerDictationTranscript.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +1 -3
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerInputPluginContext.d.ts +1 -2
- package/dist/primitives/composer/ComposerInputPluginContext.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerInputPluginContext.js +1 -6
- package/dist/primitives/composer/ComposerInputPluginContext.js.map +1 -1
- package/dist/primitives/composer/ComposerQuote.js +5 -5
- package/dist/primitives/composer/ComposerQuote.js.map +1 -1
- package/dist/primitives/composer/ComposerRoot.js +3 -3
- package/dist/primitives/composer/ComposerRoot.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopover.d.ts +5 -0
- package/dist/primitives/composer/trigger/TriggerPopover.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopover.js +157 -130
- package/dist/primitives/composer/trigger/TriggerPopover.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverAction.js +46 -15
- package/dist/primitives/composer/trigger/TriggerPopoverAction.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverBack.js +3 -3
- package/dist/primitives/composer/trigger/TriggerPopoverBack.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.js +4 -4
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverDirective.js +40 -10
- package/dist/primitives/composer/trigger/TriggerPopoverDirective.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverItems.js +4 -4
- package/dist/primitives/composer/trigger/TriggerPopoverItems.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts +2 -0
- package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverResource.js +63 -61
- package/dist/primitives/composer/trigger/TriggerPopoverResource.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.d.ts +2 -0
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js.map +1 -1
- package/dist/primitives/composer/trigger/detectTrigger.d.ts +21 -1
- package/dist/primitives/composer/trigger/detectTrigger.d.ts.map +1 -0
- package/dist/primitives/composer/trigger/detectTrigger.js +17 -3
- package/dist/primitives/composer/trigger/detectTrigger.js.map +1 -1
- package/dist/primitives/composer/trigger/index.d.ts +3 -1
- package/dist/primitives/composer/trigger/index.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/index.js.map +1 -1
- package/dist/primitives/composer/trigger/matchesTriggerItemQuery.d.ts +6 -0
- package/dist/primitives/composer/trigger/matchesTriggerItemQuery.d.ts.map +1 -0
- package/dist/primitives/composer/trigger/matchesTriggerItemQuery.js +9 -0
- package/dist/primitives/composer/trigger/matchesTriggerItemQuery.js.map +1 -0
- package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts +3 -4
- package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerDetectionResource.js +15 -14
- package/dist/primitives/composer/trigger/triggerDetectionResource.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerNavigationResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerNavigationResource.js +3 -5
- package/dist/primitives/composer/trigger/triggerNavigationResource.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.js +18 -19
- package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
- package/dist/primitives/composer.d.ts +3 -2
- package/dist/primitives/composer.js.map +1 -1
- package/dist/primitives/error/ErrorMessage.js.map +1 -1
- package/dist/primitives/message/MessageIf.js.map +1 -1
- package/dist/primitives/message/MessageParts.js.map +1 -1
- package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
- package/dist/primitives/message/MessageRoot.js +4 -4
- package/dist/primitives/message/MessageRoot.js.map +1 -1
- package/dist/primitives/messagePart/MessagePartText.d.ts +1 -1
- package/dist/primitives/messagePart/MessagePartText.js.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +3 -3
- package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +3 -3
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
- package/dist/primitives/thread/ThreadIf.js.map +1 -1
- package/dist/primitives/thread/ThreadViewport.js +3 -3
- package/dist/primitives/thread/ThreadViewport.js.map +1 -1
- package/dist/primitives/thread/ThreadViewportFooter.js +3 -3
- package/dist/primitives/thread/ThreadViewportFooter.js.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +4 -4
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/dist/primitives/threadList/ThreadListNew.js +3 -3
- package/dist/primitives/threadList/ThreadListNew.js.map +1 -1
- package/dist/primitives/threadListFocusGroup.d.ts +2 -1
- package/dist/primitives/threadListFocusGroup.d.ts.map +1 -1
- package/dist/primitives/threadListFocusGroup.js +2 -2
- package/dist/primitives/threadListFocusGroup.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemRoot.js +3 -3
- package/dist/primitives/threadListItem/ThreadListItemRoot.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.js +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreItem.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreSeparator.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js +1 -2
- package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js.map +1 -1
- package/dist/primitives/threadListItemMore/scope.d.ts +2 -2
- package/dist/primitives/threadListItemMore/scope.d.ts.map +1 -1
- package/dist/primitives/threadListItemMore/scope.js.map +1 -1
- package/dist/sandbox-host/SandboxHost.d.ts.map +1 -1
- package/dist/sandbox-host/SandboxHost.js +148 -72
- package/dist/sandbox-host/SandboxHost.js.map +1 -1
- package/dist/unstable/useLiveCompletionAdapter.js +3 -1
- package/dist/unstable/useLiveCompletionAdapter.js.map +1 -1
- package/dist/unstable/useMentionAdapter.d.ts +9 -3
- package/dist/unstable/useMentionAdapter.d.ts.map +1 -1
- package/dist/unstable/useMentionAdapter.js +50 -43
- package/dist/unstable/useMentionAdapter.js.map +1 -1
- package/dist/unstable/useModelContextSnapshot.d.ts +34 -0
- package/dist/unstable/useModelContextSnapshot.d.ts.map +1 -0
- package/dist/unstable/useModelContextSnapshot.js +61 -0
- package/dist/unstable/useModelContextSnapshot.js.map +1 -0
- package/dist/unstable/useSlashCommandAdapter.d.ts.map +1 -1
- package/dist/unstable/useSlashCommandAdapter.js +37 -29
- package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
- package/dist/unstable/webmcp/WebMcpRegistrationResource.d.ts +14 -0
- package/dist/unstable/webmcp/WebMcpRegistrationResource.d.ts.map +1 -0
- package/dist/unstable/webmcp/WebMcpRegistrationResource.js +85 -0
- package/dist/unstable/webmcp/WebMcpRegistrationResource.js.map +1 -0
- package/dist/unstable/webmcp/convertTools.d.ts +21 -0
- package/dist/unstable/webmcp/convertTools.d.ts.map +1 -0
- package/dist/unstable/webmcp/convertTools.js +110 -0
- package/dist/unstable/webmcp/convertTools.js.map +1 -0
- package/dist/unstable/webmcp/useWebMcpProvider.d.ts +21 -0
- package/dist/unstable/webmcp/useWebMcpProvider.d.ts.map +1 -0
- package/dist/unstable/webmcp/useWebMcpProvider.js +105 -0
- package/dist/unstable/webmcp/useWebMcpProvider.js.map +1 -0
- package/dist/unstable/webmcp/webmcp-host.d.ts +39 -0
- package/dist/unstable/webmcp/webmcp-host.d.ts.map +1 -0
- package/dist/unstable/webmcp/webmcp-host.js +29 -0
- package/dist/unstable/webmcp/webmcp-host.js.map +1 -0
- package/dist/utils/Primitive.d.ts +6 -6
- package/dist/utils/Primitive.js +2 -2
- package/dist/utils/Primitive.js.map +1 -1
- package/dist/utils/createActionButton.js +3 -3
- package/dist/utils/createActionButton.js.map +1 -1
- package/dist/utils/hooks/useOnResizeContent.js +1 -1
- package/dist/utils/hooks/useOnResizeContent.js.map +1 -1
- package/dist/utils/hooks/useOnScrollToBottom.js +1 -1
- package/dist/utils/hooks/useOnScrollToBottom.js.map +1 -1
- package/dist/utils/smooth/SmoothContext.d.ts +24 -0
- package/dist/utils/smooth/SmoothContext.d.ts.map +1 -1
- package/dist/utils/smooth/useSmooth.js +1 -1
- package/dist/utils/smooth/useSmooth.js.map +1 -1
- package/package.json +14 -22
- package/src/index.ts +22 -1
- package/src/mcp-apps/McpAppRenderer.test.tsx +386 -9
- package/src/mcp-apps/McpAppRenderer.tsx +171 -69
- package/src/mcp-apps/McpAppsRemoteHost.concurrent.test.tsx +215 -0
- package/src/mcp-apps/McpAppsRemoteHost.ts +28 -12
- package/src/mcp-apps/app-frame.test.tsx +349 -0
- package/src/mcp-apps/app-frame.tsx +68 -9
- package/src/mcp-apps/bridge.ts +7 -2
- package/src/mcp-apps/index.ts +6 -1
- package/src/model-context/frame/AssistantFrame.test.ts +3 -3
- package/src/model-context/frame/SPEC_AssistantFrame.md +1 -1
- package/src/model-context/frame/useAssistantFrameHost.ts +2 -2
- package/src/model-context/makeAssistantVisible.tsx +1 -1
- package/src/primitives/actionBar/ActionBarCopy.tsx +1 -1
- package/src/primitives/actionBar/ActionBarExportMarkdown.tsx +1 -1
- package/src/primitives/actionBar/ActionBarFeedbackNegative.tsx +1 -1
- package/src/primitives/actionBar/ActionBarFeedbackPositive.tsx +1 -1
- package/src/primitives/actionBar/ActionBarStopSpeaking.tsx +1 -1
- package/src/primitives/actionBarMore/scope.ts +2 -2
- package/src/primitives/assistantModal/AssistantModalContent.tsx +1 -1
- package/src/primitives/assistantModal/scope.ts +2 -2
- package/src/primitives/composer/ComposerAttachmentDropzone.tsx +1 -1
- package/src/primitives/composer/ComposerInput.test.tsx +2 -1
- package/src/primitives/composer/ComposerInput.tsx +3 -3
- package/src/primitives/composer/ComposerInputPluginContext.tsx +0 -10
- package/src/primitives/composer/ComposerQuote.tsx +1 -1
- package/src/primitives/composer/ComposerRoot.tsx +1 -1
- package/src/primitives/composer/trigger/TriggerPopover.tsx +16 -1
- package/src/primitives/composer/trigger/TriggerPopoverAction.tsx +4 -2
- package/src/primitives/composer/trigger/TriggerPopoverBack.tsx +1 -1
- package/src/primitives/composer/trigger/TriggerPopoverBehavior.test.tsx +109 -0
- package/src/primitives/composer/trigger/TriggerPopoverCategories.tsx +1 -1
- package/src/primitives/composer/trigger/TriggerPopoverDirective.tsx +4 -2
- package/src/primitives/composer/trigger/TriggerPopoverItems.tsx +1 -1
- package/src/primitives/composer/trigger/TriggerPopoverResource.ts +4 -1
- package/src/primitives/composer/trigger/TriggerPopoverRootContext.tsx +2 -0
- package/src/primitives/composer/trigger/detectTrigger.test.ts +88 -5
- package/src/primitives/composer/trigger/detectTrigger.ts +50 -9
- package/src/primitives/composer/trigger/index.ts +1 -0
- package/src/primitives/composer/trigger/matchesTriggerItemQuery.test.ts +52 -0
- package/src/primitives/composer/trigger/matchesTriggerItemQuery.ts +13 -0
- package/src/primitives/composer/trigger/triggerDetectionResource.ts +10 -7
- package/src/primitives/composer/trigger/triggerNavigationResource.ts +3 -10
- package/src/primitives/composer/trigger/triggerSelectionResource.test.ts +15 -3
- package/src/primitives/composer/trigger/triggerSelectionResource.ts +1 -4
- package/src/primitives/composer.ts +1 -0
- package/src/primitives/message/MessageRoot.tsx +1 -1
- package/src/primitives/selectionToolbar/SelectionToolbarQuote.tsx +1 -1
- package/src/primitives/selectionToolbar/SelectionToolbarRoot.tsx +1 -1
- package/src/primitives/thread/ThreadViewport.tsx +1 -1
- package/src/primitives/thread/ThreadViewportFooter.tsx +1 -1
- package/src/primitives/thread/useThreadViewportAutoScroll.ts +8 -7
- package/src/primitives/threadList/ThreadListNew.tsx +1 -1
- package/src/primitives/threadListFocusGroup.ts +2 -2
- package/src/primitives/threadListItem/ThreadListItemRoot.tsx +1 -1
- package/src/primitives/threadListItem/ThreadListItemTrigger.tsx +1 -1
- package/src/primitives/threadListItemMore/ThreadListItemMoreContent.tsx +1 -1
- package/src/primitives/threadListItemMore/ThreadListItemMoreRoot.tsx +1 -1
- package/src/primitives/threadListItemMore/ThreadListItemMoreTrigger.tsx +2 -2
- package/src/primitives/threadListItemMore/scope.ts +2 -2
- package/src/sandbox-host/SandboxHost.test.tsx +165 -3
- package/src/sandbox-host/SandboxHost.tsx +42 -9
- package/src/tests/RemoteThreadListRuntime.adapterProvider.test.tsx +31 -1
- package/src/unstable/useComposerInputHistory.test.tsx +2 -1
- package/src/unstable/useLiveCompletionAdapter.test.tsx +60 -1
- package/src/unstable/useLiveCompletionAdapter.ts +4 -1
- package/src/unstable/useMentionAdapter.test.tsx +337 -0
- package/src/unstable/useMentionAdapter.ts +94 -57
- package/src/unstable/useModelContextSnapshot.test.tsx +132 -0
- package/src/unstable/useModelContextSnapshot.ts +67 -0
- package/src/unstable/useSlashCommandAdapter.test.tsx +141 -0
- package/src/unstable/useSlashCommandAdapter.ts +47 -19
- package/src/unstable/webmcp/WebMcpRegistrationResource.ts +92 -0
- package/src/unstable/webmcp/__tests__/webmcp.fake.tsx +168 -0
- package/src/unstable/webmcp/convertTools.test.ts +393 -0
- package/src/unstable/webmcp/convertTools.ts +180 -0
- package/src/unstable/webmcp/useWebMcpProvider.test.tsx +466 -0
- package/src/unstable/webmcp/useWebMcpProvider.ts +150 -0
- package/src/unstable/webmcp/webmcp-host.test.ts +142 -0
- package/src/unstable/webmcp/webmcp-host.ts +83 -0
- package/src/utils/Primitive.tsx +2 -2
- package/src/utils/createActionButton.tsx +1 -1
- package/src/utils/hooks/useOnResizeContent.ts +1 -1
- package/src/utils/hooks/useOnScrollToBottom.ts +1 -1
- package/src/utils/smooth/useSmooth.ts +1 -1
- package/dist/legacy-runtime/runtime/MessagePartRuntime.d.ts +0 -3
- package/dist/legacy-runtime/runtime/MessagePartRuntime.js +0 -2
- package/dist/legacy-runtime/runtime/MessageRuntime.d.ts +0 -3
- package/dist/legacy-runtime/runtime/MessageRuntime.js +0 -2
- package/dist/legacy-runtime/runtime/ThreadListRuntime.d.ts +0 -3
- package/dist/legacy-runtime/runtime/ThreadListRuntime.js +0 -2
- package/dist/utils/invokeCallbackSafely.d.ts +0 -5
- package/dist/utils/invokeCallbackSafely.d.ts.map +0 -1
- package/dist/utils/invokeCallbackSafely.js +0 -15
- package/dist/utils/invokeCallbackSafely.js.map +0 -1
- package/dist/utils/useToolArgsFieldStatus.d.ts +0 -15
- package/dist/utils/useToolArgsFieldStatus.d.ts.map +0 -1
- package/dist/utils/useToolArgsFieldStatus.js +0 -23
- package/dist/utils/useToolArgsFieldStatus.js.map +0 -1
- package/src/legacy-runtime/runtime/MessagePartRuntime.ts +0 -2
- package/src/legacy-runtime/runtime/MessageRuntime.ts +0 -3
- package/src/legacy-runtime/runtime/ThreadListRuntime.ts +0 -4
- package/src/utils/invokeCallbackSafely.ts +0 -11
- package/src/utils/useToolArgsFieldStatus.ts +0 -18
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { act, startTransition, Suspense } from "react";
|
|
3
|
+
import { cleanup, render } from "@testing-library/react";
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import type {
|
|
6
|
+
SandboxBridge,
|
|
7
|
+
SandboxHostProps,
|
|
8
|
+
} from "../sandbox-host/SandboxHost";
|
|
9
|
+
import type { CreateMcpAppBridgeOptions, McpAppBridge } from "./bridge";
|
|
10
|
+
import { MCP_APP_MIME_TYPE, type McpAppHostContext } from "./types";
|
|
11
|
+
|
|
12
|
+
const { sandboxHostMock, createMcpAppBridgeMock } = vi.hoisted(() => ({
|
|
13
|
+
sandboxHostMock: vi.fn(),
|
|
14
|
+
createMcpAppBridgeMock: vi.fn(),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
vi.mock("../sandbox-host/SandboxHost", () => ({
|
|
18
|
+
SandboxHost: sandboxHostMock,
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
vi.mock("./bridge", async (importOriginal) => ({
|
|
22
|
+
...(await importOriginal()),
|
|
23
|
+
createMcpAppBridge: createMcpAppBridgeMock,
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
import { McpAppFrame } from "./app-frame";
|
|
27
|
+
|
|
28
|
+
describe("McpAppFrame", () => {
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
cleanup();
|
|
31
|
+
vi.clearAllMocks();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("keeps bridge options scoped to committed renders", async () => {
|
|
35
|
+
let committedCreateBridge: SandboxHostProps["createBridge"] | null = null;
|
|
36
|
+
sandboxHostMock.mockImplementation((props: SandboxHostProps) => {
|
|
37
|
+
committedCreateBridge ??= props.createBridge;
|
|
38
|
+
return null;
|
|
39
|
+
});
|
|
40
|
+
const bridge: McpAppBridge = {
|
|
41
|
+
onMessage: vi.fn(),
|
|
42
|
+
dispose: vi.fn(),
|
|
43
|
+
notifyToolInput: vi.fn(),
|
|
44
|
+
notifyToolResult: vi.fn(),
|
|
45
|
+
notifyHostContextChanged: vi.fn(),
|
|
46
|
+
};
|
|
47
|
+
createMcpAppBridgeMock.mockReturnValue(bridge);
|
|
48
|
+
const callToolA = vi.fn();
|
|
49
|
+
const callToolB = vi.fn();
|
|
50
|
+
const interruptedRender = vi.fn();
|
|
51
|
+
const pending = new Promise<never>(() => {});
|
|
52
|
+
const Block = () => {
|
|
53
|
+
interruptedRender();
|
|
54
|
+
throw pending;
|
|
55
|
+
};
|
|
56
|
+
const view = (
|
|
57
|
+
workspace: string,
|
|
58
|
+
callTool: typeof callToolA,
|
|
59
|
+
blocked: boolean,
|
|
60
|
+
) => (
|
|
61
|
+
<Suspense fallback={null}>
|
|
62
|
+
<McpAppFrame
|
|
63
|
+
app={{ resourceUri: "ui://example/widget" }}
|
|
64
|
+
resource={{
|
|
65
|
+
uri: "ui://example/widget",
|
|
66
|
+
mimeType: MCP_APP_MIME_TYPE,
|
|
67
|
+
html: "",
|
|
68
|
+
}}
|
|
69
|
+
handlers={{ callTool }}
|
|
70
|
+
hostContext={{ workspace }}
|
|
71
|
+
/>
|
|
72
|
+
{blocked ? <Block /> : null}
|
|
73
|
+
</Suspense>
|
|
74
|
+
);
|
|
75
|
+
const rendered = render(view("workspace-a", callToolA, false));
|
|
76
|
+
|
|
77
|
+
act(() => {
|
|
78
|
+
startTransition(() =>
|
|
79
|
+
rendered.rerender(view("workspace-b", callToolB, true)),
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
expect(interruptedRender).toHaveBeenCalled();
|
|
83
|
+
expect(committedCreateBridge).not.toBeNull();
|
|
84
|
+
|
|
85
|
+
const sandboxBridge: SandboxBridge = committedCreateBridge!(
|
|
86
|
+
{
|
|
87
|
+
iframe: document.createElement("iframe"),
|
|
88
|
+
origin: "https://widget.example",
|
|
89
|
+
sendMessage: vi.fn(),
|
|
90
|
+
},
|
|
91
|
+
{ setHeight: vi.fn() },
|
|
92
|
+
);
|
|
93
|
+
const options = createMcpAppBridgeMock.mock
|
|
94
|
+
.calls[0]![0] as CreateMcpAppBridgeOptions;
|
|
95
|
+
expect(options.hostContext).toEqual({ workspace: "workspace-a" });
|
|
96
|
+
await options.handlers?.callTool?.({ name: "search" });
|
|
97
|
+
expect(callToolA).toHaveBeenCalledOnce();
|
|
98
|
+
expect(callToolB).not.toHaveBeenCalled();
|
|
99
|
+
|
|
100
|
+
sandboxBridge.dispose();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("only notifies the widget when host context actually changes", () => {
|
|
104
|
+
let createBridge: SandboxHostProps["createBridge"] | null = null;
|
|
105
|
+
sandboxHostMock.mockImplementation((props: SandboxHostProps) => {
|
|
106
|
+
createBridge ??= props.createBridge;
|
|
107
|
+
return null;
|
|
108
|
+
});
|
|
109
|
+
const bridge: McpAppBridge = {
|
|
110
|
+
onMessage: vi.fn(),
|
|
111
|
+
dispose: vi.fn(),
|
|
112
|
+
notifyToolInput: vi.fn(),
|
|
113
|
+
notifyToolResult: vi.fn(),
|
|
114
|
+
notifyHostContextChanged: vi.fn(),
|
|
115
|
+
};
|
|
116
|
+
createMcpAppBridgeMock.mockReturnValue(bridge);
|
|
117
|
+
|
|
118
|
+
const view = (hostContext: McpAppHostContext) => (
|
|
119
|
+
<McpAppFrame
|
|
120
|
+
app={{ resourceUri: "ui://example/widget" }}
|
|
121
|
+
resource={{
|
|
122
|
+
uri: "ui://example/widget",
|
|
123
|
+
mimeType: MCP_APP_MIME_TYPE,
|
|
124
|
+
html: "",
|
|
125
|
+
}}
|
|
126
|
+
hostContext={hostContext}
|
|
127
|
+
/>
|
|
128
|
+
);
|
|
129
|
+
const rendered = render(
|
|
130
|
+
view({ displayMode: "inline", availableDisplayModes: ["inline", "pip"] }),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const sandboxBridge = createBridge!(
|
|
134
|
+
{
|
|
135
|
+
iframe: document.createElement("iframe"),
|
|
136
|
+
origin: "https://widget.example",
|
|
137
|
+
sendMessage: vi.fn(),
|
|
138
|
+
},
|
|
139
|
+
{ setHeight: vi.fn() },
|
|
140
|
+
);
|
|
141
|
+
const options = createMcpAppBridgeMock.mock
|
|
142
|
+
.calls[0]![0] as CreateMcpAppBridgeOptions;
|
|
143
|
+
options.handlers?.onInitialized?.();
|
|
144
|
+
|
|
145
|
+
rendered.rerender(
|
|
146
|
+
view({ displayMode: "inline", availableDisplayModes: ["inline", "pip"] }),
|
|
147
|
+
);
|
|
148
|
+
expect(bridge.notifyHostContextChanged).not.toHaveBeenCalled();
|
|
149
|
+
|
|
150
|
+
rendered.rerender(
|
|
151
|
+
view({
|
|
152
|
+
displayMode: "fullscreen",
|
|
153
|
+
availableDisplayModes: ["inline", "pip"],
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
expect(bridge.notifyHostContextChanged).toHaveBeenCalledTimes(1);
|
|
157
|
+
expect(bridge.notifyHostContextChanged).toHaveBeenCalledWith({
|
|
158
|
+
displayMode: "fullscreen",
|
|
159
|
+
availableDisplayModes: ["inline", "pip"],
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
sandboxBridge.dispose();
|
|
163
|
+
});
|
|
164
|
+
it("cancels a queued host context that returns to the delivered value", () => {
|
|
165
|
+
let createBridge: SandboxHostProps["createBridge"] | null = null;
|
|
166
|
+
sandboxHostMock.mockImplementation((props: SandboxHostProps) => {
|
|
167
|
+
createBridge ??= props.createBridge;
|
|
168
|
+
return null;
|
|
169
|
+
});
|
|
170
|
+
const bridge: McpAppBridge = {
|
|
171
|
+
onMessage: vi.fn(),
|
|
172
|
+
dispose: vi.fn(),
|
|
173
|
+
notifyToolInput: vi.fn(),
|
|
174
|
+
notifyToolResult: vi.fn(),
|
|
175
|
+
notifyHostContextChanged: vi.fn(),
|
|
176
|
+
};
|
|
177
|
+
createMcpAppBridgeMock.mockReturnValue(bridge);
|
|
178
|
+
|
|
179
|
+
const view = (hostContext: McpAppHostContext) => (
|
|
180
|
+
<McpAppFrame
|
|
181
|
+
app={{ resourceUri: "ui://example/widget" }}
|
|
182
|
+
resource={{
|
|
183
|
+
uri: "ui://example/widget",
|
|
184
|
+
mimeType: MCP_APP_MIME_TYPE,
|
|
185
|
+
html: "",
|
|
186
|
+
}}
|
|
187
|
+
hostContext={hostContext}
|
|
188
|
+
/>
|
|
189
|
+
);
|
|
190
|
+
const rendered = render(view({ displayMode: "inline" }));
|
|
191
|
+
|
|
192
|
+
const sandboxBridge = createBridge!(
|
|
193
|
+
{
|
|
194
|
+
iframe: document.createElement("iframe"),
|
|
195
|
+
origin: "https://widget.example",
|
|
196
|
+
sendMessage: vi.fn(),
|
|
197
|
+
},
|
|
198
|
+
{ setHeight: vi.fn() },
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
rendered.rerender(view({ displayMode: "fullscreen" }));
|
|
202
|
+
rendered.rerender(view({ displayMode: "inline" }));
|
|
203
|
+
|
|
204
|
+
const options = createMcpAppBridgeMock.mock
|
|
205
|
+
.calls[0]![0] as CreateMcpAppBridgeOptions;
|
|
206
|
+
options.handlers?.onInitialized?.();
|
|
207
|
+
|
|
208
|
+
expect(bridge.notifyHostContextChanged).not.toHaveBeenCalled();
|
|
209
|
+
|
|
210
|
+
sandboxBridge.dispose();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("treats an equal host context as unchanged when it is not strict JSON", () => {
|
|
214
|
+
let createBridge: SandboxHostProps["createBridge"] | null = null;
|
|
215
|
+
sandboxHostMock.mockImplementation((props: SandboxHostProps) => {
|
|
216
|
+
createBridge ??= props.createBridge;
|
|
217
|
+
return null;
|
|
218
|
+
});
|
|
219
|
+
const bridge: McpAppBridge = {
|
|
220
|
+
onMessage: vi.fn(),
|
|
221
|
+
dispose: vi.fn(),
|
|
222
|
+
notifyToolInput: vi.fn(),
|
|
223
|
+
notifyToolResult: vi.fn(),
|
|
224
|
+
notifyHostContextChanged: vi.fn(),
|
|
225
|
+
};
|
|
226
|
+
createMcpAppBridgeMock.mockReturnValue(bridge);
|
|
227
|
+
|
|
228
|
+
const view = () => (
|
|
229
|
+
<McpAppFrame
|
|
230
|
+
app={{ resourceUri: "ui://example/widget" }}
|
|
231
|
+
resource={{
|
|
232
|
+
uri: "ui://example/widget",
|
|
233
|
+
mimeType: MCP_APP_MIME_TYPE,
|
|
234
|
+
html: "",
|
|
235
|
+
}}
|
|
236
|
+
hostContext={{ displayMode: "inline", workspace: undefined }}
|
|
237
|
+
/>
|
|
238
|
+
);
|
|
239
|
+
const rendered = render(view());
|
|
240
|
+
|
|
241
|
+
const sandboxBridge = createBridge!(
|
|
242
|
+
{
|
|
243
|
+
iframe: document.createElement("iframe"),
|
|
244
|
+
origin: "https://widget.example",
|
|
245
|
+
sendMessage: vi.fn(),
|
|
246
|
+
},
|
|
247
|
+
{ setHeight: vi.fn() },
|
|
248
|
+
);
|
|
249
|
+
const options = createMcpAppBridgeMock.mock
|
|
250
|
+
.calls[0]![0] as CreateMcpAppBridgeOptions;
|
|
251
|
+
options.handlers?.onInitialized?.();
|
|
252
|
+
|
|
253
|
+
rendered.rerender(view());
|
|
254
|
+
expect(bridge.notifyHostContextChanged).not.toHaveBeenCalled();
|
|
255
|
+
|
|
256
|
+
sandboxBridge.dispose();
|
|
257
|
+
});
|
|
258
|
+
it("notifies when a context value changes behind an unchanged key set", () => {
|
|
259
|
+
let createBridge: SandboxHostProps["createBridge"] | null = null;
|
|
260
|
+
sandboxHostMock.mockImplementation((props: SandboxHostProps) => {
|
|
261
|
+
createBridge ??= props.createBridge;
|
|
262
|
+
return null;
|
|
263
|
+
});
|
|
264
|
+
const bridge: McpAppBridge = {
|
|
265
|
+
onMessage: vi.fn(),
|
|
266
|
+
dispose: vi.fn(),
|
|
267
|
+
notifyToolInput: vi.fn(),
|
|
268
|
+
notifyToolResult: vi.fn(),
|
|
269
|
+
notifyHostContextChanged: vi.fn(),
|
|
270
|
+
};
|
|
271
|
+
createMcpAppBridgeMock.mockReturnValue(bridge);
|
|
272
|
+
|
|
273
|
+
const view = (updatedAt: Date) => (
|
|
274
|
+
<McpAppFrame
|
|
275
|
+
app={{ resourceUri: "ui://example/widget" }}
|
|
276
|
+
resource={{
|
|
277
|
+
uri: "ui://example/widget",
|
|
278
|
+
mimeType: MCP_APP_MIME_TYPE,
|
|
279
|
+
html: "",
|
|
280
|
+
}}
|
|
281
|
+
hostContext={{ updatedAt }}
|
|
282
|
+
/>
|
|
283
|
+
);
|
|
284
|
+
const rendered = render(view(new Date(1)));
|
|
285
|
+
|
|
286
|
+
const sandboxBridge = createBridge!(
|
|
287
|
+
{
|
|
288
|
+
iframe: document.createElement("iframe"),
|
|
289
|
+
origin: "https://widget.example",
|
|
290
|
+
sendMessage: vi.fn(),
|
|
291
|
+
},
|
|
292
|
+
{ setHeight: vi.fn() },
|
|
293
|
+
);
|
|
294
|
+
const options = createMcpAppBridgeMock.mock
|
|
295
|
+
.calls[0]![0] as CreateMcpAppBridgeOptions;
|
|
296
|
+
options.handlers?.onInitialized?.();
|
|
297
|
+
|
|
298
|
+
rendered.rerender(view(new Date(2)));
|
|
299
|
+
expect(bridge.notifyHostContextChanged).toHaveBeenCalledTimes(1);
|
|
300
|
+
|
|
301
|
+
sandboxBridge.dispose();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("does not repeat the empty context the bridge already delivered", () => {
|
|
305
|
+
let createBridge: SandboxHostProps["createBridge"] | null = null;
|
|
306
|
+
sandboxHostMock.mockImplementation((props: SandboxHostProps) => {
|
|
307
|
+
createBridge ??= props.createBridge;
|
|
308
|
+
return null;
|
|
309
|
+
});
|
|
310
|
+
const bridge: McpAppBridge = {
|
|
311
|
+
onMessage: vi.fn(),
|
|
312
|
+
dispose: vi.fn(),
|
|
313
|
+
notifyToolInput: vi.fn(),
|
|
314
|
+
notifyToolResult: vi.fn(),
|
|
315
|
+
notifyHostContextChanged: vi.fn(),
|
|
316
|
+
};
|
|
317
|
+
createMcpAppBridgeMock.mockReturnValue(bridge);
|
|
318
|
+
|
|
319
|
+
const view = (hostContext?: McpAppHostContext) => (
|
|
320
|
+
<McpAppFrame
|
|
321
|
+
app={{ resourceUri: "ui://example/widget" }}
|
|
322
|
+
resource={{
|
|
323
|
+
uri: "ui://example/widget",
|
|
324
|
+
mimeType: MCP_APP_MIME_TYPE,
|
|
325
|
+
html: "",
|
|
326
|
+
}}
|
|
327
|
+
{...(hostContext === undefined ? {} : { hostContext })}
|
|
328
|
+
/>
|
|
329
|
+
);
|
|
330
|
+
const rendered = render(view());
|
|
331
|
+
|
|
332
|
+
const sandboxBridge = createBridge!(
|
|
333
|
+
{
|
|
334
|
+
iframe: document.createElement("iframe"),
|
|
335
|
+
origin: "https://widget.example",
|
|
336
|
+
sendMessage: vi.fn(),
|
|
337
|
+
},
|
|
338
|
+
{ setHeight: vi.fn() },
|
|
339
|
+
);
|
|
340
|
+
const options = createMcpAppBridgeMock.mock
|
|
341
|
+
.calls[0]![0] as CreateMcpAppBridgeOptions;
|
|
342
|
+
options.handlers?.onInitialized?.();
|
|
343
|
+
|
|
344
|
+
rendered.rerender(view({}));
|
|
345
|
+
expect(bridge.notifyHostContextChanged).not.toHaveBeenCalled();
|
|
346
|
+
|
|
347
|
+
sandboxBridge.dispose();
|
|
348
|
+
});
|
|
349
|
+
});
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type MutableRefObject,
|
|
5
|
+
useEffect,
|
|
6
|
+
useLayoutEffect,
|
|
7
|
+
useRef,
|
|
8
|
+
} from "react";
|
|
4
9
|
import { type McpAppBridge, createMcpAppBridge } from "./bridge";
|
|
5
10
|
import {
|
|
6
11
|
SandboxHost,
|
|
@@ -13,11 +18,48 @@ import type {
|
|
|
13
18
|
McpAppFrameProps,
|
|
14
19
|
McpAppHostContext,
|
|
15
20
|
} from "./types";
|
|
21
|
+
import { isRecord } from "@assistant-ui/core/internal";
|
|
16
22
|
|
|
17
23
|
const DEFAULT_PRODUCT = "assistant-ui-mcp-app";
|
|
18
24
|
const INIT_TIMEOUT_MS = 5000;
|
|
19
25
|
const DEFAULT_MAX_HEIGHT = 800;
|
|
20
26
|
|
|
27
|
+
// Only a plain object can be compared by its own keys. Structured clone carries
|
|
28
|
+
// a Date, Map, or class instance to the widget intact, and those expose no
|
|
29
|
+
// enumerable keys, so walking them would report two different values as equal.
|
|
30
|
+
const isPlainObject = (value: unknown): value is Record<string, unknown> => {
|
|
31
|
+
if (!isRecord(value)) return false;
|
|
32
|
+
const prototype = Object.getPrototypeOf(value);
|
|
33
|
+
return prototype === null || prototype === Object.prototype;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Hosts commonly rebuild an equal context object on every render, and the
|
|
37
|
+
// widget cannot tell a repeated notification apart from a real change. The
|
|
38
|
+
// open index signature on McpAppHostContext admits values JSON rejects (an
|
|
39
|
+
// undefined property value is enough), so leaves the walk cannot compare fall
|
|
40
|
+
// back to identity rather than failing the whole comparison.
|
|
41
|
+
const isSameHostContext = (a: unknown, b: unknown, depth = 0): boolean => {
|
|
42
|
+
if (Object.is(a, b)) return true;
|
|
43
|
+
if (depth > 100) return false;
|
|
44
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
45
|
+
return (
|
|
46
|
+
Array.isArray(a) &&
|
|
47
|
+
Array.isArray(b) &&
|
|
48
|
+
a.length === b.length &&
|
|
49
|
+
a.every((item, index) => isSameHostContext(item, b[index], depth + 1))
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (!isPlainObject(a) || !isPlainObject(b)) return false;
|
|
53
|
+
const aKeys = Object.keys(a);
|
|
54
|
+
return (
|
|
55
|
+
aKeys.length === Object.keys(b).length &&
|
|
56
|
+
aKeys.every(
|
|
57
|
+
(key) =>
|
|
58
|
+
Object.hasOwn(b, key) && isSameHostContext(a[key], b[key], depth + 1),
|
|
59
|
+
)
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
21
63
|
function useBridgeNotify<T>(
|
|
22
64
|
value: T | undefined,
|
|
23
65
|
bridgeRef: MutableRefObject<McpAppBridge | null>,
|
|
@@ -25,18 +67,24 @@ function useBridgeNotify<T>(
|
|
|
25
67
|
pendingRef: MutableRefObject<T | undefined>,
|
|
26
68
|
lastSentRef: MutableRefObject<T | undefined>,
|
|
27
69
|
notify: (bridge: McpAppBridge, v: T) => void,
|
|
70
|
+
isEqual: (sent: T | undefined, next: T) => boolean = Object.is,
|
|
28
71
|
) {
|
|
29
72
|
useEffect(() => {
|
|
30
73
|
if (!bridgeRef.current) return;
|
|
31
|
-
|
|
32
|
-
|
|
74
|
+
// Nothing to deliver, either because there is no value or because the
|
|
75
|
+
// widget already holds it; a queued value here is superseded and would
|
|
76
|
+
// otherwise flush after initialization as a stale update.
|
|
77
|
+
if (value === undefined || isEqual(lastSentRef.current, value)) {
|
|
78
|
+
pendingRef.current = undefined;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
33
81
|
if (!widgetReadyRef.current) {
|
|
34
82
|
pendingRef.current = value;
|
|
35
83
|
return;
|
|
36
84
|
}
|
|
37
85
|
notify(bridgeRef.current, value);
|
|
38
86
|
lastSentRef.current = value;
|
|
39
|
-
// oxlint-disable-next-line react/exhaustive-deps -- refs are stable; notify
|
|
87
|
+
// oxlint-disable-next-line react/exhaustive-deps -- refs are stable; notify and isEqual are assumed stable; re-run only when value changes
|
|
40
88
|
}, [value]);
|
|
41
89
|
}
|
|
42
90
|
|
|
@@ -121,14 +169,22 @@ export function McpAppFrame({
|
|
|
121
169
|
undefined,
|
|
122
170
|
);
|
|
123
171
|
|
|
124
|
-
const liveRef = useRef<LiveSnapshot>(
|
|
125
|
-
liveRef.current = {
|
|
172
|
+
const liveRef = useRef<LiveSnapshot>({
|
|
126
173
|
handlers,
|
|
127
174
|
hostInfo,
|
|
128
175
|
hostContext,
|
|
129
176
|
input,
|
|
130
177
|
output,
|
|
131
|
-
};
|
|
178
|
+
});
|
|
179
|
+
useLayoutEffect(() => {
|
|
180
|
+
liveRef.current = {
|
|
181
|
+
handlers,
|
|
182
|
+
hostInfo,
|
|
183
|
+
hostContext,
|
|
184
|
+
input,
|
|
185
|
+
output,
|
|
186
|
+
};
|
|
187
|
+
}, [handlers, hostInfo, hostContext, input, output]);
|
|
132
188
|
|
|
133
189
|
const createBridge = (
|
|
134
190
|
frame: SandboxHostFrame,
|
|
@@ -195,8 +251,10 @@ export function McpAppFrame({
|
|
|
195
251
|
|
|
196
252
|
if (current.input !== undefined) pendingInputRef.current = current.input;
|
|
197
253
|
if (current.output !== undefined) pendingOutputRef.current = current.output;
|
|
198
|
-
// hostContext is delivered inside the ui/initialize response
|
|
199
|
-
//
|
|
254
|
+
// hostContext is delivered inside the ui/initialize response, where the
|
|
255
|
+
// bridge defaults it to {}; recording that same normalized value keeps the
|
|
256
|
+
// first later change from repeating what the widget already holds.
|
|
257
|
+
lastSentHostContextRef.current = current.hostContext ?? {};
|
|
200
258
|
|
|
201
259
|
return {
|
|
202
260
|
onMessage: bridge.onMessage,
|
|
@@ -241,6 +299,7 @@ export function McpAppFrame({
|
|
|
241
299
|
pendingHostContextRef,
|
|
242
300
|
lastSentHostContextRef,
|
|
243
301
|
(b, v) => b.notifyHostContextChanged(v),
|
|
302
|
+
isSameHostContext,
|
|
244
303
|
);
|
|
245
304
|
|
|
246
305
|
return (
|
package/src/mcp-apps/bridge.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SandboxHostFrame } from "../sandbox-host/SandboxHost";
|
|
2
|
-
import {
|
|
2
|
+
import { invokeUserCallback } from "@assistant-ui/core/internal";
|
|
3
3
|
import {
|
|
4
4
|
MCP_APP_PROTOCOL_VERSION,
|
|
5
5
|
type McpAppBridgeHandlers,
|
|
@@ -125,7 +125,12 @@ export function createMcpAppBridge(
|
|
|
125
125
|
};
|
|
126
126
|
|
|
127
127
|
const reportError = (error: Error) => {
|
|
128
|
-
|
|
128
|
+
invokeUserCallback(
|
|
129
|
+
"assistant-ui",
|
|
130
|
+
"MCP App onError",
|
|
131
|
+
handlers.onError?.bind(handlers),
|
|
132
|
+
error,
|
|
133
|
+
);
|
|
129
134
|
};
|
|
130
135
|
|
|
131
136
|
const handleRequest = async (req: McpAppJsonRpcRequest) => {
|
package/src/mcp-apps/index.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
McpAppRenderer,
|
|
3
|
+
type McpAppPartOptions,
|
|
4
|
+
type McpAppRendererOptions,
|
|
5
|
+
} from "./McpAppRenderer";
|
|
2
6
|
export { McpAppsRemoteHost } from "./McpAppsRemoteHost";
|
|
3
7
|
export { getMcpAppFromToolPart } from "./utils";
|
|
4
8
|
export type {
|
|
@@ -13,5 +17,6 @@ export type {
|
|
|
13
17
|
McpAppsHost,
|
|
14
18
|
McpAppsRemoteHostOptions,
|
|
15
19
|
McpAppToolCallParams,
|
|
20
|
+
McpAppBridgeHandlers,
|
|
16
21
|
ToolCallMessagePartMcpMetadata,
|
|
17
22
|
} from "./types";
|
|
@@ -27,7 +27,7 @@ describe("AssistantFrame Integration", () => {
|
|
|
27
27
|
parentHandler({
|
|
28
28
|
data,
|
|
29
29
|
source: iframeWindow,
|
|
30
|
-
origin:
|
|
30
|
+
origin: window.location.origin,
|
|
31
31
|
} as MessageEvent);
|
|
32
32
|
});
|
|
33
33
|
}
|
|
@@ -44,7 +44,7 @@ describe("AssistantFrame Integration", () => {
|
|
|
44
44
|
iframeHandler({
|
|
45
45
|
data,
|
|
46
46
|
source: parentWindow, // parent window is the source for subscription
|
|
47
|
-
origin:
|
|
47
|
+
origin: window.location.origin,
|
|
48
48
|
} as MessageEvent);
|
|
49
49
|
});
|
|
50
50
|
}
|
|
@@ -104,7 +104,7 @@ describe("AssistantFrame Integration", () => {
|
|
|
104
104
|
type: "model-context-request",
|
|
105
105
|
}),
|
|
106
106
|
}),
|
|
107
|
-
|
|
107
|
+
window.location.origin,
|
|
108
108
|
);
|
|
109
109
|
});
|
|
110
110
|
|
|
@@ -88,7 +88,7 @@ All messages are wrapped with a channel identifier to avoid conflicts with other
|
|
|
88
88
|
|
|
89
89
|
#### Security Considerations
|
|
90
90
|
|
|
91
|
-
1. **Origin Validation**: Both sides can specify `targetOrigin`
|
|
91
|
+
1. **Origin Validation**: Both sides default to the current page's origin and can specify an exact `targetOrigin` for cross-origin frames; providers share a fail-closed origin policy
|
|
92
92
|
2. **Window Reference**: Host (parent) only accepts messages from the specific iframe window it's connected to
|
|
93
93
|
3. **Message Channel**: Using a unique channel identifier prevents cross-talk with other postMessage users
|
|
94
94
|
|
|
@@ -20,7 +20,7 @@ type UseAssistantFrameHostOptions = {
|
|
|
20
20
|
*
|
|
21
21
|
* useAssistantFrameHost({
|
|
22
22
|
* iframeRef,
|
|
23
|
-
* targetOrigin: "https://trusted-domain.com",
|
|
23
|
+
* targetOrigin: "https://trusted-domain.com",
|
|
24
24
|
* });
|
|
25
25
|
*
|
|
26
26
|
* return <iframe ref={iframeRef} src="..." />;
|
|
@@ -29,7 +29,7 @@ type UseAssistantFrameHostOptions = {
|
|
|
29
29
|
*/
|
|
30
30
|
export const useAssistantFrameHost = ({
|
|
31
31
|
iframeRef,
|
|
32
|
-
targetOrigin
|
|
32
|
+
targetOrigin,
|
|
33
33
|
register,
|
|
34
34
|
}: UseAssistantFrameHostOptions): void => {
|
|
35
35
|
useEffect(() => {
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
useContext,
|
|
13
13
|
} from "react";
|
|
14
14
|
import { useAui } from "@assistant-ui/store";
|
|
15
|
-
import { useComposedRefs } from "
|
|
15
|
+
import { useComposedRefs } from "radix-ui/internal";
|
|
16
16
|
import { tool } from "@assistant-ui/core";
|
|
17
17
|
|
|
18
18
|
const click = tool({
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { forwardRef } from "react";
|
|
4
4
|
import type { ActionButtonProps } from "../../utils/createActionButton";
|
|
5
|
-
import { composeEventHandlers } from "
|
|
5
|
+
import { composeEventHandlers } from "radix-ui/internal";
|
|
6
6
|
import { Primitive } from "../../utils/Primitive";
|
|
7
7
|
import { useActionBarCopy } from "@assistant-ui/core/react";
|
|
8
8
|
import { useAuiState } from "@assistant-ui/store";
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { forwardRef, useCallback } from "react";
|
|
4
4
|
import type { ActionButtonProps } from "../../utils/createActionButton";
|
|
5
|
-
import { composeEventHandlers } from "
|
|
5
|
+
import { composeEventHandlers } from "radix-ui/internal";
|
|
6
6
|
import { Primitive } from "../../utils/Primitive";
|
|
7
7
|
import { useAuiState, useAui } from "@assistant-ui/store";
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { forwardRef } from "react";
|
|
4
4
|
import type { ActionButtonProps } from "../../utils/createActionButton";
|
|
5
|
-
import { composeEventHandlers } from "
|
|
5
|
+
import { composeEventHandlers } from "radix-ui/internal";
|
|
6
6
|
import { Primitive } from "../../utils/Primitive";
|
|
7
7
|
import { useAuiState } from "@assistant-ui/store";
|
|
8
8
|
import { useActionBarFeedbackNegative as useActionBarFeedbackNegativeBehavior } from "@assistant-ui/core/react";
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { forwardRef } from "react";
|
|
4
4
|
import type { ActionButtonProps } from "../../utils/createActionButton";
|
|
5
|
-
import { composeEventHandlers } from "
|
|
5
|
+
import { composeEventHandlers } from "radix-ui/internal";
|
|
6
6
|
import { useAuiState } from "@assistant-ui/store";
|
|
7
7
|
import { Primitive } from "../../utils/Primitive";
|
|
8
8
|
import { useActionBarFeedbackPositive as useActionBarFeedbackPositiveBehavior } from "@assistant-ui/core/react";
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { forwardRef } from "react";
|
|
4
4
|
import type { ActionButtonProps } from "../../utils/createActionButton";
|
|
5
5
|
import { Primitive } from "../../utils/Primitive";
|
|
6
|
-
import { composeEventHandlers } from "
|
|
6
|
+
import { composeEventHandlers } from "radix-ui/internal";
|
|
7
7
|
import { useActionBarStopSpeaking as useActionBarStopSpeakingBehavior } from "@assistant-ui/core/react";
|
|
8
8
|
|
|
9
9
|
const useActionBarStopSpeaking = () => {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
|
|
2
|
-
import type {
|
|
2
|
+
import type { Context } from "radix-ui/internal";
|
|
3
3
|
|
|
4
4
|
export const useDropdownMenuScope: ReturnType<
|
|
5
5
|
typeof DropdownMenuPrimitive.createDropdownMenuScope
|
|
6
6
|
> = DropdownMenuPrimitive.createDropdownMenuScope();
|
|
7
7
|
|
|
8
|
-
export type ScopedProps<P> = P & { __scopeActionBarMore?: Scope };
|
|
8
|
+
export type ScopedProps<P> = P & { __scopeActionBarMore?: Context.Scope };
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
forwardRef,
|
|
7
7
|
} from "react";
|
|
8
8
|
import { Popover as PopoverPrimitive } from "radix-ui";
|
|
9
|
-
import { composeEventHandlers } from "
|
|
9
|
+
import { composeEventHandlers } from "radix-ui/internal";
|
|
10
10
|
import type { WithRenderPropProps } from "../../utils/Primitive";
|
|
11
11
|
import { PopoverRenderContent } from "./popoverRenderPrimitives";
|
|
12
12
|
import { type ScopedProps, usePopoverScope } from "./scope";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Popover as PopoverPrimitive } from "radix-ui";
|
|
2
|
-
import type {
|
|
2
|
+
import type { Context } from "radix-ui/internal";
|
|
3
3
|
|
|
4
4
|
export const usePopoverScope: ReturnType<
|
|
5
5
|
typeof PopoverPrimitive.createPopoverScope
|
|
6
6
|
> = PopoverPrimitive.createPopoverScope();
|
|
7
|
-
export type ScopedProps<P> = P & { __scopeAssistantModal?: Scope };
|
|
7
|
+
export type ScopedProps<P> = P & { __scopeAssistantModal?: Context.Scope };
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
isValidElement,
|
|
10
10
|
} from "react";
|
|
11
11
|
|
|
12
|
-
import { composeEventHandlers } from "
|
|
12
|
+
import { composeEventHandlers } from "radix-ui/internal";
|
|
13
13
|
import { Slot } from "radix-ui";
|
|
14
14
|
import type React from "react";
|
|
15
15
|
import { useAui } from "@assistant-ui/store";
|