@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,132 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { act, renderHook } from "@testing-library/react";
|
|
4
|
+
import type { AssistantClient } from "@assistant-ui/store";
|
|
5
|
+
import { describe, expect, it, vi } from "vitest";
|
|
6
|
+
import {
|
|
7
|
+
shallowEqualRecords,
|
|
8
|
+
useModelContextSnapshot,
|
|
9
|
+
type ModelContextSnapshotSource,
|
|
10
|
+
} from "./useModelContextSnapshot";
|
|
11
|
+
|
|
12
|
+
const EMPTY: Readonly<Record<string, string>> = Object.freeze({});
|
|
13
|
+
|
|
14
|
+
const createSource = (
|
|
15
|
+
overrides: Partial<
|
|
16
|
+
ModelContextSnapshotSource<Readonly<Record<string, string>>>
|
|
17
|
+
> = {},
|
|
18
|
+
) => {
|
|
19
|
+
const listeners = new Set<() => void>();
|
|
20
|
+
const state = { tools: EMPTY as Record<string, string> };
|
|
21
|
+
const unsubscribe = vi.fn();
|
|
22
|
+
const source: ModelContextSnapshotSource<Readonly<Record<string, string>>> = {
|
|
23
|
+
empty: EMPTY,
|
|
24
|
+
read: vi.fn(() => ({ ...state.tools })),
|
|
25
|
+
subscribe: vi.fn((_aui, onChange) => {
|
|
26
|
+
listeners.add(onChange);
|
|
27
|
+
return () => {
|
|
28
|
+
listeners.delete(onChange);
|
|
29
|
+
unsubscribe();
|
|
30
|
+
};
|
|
31
|
+
}),
|
|
32
|
+
isEqual: shallowEqualRecords,
|
|
33
|
+
...overrides,
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
source,
|
|
37
|
+
state,
|
|
38
|
+
unsubscribe,
|
|
39
|
+
notify: () => listeners.forEach((listener) => listener()),
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const aui = {} as AssistantClient;
|
|
44
|
+
|
|
45
|
+
describe("useModelContextSnapshot", () => {
|
|
46
|
+
it("seeds from the source on the first render when enabled", () => {
|
|
47
|
+
const { source } = createSource();
|
|
48
|
+
(source.read as ReturnType<typeof vi.fn>).mockReturnValue({ a: "1" });
|
|
49
|
+
|
|
50
|
+
const { result } = renderHook(() =>
|
|
51
|
+
useModelContextSnapshot(aui, true, source),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
expect(result.current).toEqual({ a: "1" });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("neither reads nor subscribes while disabled", () => {
|
|
58
|
+
const { source } = createSource();
|
|
59
|
+
|
|
60
|
+
const { result } = renderHook(() =>
|
|
61
|
+
useModelContextSnapshot(aui, false, source),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
expect(result.current).toBe(source.empty);
|
|
65
|
+
expect(source.read).not.toHaveBeenCalled();
|
|
66
|
+
expect(source.subscribe).not.toHaveBeenCalled();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("keeps the previous reference when isEqual reports no change", () => {
|
|
70
|
+
const { source, state, notify } = createSource();
|
|
71
|
+
state.tools = { a: "1" };
|
|
72
|
+
|
|
73
|
+
const { result } = renderHook(() =>
|
|
74
|
+
useModelContextSnapshot(aui, true, source),
|
|
75
|
+
);
|
|
76
|
+
const first = result.current;
|
|
77
|
+
|
|
78
|
+
act(() => {
|
|
79
|
+
state.tools = { a: "1" };
|
|
80
|
+
notify();
|
|
81
|
+
});
|
|
82
|
+
expect(result.current).toBe(first);
|
|
83
|
+
|
|
84
|
+
act(() => {
|
|
85
|
+
state.tools = { a: "2" };
|
|
86
|
+
notify();
|
|
87
|
+
});
|
|
88
|
+
expect(result.current).not.toBe(first);
|
|
89
|
+
expect(result.current).toEqual({ a: "2" });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("takes every notification when the source omits isEqual", () => {
|
|
93
|
+
const { source, state, notify } = createSource({ isEqual: undefined });
|
|
94
|
+
state.tools = { a: "1" };
|
|
95
|
+
|
|
96
|
+
const { result } = renderHook(() =>
|
|
97
|
+
useModelContextSnapshot(aui, true, source),
|
|
98
|
+
);
|
|
99
|
+
const first = result.current;
|
|
100
|
+
|
|
101
|
+
act(() => notify());
|
|
102
|
+
|
|
103
|
+
expect(result.current).not.toBe(first);
|
|
104
|
+
expect(result.current).toEqual({ a: "1" });
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("unsubscribes on unmount", () => {
|
|
108
|
+
const { source, unsubscribe } = createSource();
|
|
109
|
+
const { unmount } = renderHook(() =>
|
|
110
|
+
useModelContextSnapshot(aui, true, source),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
unmount();
|
|
114
|
+
|
|
115
|
+
expect(unsubscribe).toHaveBeenCalled();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("shallowEqualRecords", () => {
|
|
120
|
+
it("compares own keys by identity", () => {
|
|
121
|
+
expect(shallowEqualRecords({ a: 1 }, { a: 1 })).toBe(true);
|
|
122
|
+
expect(shallowEqualRecords({ a: 1 }, { a: 2 })).toBe(false);
|
|
123
|
+
expect(shallowEqualRecords({ a: 1 }, { a: 1, b: 2 })).toBe(false);
|
|
124
|
+
expect(shallowEqualRecords({ a: undefined }, { b: undefined })).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("does not match a key inherited from Object.prototype", () => {
|
|
128
|
+
expect(
|
|
129
|
+
shallowEqualRecords({ toString: Object.prototype.toString }, { a: 1 }),
|
|
130
|
+
).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import type { AssistantClient } from "@assistant-ui/store";
|
|
5
|
+
import type { Unsubscribe } from "@assistant-ui/core";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Where a snapshot comes from and what counts as a change to it. Declared once
|
|
9
|
+
* per consumer at module scope: the subscription effect keys on this object, so
|
|
10
|
+
* an inline source without an `isEqual` re-reads itself into a render loop.
|
|
11
|
+
*/
|
|
12
|
+
export type ModelContextSnapshotSource<T> = {
|
|
13
|
+
/** Seeds the snapshot when it starts disabled. */
|
|
14
|
+
readonly empty: T;
|
|
15
|
+
/** Projects the fields the consumer observes out of the live model context. */
|
|
16
|
+
read(aui: AssistantClient): T;
|
|
17
|
+
subscribe(aui: AssistantClient, onChange: () => void): Unsubscribe;
|
|
18
|
+
/**
|
|
19
|
+
* An equal pair keeps the previous reference. Omitted by a consumer that
|
|
20
|
+
* re-derives on every notification, where a caller republishing an unchanged
|
|
21
|
+
* tool set is how it asks to be re-run.
|
|
22
|
+
*/
|
|
23
|
+
isEqual?(previous: T, next: T): boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* `getModelContext()` rebuilds its result on every call, so a projection of the
|
|
28
|
+
* fields a consumer observes is held in state and refreshed on notification
|
|
29
|
+
* rather than read during render. Keeping the previous reference for an equal
|
|
30
|
+
* projection is what stops a notification carrying no observable change from
|
|
31
|
+
* invalidating everything derived from it.
|
|
32
|
+
*/
|
|
33
|
+
export const useModelContextSnapshot = <T>(
|
|
34
|
+
aui: AssistantClient,
|
|
35
|
+
enabled: boolean,
|
|
36
|
+
source: ModelContextSnapshotSource<T>,
|
|
37
|
+
): T => {
|
|
38
|
+
const [snapshot, setSnapshot] = useState(() =>
|
|
39
|
+
enabled ? source.read(aui) : source.empty,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!enabled) return undefined;
|
|
44
|
+
const read = () => {
|
|
45
|
+
const next = source.read(aui);
|
|
46
|
+
setSnapshot((previous) =>
|
|
47
|
+
source.isEqual?.(previous, next) ? previous : next,
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
read();
|
|
51
|
+
return source.subscribe(aui, read);
|
|
52
|
+
}, [aui, enabled, source]);
|
|
53
|
+
|
|
54
|
+
return snapshot;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Shallow own-key comparison, for a projection whose values are records. */
|
|
58
|
+
export const shallowEqualRecords = (
|
|
59
|
+
previous: Readonly<Record<string, unknown>>,
|
|
60
|
+
next: Readonly<Record<string, unknown>>,
|
|
61
|
+
): boolean => {
|
|
62
|
+
const keys = Object.keys(previous);
|
|
63
|
+
if (keys.length !== Object.keys(next).length) return false;
|
|
64
|
+
return keys.every(
|
|
65
|
+
(key) => Object.hasOwn(next, key) && previous[key] === next[key],
|
|
66
|
+
);
|
|
67
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { act, renderHook } from "@testing-library/react";
|
|
4
|
+
import { startTransition, Suspense, type PropsWithChildren } from "react";
|
|
5
|
+
import { describe, expect, it, vi } from "vitest";
|
|
6
|
+
import {
|
|
7
|
+
unstable_useSlashCommandAdapter,
|
|
8
|
+
type Unstable_SlashCommand,
|
|
9
|
+
} from "./useSlashCommandAdapter";
|
|
10
|
+
|
|
11
|
+
const command = (
|
|
12
|
+
label: string,
|
|
13
|
+
execute: () => void,
|
|
14
|
+
): Unstable_SlashCommand => ({
|
|
15
|
+
id: "run",
|
|
16
|
+
label,
|
|
17
|
+
execute,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("unstable_useSlashCommandAdapter", () => {
|
|
21
|
+
it("keeps commands scoped to committed renders", () => {
|
|
22
|
+
const executeA = vi.fn();
|
|
23
|
+
const executeB = vi.fn();
|
|
24
|
+
const commandsA = [command("Workspace A", executeA)];
|
|
25
|
+
const commandsB = [command("Workspace B", executeB)];
|
|
26
|
+
const renderedLabel = vi.fn();
|
|
27
|
+
const interruptedRender = vi.fn();
|
|
28
|
+
const pending = new Promise<never>(() => {});
|
|
29
|
+
let blocked = false;
|
|
30
|
+
|
|
31
|
+
const Blocker = () => {
|
|
32
|
+
if (blocked) {
|
|
33
|
+
interruptedRender();
|
|
34
|
+
throw pending;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
};
|
|
38
|
+
const Wrapper = ({ children }: PropsWithChildren) => (
|
|
39
|
+
<Suspense fallback={null}>
|
|
40
|
+
{children}
|
|
41
|
+
<Blocker />
|
|
42
|
+
</Suspense>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const { result, rerender } = renderHook(
|
|
46
|
+
({ commands }) => {
|
|
47
|
+
renderedLabel(commands[0]?.label);
|
|
48
|
+
return unstable_useSlashCommandAdapter({ commands });
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
initialProps: { commands: commandsA },
|
|
52
|
+
wrapper: Wrapper,
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
act(() => {
|
|
57
|
+
blocked = true;
|
|
58
|
+
startTransition(() => rerender({ commands: commandsB }));
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(interruptedRender).toHaveBeenCalled();
|
|
62
|
+
expect(renderedLabel).toHaveBeenCalledWith("Workspace B");
|
|
63
|
+
expect(result.current.adapter.search?.("")).toEqual([
|
|
64
|
+
{ id: "run", type: "command", label: "Workspace A" },
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
result.current.action.onExecute({
|
|
68
|
+
id: "run",
|
|
69
|
+
type: "command",
|
|
70
|
+
label: "Workspace A",
|
|
71
|
+
});
|
|
72
|
+
expect(executeA).toHaveBeenCalledOnce();
|
|
73
|
+
expect(executeB).not.toHaveBeenCalled();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("updates search while keeping actions stable after commands commit", () => {
|
|
77
|
+
const executeA = vi.fn();
|
|
78
|
+
const executeB = vi.fn();
|
|
79
|
+
const commandsA = [command("Workspace A", executeA)];
|
|
80
|
+
const commandsB = [command("Workspace B", executeB)];
|
|
81
|
+
const { result, rerender } = renderHook(
|
|
82
|
+
({ commands }) => unstable_useSlashCommandAdapter({ commands }),
|
|
83
|
+
{ initialProps: { commands: commandsA } },
|
|
84
|
+
);
|
|
85
|
+
const initial = result.current;
|
|
86
|
+
|
|
87
|
+
rerender({ commands: commandsB });
|
|
88
|
+
|
|
89
|
+
expect(result.current.adapter).not.toBe(initial.adapter);
|
|
90
|
+
expect(result.current.action).toBe(initial.action);
|
|
91
|
+
expect(result.current.adapter.search?.("")).toEqual([
|
|
92
|
+
{ id: "run", type: "command", label: "Workspace B" },
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
result.current.action.onExecute({
|
|
96
|
+
id: "run",
|
|
97
|
+
type: "command",
|
|
98
|
+
label: "Workspace B",
|
|
99
|
+
});
|
|
100
|
+
expect(executeA).not.toHaveBeenCalled();
|
|
101
|
+
expect(executeB).toHaveBeenCalledOnce();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("matches the displayed label for a command with no explicit label", () => {
|
|
105
|
+
const { result } = renderHook(() =>
|
|
106
|
+
unstable_useSlashCommandAdapter({
|
|
107
|
+
commands: [{ id: "summarize", execute: vi.fn() }],
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
expect(result.current.adapter.search?.("/")).toEqual([
|
|
112
|
+
{ id: "summarize", type: "command", label: "/summarize" },
|
|
113
|
+
]);
|
|
114
|
+
expect(result.current.adapter.search?.("sum")).toEqual([
|
|
115
|
+
{ id: "summarize", type: "command", label: "/summarize" },
|
|
116
|
+
]);
|
|
117
|
+
expect(result.current.adapter.search?.("nope")).toEqual([]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("keeps the adapter stable for equivalent inline commands", () => {
|
|
121
|
+
const executeA = vi.fn();
|
|
122
|
+
const executeB = vi.fn();
|
|
123
|
+
const { result, rerender } = renderHook(
|
|
124
|
+
({ execute }) =>
|
|
125
|
+
unstable_useSlashCommandAdapter({
|
|
126
|
+
commands: [{ id: "run", label: "Run", execute }],
|
|
127
|
+
}),
|
|
128
|
+
{ initialProps: { execute: executeA } },
|
|
129
|
+
);
|
|
130
|
+
const initialAdapter = result.current.adapter;
|
|
131
|
+
|
|
132
|
+
rerender({ execute: executeB });
|
|
133
|
+
|
|
134
|
+
expect(result.current.adapter).toBe(initialAdapter);
|
|
135
|
+
act(() =>
|
|
136
|
+
result.current.action.onExecute(result.current.adapter.search!("")[0]!),
|
|
137
|
+
);
|
|
138
|
+
expect(executeA).not.toHaveBeenCalled();
|
|
139
|
+
expect(executeB).toHaveBeenCalledOnce();
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useMemo, useRef } from "react";
|
|
3
|
+
import { useLayoutEffect, useMemo, useRef } from "react";
|
|
4
4
|
import type {
|
|
5
5
|
Unstable_TriggerAdapter,
|
|
6
6
|
Unstable_TriggerItem,
|
|
7
7
|
} from "@assistant-ui/core";
|
|
8
8
|
import type { Unstable_IconComponent } from "./useMentionAdapter";
|
|
9
|
+
import { matchesTriggerItemQuery } from "../primitives/composer/trigger/matchesTriggerItemQuery";
|
|
9
10
|
|
|
10
11
|
export type Unstable_SlashCommand = {
|
|
11
12
|
readonly id: string;
|
|
@@ -61,34 +62,51 @@ export function unstable_useSlashCommandAdapter(
|
|
|
61
62
|
const { commands, removeOnExecute } = options;
|
|
62
63
|
|
|
63
64
|
const commandsRef = useRef(commands);
|
|
64
|
-
|
|
65
|
+
const committedItemsRef = useRef<readonly Unstable_TriggerItem[]>(undefined);
|
|
66
|
+
const nextItems = commands.map(toItem);
|
|
67
|
+
// A referential cache, never state: `items` is always structurally equal to
|
|
68
|
+
// `nextItems`, so the render-time read cannot change what the adapter shows.
|
|
69
|
+
const items = areTriggerItemsEqual(committedItemsRef.current, nextItems)
|
|
70
|
+
? committedItemsRef.current
|
|
71
|
+
: nextItems;
|
|
65
72
|
|
|
66
|
-
|
|
67
|
-
|
|
73
|
+
// The adapter's callbacks must only observe commands from committed renders.
|
|
74
|
+
useLayoutEffect(() => {
|
|
75
|
+
commandsRef.current = commands;
|
|
76
|
+
committedItemsRef.current = items;
|
|
77
|
+
}, [commands, items]);
|
|
78
|
+
|
|
79
|
+
const adapter = useMemo<Unstable_TriggerAdapter>(
|
|
80
|
+
() => ({
|
|
68
81
|
categories: () => [],
|
|
69
82
|
categoryItems: () => [],
|
|
70
83
|
search: (query: string) => {
|
|
71
84
|
const lower = query.toLowerCase();
|
|
72
|
-
return
|
|
73
|
-
.filter((c) => matchesQuery(c, lower))
|
|
74
|
-
.map(toItem);
|
|
85
|
+
return items.filter((item) => matchesTriggerItemQuery(item, lower));
|
|
75
86
|
},
|
|
76
|
-
}
|
|
87
|
+
}),
|
|
88
|
+
[items],
|
|
89
|
+
);
|
|
77
90
|
|
|
78
|
-
|
|
91
|
+
const action = useMemo<Unstable_SlashCommandAction>(
|
|
92
|
+
() => ({
|
|
79
93
|
onExecute: (item) => {
|
|
80
94
|
commandsRef.current.find((c) => c.id === item.id)?.execute();
|
|
81
95
|
},
|
|
82
96
|
...(removeOnExecute !== undefined ? { removeOnExecute } : {}),
|
|
83
|
-
}
|
|
97
|
+
}),
|
|
98
|
+
[removeOnExecute],
|
|
99
|
+
);
|
|
84
100
|
|
|
85
|
-
|
|
101
|
+
return useMemo(
|
|
102
|
+
() => ({
|
|
86
103
|
adapter,
|
|
87
104
|
action,
|
|
88
105
|
...(options.iconMap ? { iconMap: options.iconMap } : {}),
|
|
89
106
|
...(options.fallbackIcon ? { fallbackIcon: options.fallbackIcon } : {}),
|
|
90
|
-
}
|
|
91
|
-
|
|
107
|
+
}),
|
|
108
|
+
[adapter, action, options.iconMap, options.fallbackIcon],
|
|
109
|
+
);
|
|
92
110
|
}
|
|
93
111
|
|
|
94
112
|
function toItem(cmd: Unstable_SlashCommand): Unstable_TriggerItem {
|
|
@@ -101,10 +119,20 @@ function toItem(cmd: Unstable_SlashCommand): Unstable_TriggerItem {
|
|
|
101
119
|
};
|
|
102
120
|
}
|
|
103
121
|
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (
|
|
109
|
-
|
|
122
|
+
function areTriggerItemsEqual(
|
|
123
|
+
previous: readonly Unstable_TriggerItem[] | undefined,
|
|
124
|
+
next: readonly Unstable_TriggerItem[],
|
|
125
|
+
): previous is readonly Unstable_TriggerItem[] {
|
|
126
|
+
if (!previous || previous.length !== next.length) return false;
|
|
127
|
+
|
|
128
|
+
return previous.every((item, index) => {
|
|
129
|
+
const nextItem = next[index];
|
|
130
|
+
return (
|
|
131
|
+
nextItem !== undefined &&
|
|
132
|
+
item.id === nextItem.id &&
|
|
133
|
+
item.label === nextItem.label &&
|
|
134
|
+
item.description === nextItem.description &&
|
|
135
|
+
item.metadata?.icon === nextItem.metadata?.icon
|
|
136
|
+
);
|
|
137
|
+
});
|
|
110
138
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { resource } from "@assistant-ui/tap";
|
|
3
|
+
import type { Tool } from "assistant-stream";
|
|
4
|
+
import { toWebMcpTool } from "./convertTools";
|
|
5
|
+
import type { WebMcpHost } from "./webmcp-host";
|
|
6
|
+
|
|
7
|
+
export type WebMcpRegistrationProps = {
|
|
8
|
+
host: WebMcpHost;
|
|
9
|
+
name: string;
|
|
10
|
+
/** Re-registers when it changes; an execute-only edit reads through instead. */
|
|
11
|
+
signature: string;
|
|
12
|
+
tool: Tool<any, any>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// The explainer specifies NotAllowedError for a page whose tools permission is
|
|
16
|
+
// off, which a cross-origin iframe without allow="tools" reaches routinely. It
|
|
17
|
+
// documents no rejection for a name already taken, so that stays a hedge.
|
|
18
|
+
const notPermitted = (error: unknown) =>
|
|
19
|
+
(error as { name?: unknown } | null | undefined)?.name === "NotAllowedError";
|
|
20
|
+
|
|
21
|
+
const notPermittedMessage = (name: string) =>
|
|
22
|
+
`[assistant-ui] WebMCP registration for tool "${name}" was not permitted; the page's tools permission is disabled.`;
|
|
23
|
+
|
|
24
|
+
const useWebMcpRegistration = ({
|
|
25
|
+
host,
|
|
26
|
+
name,
|
|
27
|
+
signature,
|
|
28
|
+
tool,
|
|
29
|
+
}: WebMcpRegistrationProps): string | null => {
|
|
30
|
+
// A refused name is remembered for as long as the tool stays in the model
|
|
31
|
+
// context, so a permanent collision warns once rather than on every sync.
|
|
32
|
+
const [refused, setRefused] = useState(false);
|
|
33
|
+
const toolRef = useRef(tool);
|
|
34
|
+
// Commit phase, so an abandoned render cannot hand the host a tool the tree
|
|
35
|
+
// never committed.
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
toolRef.current = tool;
|
|
38
|
+
}, [tool]);
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (refused) return undefined;
|
|
42
|
+
|
|
43
|
+
const lifecycle = new AbortController();
|
|
44
|
+
let live = true;
|
|
45
|
+
let dispose: (() => void) | undefined;
|
|
46
|
+
|
|
47
|
+
const refuse = (message: string, error: unknown) => {
|
|
48
|
+
if (!live) return;
|
|
49
|
+
live = false;
|
|
50
|
+
setRefused(true);
|
|
51
|
+
console.warn(message, error);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
dispose = host.registerTool(
|
|
56
|
+
toWebMcpTool(name, () => toolRef.current, lifecycle.signal),
|
|
57
|
+
(error) =>
|
|
58
|
+
refuse(
|
|
59
|
+
notPermitted(error)
|
|
60
|
+
? notPermittedMessage(name)
|
|
61
|
+
: `[assistant-ui] WebMCP registration for tool "${name}" failed (name may already be registered).`,
|
|
62
|
+
error,
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
refuse(
|
|
67
|
+
notPermitted(error)
|
|
68
|
+
? notPermittedMessage(name)
|
|
69
|
+
: `[assistant-ui] Skipping WebMCP registration for tool "${name}": registerTool failed (name may already be registered).`,
|
|
70
|
+
error,
|
|
71
|
+
);
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return () => {
|
|
76
|
+
live = false;
|
|
77
|
+
lifecycle.abort();
|
|
78
|
+
try {
|
|
79
|
+
dispose();
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.warn(
|
|
82
|
+
`[assistant-ui] Unregistering WebMCP tool "${name}" failed.`,
|
|
83
|
+
error,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}, [host, name, signature, refused]);
|
|
88
|
+
|
|
89
|
+
return refused ? null : name;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const WebMcpRegistrationResource = resource(useWebMcpRegistration);
|