@assistant-ui/react 0.15.17 → 0.15.19
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 +8 -2
- package/dist/context/providers/ThreadViewportProvider.js +2 -2
- package/dist/context/providers/ThreadViewportProvider.js.map +1 -1
- package/dist/hooks/useToolCallElapsed.js.map +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +5 -3
- package/dist/mcp-apps/McpAppRenderer.d.ts +30 -8
- package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppRenderer.js +91 -47
- package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/mcp-apps/app-frame.d.ts.map +1 -1
- package/dist/mcp-apps/app-frame.js +57 -33
- package/dist/mcp-apps/app-frame.js.map +1 -1
- package/dist/mcp-apps/bridge.js +9 -4
- package/dist/mcp-apps/bridge.js.map +1 -1
- package/dist/mcp-apps/index.d.ts +2 -2
- package/dist/primitives/assistantModal/AssistantModalRoot.d.ts.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.js +47 -79
- package/dist/primitives/assistantModal/AssistantModalRoot.js.map +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.js +61 -138
- package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +3 -8
- 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/trigger/TriggerPopoverAction.js +46 -15
- package/dist/primitives/composer/trigger/TriggerPopoverAction.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/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/triggerKeyboardResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerKeyboardResource.js +53 -61
- package/dist/primitives/composer/trigger/triggerKeyboardResource.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerNavigationResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerNavigationResource.js +129 -141
- package/dist/primitives/composer/trigger/triggerNavigationResource.js.map +1 -1
- package/dist/primitives/message/MessageParts.js +2 -14
- package/dist/primitives/message/MessageParts.js.map +1 -1
- package/dist/primitives/message/MessagePartsGrouped.d.ts +3 -3
- package/dist/primitives/message/MessagePartsGrouped.js +2 -2
- package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
- package/dist/primitives/messagePart/MessagePartText.d.ts +2 -0
- package/dist/primitives/messagePart/MessagePartText.d.ts.map +1 -1
- package/dist/primitives/messagePart/MessagePartText.js +13 -36
- package/dist/primitives/messagePart/MessagePartText.js.map +1 -1
- package/dist/primitives/reasoning/useScrollLock.js +3 -1
- package/dist/primitives/reasoning/useScrollLock.js.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +3 -3
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/dist/sandbox-host/SandboxHost.d.ts.map +1 -1
- package/dist/sandbox-host/SandboxHost.js +36 -9
- package/dist/sandbox-host/SandboxHost.js.map +1 -1
- package/dist/unstable/useLiveCompletionAdapter.js +24 -2
- 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/useMessageStallDetection.d.ts +4 -5
- package/dist/unstable/useMessageStallDetection.d.ts.map +1 -1
- package/dist/unstable/useMessageStallDetection.js +16 -15
- package/dist/unstable/useMessageStallDetection.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 +2 -8
- 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 +161 -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 +106 -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 +10 -2
- package/dist/utils/Primitive.d.ts.map +1 -1
- package/dist/utils/Primitive.js +25 -6
- package/dist/utils/Primitive.js.map +1 -1
- package/dist/utils/getSelectionMessageId.d.ts.map +1 -1
- package/dist/utils/getSelectionMessageId.js +17 -3
- package/dist/utils/getSelectionMessageId.js.map +1 -1
- package/package.json +13 -13
- package/src/context/providers/ThreadViewportProvider.tsx +2 -2
- package/src/hooks/useToolCallElapsed.ts +3 -0
- package/src/index.ts +14 -1
- package/src/mcp-apps/McpAppRenderer.test.tsx +253 -5
- package/src/mcp-apps/McpAppRenderer.tsx +147 -52
- package/src/mcp-apps/app-frame.test.tsx +437 -1
- package/src/mcp-apps/app-frame.tsx +76 -21
- package/src/mcp-apps/bridge.test.ts +98 -0
- package/src/mcp-apps/bridge.ts +14 -3
- package/src/mcp-apps/index.ts +5 -1
- package/src/primitives/assistantModal/AssistantModalRoot.test.tsx +123 -0
- package/src/primitives/assistantModal/AssistantModalRoot.tsx +11 -31
- package/src/primitives/assistantModal/AssistantModalTrigger.test.tsx +1 -16
- package/src/primitives/composer/ComposerAttachmentDropzone.test.tsx +43 -0
- package/src/primitives/composer/ComposerAttachmentDropzone.tsx +2 -10
- package/src/primitives/composer/ComposerInput.test.tsx +22 -0
- package/src/primitives/composer/ComposerInput.tsx +5 -9
- package/src/primitives/composer/ComposerInputPluginContext.tsx +0 -10
- package/src/primitives/composer/trigger/TriggerPopoverAction.tsx +4 -2
- package/src/primitives/composer/trigger/TriggerPopoverBehavior.test.tsx +109 -0
- package/src/primitives/composer/trigger/TriggerPopoverDirective.tsx +4 -2
- package/src/primitives/composer/trigger/matchesTriggerItemQuery.test.ts +52 -0
- package/src/primitives/composer/trigger/matchesTriggerItemQuery.ts +13 -0
- package/src/primitives/composer/trigger/triggerKeyboardResource.ts +13 -7
- package/src/primitives/composer/trigger/triggerNavigationResource.ts +8 -13
- package/src/primitives/message/MessageParts.tsx +1 -21
- package/src/primitives/message/MessagePartsGrouped.test.tsx +93 -0
- package/src/primitives/message/MessagePartsGrouped.tsx +6 -6
- package/src/primitives/messagePart/MessagePartText.test.tsx +69 -0
- package/src/primitives/messagePart/MessagePartText.tsx +22 -9
- package/src/primitives/reasoning/useScrollLock.test.tsx +129 -0
- package/src/primitives/reasoning/useScrollLock.ts +15 -1
- package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +38 -1
- package/src/primitives/thread/useThreadViewportAutoScroll.ts +7 -6
- package/src/sandbox-host/SandboxHost.test.tsx +160 -2
- package/src/sandbox-host/SandboxHost.tsx +50 -10
- package/src/tests/MessageParts.rendererOptions.test.tsx +117 -0
- package/src/tests/toolCallTiming.test.tsx +55 -1
- package/src/unstable/useComposerInputHistory.test.tsx +2 -1
- package/src/unstable/useLiveCompletionAdapter.test.tsx +202 -1
- package/src/unstable/useLiveCompletionAdapter.ts +38 -8
- package/src/unstable/useMentionAdapter.test.tsx +383 -0
- package/src/unstable/useMentionAdapter.ts +94 -57
- package/src/unstable/useMessageStallDetection.ts +26 -19
- package/src/unstable/useModelContextSnapshot.test.tsx +132 -0
- package/src/unstable/useModelContextSnapshot.ts +67 -0
- package/src/unstable/useSlashCommandAdapter.ts +2 -9
- 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 +553 -0
- package/src/unstable/webmcp/convertTools.ts +260 -0
- package/src/unstable/webmcp/useWebMcpProvider.test.tsx +466 -0
- package/src/unstable/webmcp/useWebMcpProvider.ts +148 -0
- package/src/unstable/webmcp/webmcp-host.test.ts +142 -0
- package/src/unstable/webmcp/webmcp-host.ts +83 -0
- package/src/utils/Primitive.test.tsx +46 -1
- package/src/utils/Primitive.tsx +37 -7
- package/src/utils/getSelectionMessageId.test.ts +176 -1
- package/src/utils/getSelectionMessageId.ts +21 -3
- 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 -19
- 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/utils/invokeCallbackSafely.ts +0 -11
- package/src/utils/useToolArgsFieldStatus.ts +0 -18
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { ComponentPropsWithoutRef, ComponentRef, ElementType, ForwardRefExoticComponent, ReactElement, RefAttributes } from "react";
|
|
1
|
+
import { ComponentProps, ComponentPropsWithoutRef, ComponentRef, ElementType, ForwardRefExoticComponent, ReactElement, ReactNode, RefAttributes } from "react";
|
|
2
2
|
import { Primitive as Primitive$1 } from "radix-ui/internal";
|
|
3
|
+
import { Slot } from "radix-ui";
|
|
3
4
|
//#region src/utils/Primitive.d.ts
|
|
4
5
|
/**
|
|
5
6
|
* Thin wrapper around Radix `Primitive` that adds `render` prop support.
|
|
@@ -16,6 +17,13 @@ type WithRenderPropProps<T extends ElementType> = ComponentPropsWithoutRef<T> &
|
|
|
16
17
|
render?: ReactElement | undefined;
|
|
17
18
|
};
|
|
18
19
|
type PrimitiveProps<E extends PrimitiveNode> = WithRenderPropProps<(typeof Primitive$1)[E]>;
|
|
20
|
+
/**
|
|
21
|
+
* Composes a `render` element at a call site that has already computed its props.
|
|
22
|
+
*
|
|
23
|
+
* `withRenderProp` wraps a component; this covers the case where the props are
|
|
24
|
+
* already in hand and the composition happens inline.
|
|
25
|
+
*/
|
|
26
|
+
declare function renderSlot(render: ReactElement, children: ReactNode, props: ComponentProps<typeof Slot.Root>): ReactElement;
|
|
19
27
|
declare function withRenderProp<T extends ElementType>(Component: T): ForwardRefExoticComponent<WithRenderPropProps<T> & RefAttributes<ComponentRef<T>>>;
|
|
20
28
|
declare const Primitive: {
|
|
21
29
|
a: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLAnchorElement> & import("react").AnchorHTMLAttributes<HTMLAnchorElement> & {
|
|
@@ -105,5 +113,5 @@ declare const Primitive: {
|
|
|
105
113
|
} & RefAttributes<HTMLUListElement>>;
|
|
106
114
|
};
|
|
107
115
|
//#endregion
|
|
108
|
-
export { Primitive, type PrimitiveProps, type WithRenderPropProps, withRenderProp };
|
|
116
|
+
export { Primitive, type PrimitiveProps, type WithRenderPropProps, renderSlot, withRenderProp };
|
|
109
117
|
//# sourceMappingURL=Primitive.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Primitive.d.ts","names":[],"sources":["../../src/utils/Primitive.tsx"],"mappings":"
|
|
1
|
+
{"version":3,"file":"Primitive.d.ts","names":[],"sources":["../../src/utils/Primitive.tsx"],"mappings":";;;;;;;;;;;;;cA4BM;KAmBD,wBAAwB;KAExB,oBAAoB,UAAU,eACjC,yBAAyB;EACvB,SAAS;;KAGR,eAAe,UAAU,iBAAiB,4BACrC,aAAgB;;;;;;;iBAoCjB,WACP,QAAQ,cACR,UAAU,WACV,OAAO,sBAAsB,KAAK,QACjC;iBAMM,eAAe,UAAU,aAAa,WAAW,IAmCtC,0BAChB,oBAAoB,KAAK,cAAc,aAAa;cAclD;;;;IApGO,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA;;;;;IAAA,SAAA"}
|
package/dist/utils/Primitive.js
CHANGED
|
@@ -2,6 +2,7 @@ import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
|
|
|
2
2
|
import { cloneElement, forwardRef, isValidElement } from "@assistant-ui/tap/react-shim";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
import { Primitive as Primitive$1 } from "radix-ui/internal";
|
|
5
|
+
import { Slot } from "radix-ui";
|
|
5
6
|
//#region src/utils/Primitive.tsx
|
|
6
7
|
/**
|
|
7
8
|
* Thin wrapper around Radix `Primitive` that adds `render` prop support.
|
|
@@ -31,6 +32,25 @@ const NODES = [
|
|
|
31
32
|
"svg",
|
|
32
33
|
"ul"
|
|
33
34
|
];
|
|
35
|
+
/**
|
|
36
|
+
* Composes the children of a `render` element. Outer children win when supplied;
|
|
37
|
+
* the render element's own children are the fallback.
|
|
38
|
+
*/
|
|
39
|
+
function composeRenderElement(render, children) {
|
|
40
|
+
return cloneElement(render, void 0, children !== void 0 ? children : render.props.children);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Composes a `render` element at a call site that has already computed its props.
|
|
44
|
+
*
|
|
45
|
+
* `withRenderProp` wraps a component; this covers the case where the props are
|
|
46
|
+
* already in hand and the composition happens inline.
|
|
47
|
+
*/
|
|
48
|
+
function renderSlot(render, children, props) {
|
|
49
|
+
return /* @__PURE__ */ jsx(Slot.Root, {
|
|
50
|
+
...props,
|
|
51
|
+
children: composeRenderElement(render, children)
|
|
52
|
+
});
|
|
53
|
+
}
|
|
34
54
|
function withRenderProp(Component) {
|
|
35
55
|
const Wrapped = forwardRef((t0, ref) => {
|
|
36
56
|
const $ = c(17);
|
|
@@ -53,13 +73,12 @@ function withRenderProp(Component) {
|
|
|
53
73
|
}
|
|
54
74
|
const Comp = Component;
|
|
55
75
|
if (render && isValidElement(render)) {
|
|
56
|
-
const renderChildren = children !== void 0 ? children : render.props.children;
|
|
57
76
|
const t1 = rest;
|
|
58
77
|
let t2;
|
|
59
|
-
if ($[5] !==
|
|
60
|
-
t2 =
|
|
61
|
-
$[5] =
|
|
62
|
-
$[6] =
|
|
78
|
+
if ($[5] !== children || $[6] !== render) {
|
|
79
|
+
t2 = composeRenderElement(render, children);
|
|
80
|
+
$[5] = children;
|
|
81
|
+
$[6] = render;
|
|
63
82
|
$[7] = t2;
|
|
64
83
|
} else t2 = $[7];
|
|
65
84
|
let t3;
|
|
@@ -108,6 +127,6 @@ const Primitive = NODES.reduce((acc, node) => {
|
|
|
108
127
|
return acc;
|
|
109
128
|
}, {});
|
|
110
129
|
//#endregion
|
|
111
|
-
export { Primitive, withRenderProp };
|
|
130
|
+
export { Primitive, renderSlot, withRenderProp };
|
|
112
131
|
|
|
113
132
|
//# sourceMappingURL=Primitive.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Primitive.js","names":["ComponentPropsWithoutRef","ComponentRef","ElementType","ForwardRefExoticComponent","PropsWithoutRef","ReactElement","ReactNode","RefAttributes","cloneElement","forwardRef","isValidElement","Primitive","RadixPrimitive","NODES","const","PrimitiveNode","WithRenderPropProps","T","render","PrimitiveProps","E","WithRenderPropRuntimeProps","asChild","children","PrimitiveRef","
|
|
1
|
+
{"version":3,"file":"Primitive.js","names":["ComponentProps","ComponentPropsWithoutRef","ComponentRef","ElementType","ForwardRefExoticComponent","PropsWithoutRef","ReactElement","ReactNode","RefAttributes","cloneElement","forwardRef","isValidElement","Primitive","RadixPrimitive","Slot","NODES","const","PrimitiveNode","WithRenderPropProps","T","render","PrimitiveProps","E","WithRenderPropRuntimeProps","asChild","children","PrimitiveRef","composeRenderElement","undefined","props","renderSlot","Root","withRenderProp","Component","Wrapped","t0","ref","$","_c","rest","Comp","t1","t2","t3","componentName","displayName","name","createPrimitive","node","RadixComp","reduce","acc","ReturnType","K"],"sources":["../../src/utils/Primitive.tsx"],"sourcesContent":["import {\n type ComponentProps,\n type ComponentPropsWithoutRef,\n type ComponentRef,\n type ElementType,\n type ForwardRefExoticComponent,\n type PropsWithoutRef,\n type ReactElement,\n type ReactNode,\n type RefAttributes,\n cloneElement,\n forwardRef,\n isValidElement,\n} from \"react\";\nimport { Primitive as RadixPrimitive } from \"radix-ui/internal\";\nimport { Slot } from \"radix-ui\";\n\n/**\n * Thin wrapper around Radix `Primitive` that adds `render` prop support.\n *\n * When `render` is provided, it is converted to the equivalent `asChild` pattern:\n * render={<Comp props />} + children → asChild + <Comp props>{children}</Comp>\n *\n * All prop merging, ref composition, and event handler chaining remain handled\n * by Radix's battle-tested Slot implementation — we add zero custom logic for that.\n */\n\n// Match @radix-ui/react-primitive's full element set\nconst NODES = [\n \"a\",\n \"button\",\n \"div\",\n \"form\",\n \"h2\",\n \"h3\",\n \"img\",\n \"input\",\n \"label\",\n \"li\",\n \"nav\",\n \"ol\",\n \"p\",\n \"select\",\n \"span\",\n \"svg\",\n \"ul\",\n] as const;\ntype PrimitiveNode = (typeof NODES)[number];\n\ntype WithRenderPropProps<T extends ElementType> =\n ComponentPropsWithoutRef<T> & {\n render?: ReactElement | undefined;\n };\n\ntype PrimitiveProps<E extends PrimitiveNode> = WithRenderPropProps<\n (typeof RadixPrimitive)[E]\n>;\n\ntype WithRenderPropRuntimeProps<T extends ElementType> =\n WithRenderPropProps<T> & {\n asChild?: boolean | undefined;\n children?: ReactNode | undefined;\n };\n\ntype PrimitiveRef<E extends PrimitiveNode> = ComponentRef<\n (typeof RadixPrimitive)[E]\n>;\n\n/**\n * Composes the children of a `render` element. Outer children win when supplied;\n * the render element's own children are the fallback.\n */\nfunction composeRenderElement(\n render: ReactElement,\n children: ReactNode,\n): ReactElement {\n return cloneElement(\n render,\n undefined,\n children !== undefined\n ? children\n : (render.props as { children?: ReactNode }).children,\n );\n}\n\n/**\n * Composes a `render` element at a call site that has already computed its props.\n *\n * `withRenderProp` wraps a component; this covers the case where the props are\n * already in hand and the composition happens inline.\n */\nfunction renderSlot(\n render: ReactElement,\n children: ReactNode,\n props: ComponentProps<typeof Slot.Root>,\n): ReactElement {\n return (\n <Slot.Root {...props}>{composeRenderElement(render, children)}</Slot.Root>\n );\n}\n\nfunction withRenderProp<T extends ElementType>(Component: T) {\n const Wrapped = forwardRef<ComponentRef<T>, WithRenderPropRuntimeProps<T>>(\n (\n {\n render,\n asChild,\n children,\n ...rest\n }: PropsWithoutRef<WithRenderPropRuntimeProps<T>>,\n ref,\n ) => {\n const Comp = Component as any;\n\n if (render && isValidElement(render)) {\n return (\n <Comp {...(rest as any)} asChild ref={ref}>\n {composeRenderElement(render, children)}\n </Comp>\n );\n }\n\n return (\n <Comp {...(rest as any)} asChild={asChild} ref={ref}>\n {children}\n </Comp>\n );\n },\n );\n\n const componentName =\n typeof Component === \"string\"\n ? Component\n : (Component.displayName ?? Component.name ?? \"Component\");\n Wrapped.displayName = componentName;\n\n return Wrapped as ForwardRefExoticComponent<\n WithRenderPropProps<T> & RefAttributes<ComponentRef<T>>\n >;\n}\n\nfunction createPrimitive<E extends PrimitiveNode>(node: E) {\n const RadixComp = RadixPrimitive[node];\n const Component = withRenderProp(RadixComp);\n\n Component.displayName = `Primitive.${node}`;\n return Component as ForwardRefExoticComponent<\n PrimitiveProps<E> & RefAttributes<PrimitiveRef<E>>\n >;\n}\n\nconst Primitive = NODES.reduce(\n (acc, node) => {\n acc[node] = createPrimitive(node);\n return acc;\n },\n {} as {\n [K in PrimitiveNode]: ReturnType<typeof createPrimitive<K>>;\n },\n);\n\nexport { Primitive, renderSlot, withRenderProp };\nexport type { PrimitiveProps, WithRenderPropProps };\n"],"mappings":";;;;;;;;;;;;;;;AA4BA,MAAMe,QAAQ;CACZ;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AAAI;;;;;AA2BN,SAASY,qBACPP,QACAK,UACc;CACd,OAAOhB,aACLW,QACAQ,KAAAA,GACAH,aAAaG,KAAAA,IACTH,WACCL,OAAOS,MAAmCJ,QACjD;AACF;;;;;;;AAQA,SAASK,WACPV,QACAK,UACAI,OACc;CACd,OACE,oBAAC,KAAK,MAAN;EAAW,GAAIA;EAAQF,UAAAA,qBAAqBP,QAAQK,QAAQ;CAAa,CAAA;AAE7E;AAEA,SAASO,eAAsCC,WAAc;CAC3D,MAAMC,UAAUxB,YACdyB,IAAAC,QAAA;EAAA,MAAAC,IAAAC,EAAA,EAAA;EAAA,IAAAd;EAAA,IAAAC;EAAA,IAAAL;EAAA,IAAAmB;EAAA,IAAAF,EAAA,OAAAF,IAAA;GACE,CAAA,CAAAf,QAAAI,SAAAC,UAAA,GAAAc,QAAAJ;GAKiDE,EAAA,KAAAF;GAAAE,EAAA,KAAAb;GAAAa,EAAA,KAAAZ;GAAAY,EAAA,KAAAjB;GAAAiB,EAAA,KAAAE;EAAA,OAAA;GAAAf,UAAAa,EAAA;GAAAZ,WAAAY,EAAA;GAAAjB,SAAAiB,EAAA;GAAAE,OAAAF,EAAA;EAAA;EAGjD,MAAAG,OAAaP;EAEb,IAAIb,UAAUT,eAAeS,MAAM,GAAC;GAErB,MAAAqB,KAAAF;GAAW,IAAAG;GAAA,IAAAL,EAAA,OAAAZ,YAAAY,EAAA,OAAAjB,QAAA;IACnBsB,KAAAf,qBAAqBP,QAAQK,QAAQ;IAACY,EAAA,KAAAZ;IAAAY,EAAA,KAAAjB;IAAAiB,EAAA,KAAAK;GAAA,OAAAA,KAAAL,EAAA;GAAA,IAAAM;GAAA,IAAAN,EAAA,OAAAD,OAAAC,EAAA,OAAAI,MAAAJ,EAAA,QAAAK,IAAA;IADzCC,KAAA,oBAAC,MAAD;KAAK,GAAMF;KAAc,SAAA;KAAaL;KACnCM,UAAAA;IADE,CAAA;IAEEL,EAAA,KAAAD;IAAAC,EAAA,KAAAI;IAAAJ,EAAA,MAAAK;IAAAL,EAAA,MAAAM;GAAA,OAAAA,KAAAN,EAAA;GAAA,OAFPM;EAEO;EAKE,MAAAF,KAAAF;EAAW,IAAAG;EAAA,IAAAL,EAAA,QAAAb,WAAAa,EAAA,QAAAZ,YAAAY,EAAA,QAAAD,OAAAC,EAAA,QAAAI,IAAA;GAAtBC,KAAA,oBAAC,MAAD;IAAK,GAAMD;IAAuBjB;IAAcY;IAC7CX;GADE,CAAA;GAEEY,EAAA,MAAAb;GAAAa,EAAA,MAAAZ;GAAAY,EAAA,MAAAD;GAAAC,EAAA,MAAAI;GAAAJ,EAAA,MAAAK;EAAA,OAAAA,KAAAL,EAAA;EAAA,OAFPK;CAEO,CAGb;CAMAR,QAAQW,cAHN,OAAOZ,cAAc,WACjBA,YACCA,UAAUY,eAAeZ,UAAUa,QAAQ;CAGlD,OAAOZ;AAGT;AAEA,SAASa,gBAAyCC,MAAS;CACzD,MAAMC,YAAYpC,YAAemC;CACjC,MAAMf,YAAYD,eAAeiB,SAAS;CAE1ChB,UAAUY,cAAc,aAAaG;CACrC,OAAOf;AAGT;AAEA,MAAMrB,YAAYG,MAAMmC,QACrBC,KAAKH,SAAS;CACbG,IAAIH,QAAQD,gBAAgBC,IAAI;CAChC,OAAOG;AACT,GACA,CAAC,CAGH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getSelectionMessageId.d.ts","names":[],"sources":["../../src/utils/getSelectionMessageId.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"getSelectionMessageId.d.ts","names":[],"sources":["../../src/utils/getSelectionMessageId.ts"],"mappings":";cAwDa,wBAAyB,WAAW"}
|
|
@@ -25,6 +25,14 @@ const findQuoteMarker = (node, messageElement) => {
|
|
|
25
25
|
if (!messageElement.contains(marker)) return null;
|
|
26
26
|
return marker;
|
|
27
27
|
};
|
|
28
|
+
const intersectsExcluded = (scope, selection) => {
|
|
29
|
+
const ranges = Array.from({ length: selection.rangeCount }, (_, i) => selection.getRangeAt(i));
|
|
30
|
+
for (const marker of scope.querySelectorAll(QUOTE_SELECTABLE_SELECTOR)) {
|
|
31
|
+
if (!isExcluded(marker)) continue;
|
|
32
|
+
if (ranges.some((range) => range.intersectsNode(marker))) return true;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
};
|
|
28
36
|
const getSelectionMessageId = (selection) => {
|
|
29
37
|
const { anchorNode, focusNode } = selection;
|
|
30
38
|
if (!anchorNode || !focusNode) return null;
|
|
@@ -37,9 +45,15 @@ const getSelectionMessageId = (selection) => {
|
|
|
37
45
|
const focusMarker = findQuoteMarker(focusNode, anchorMessageElement);
|
|
38
46
|
if (anchorMarker && isExcluded(anchorMarker)) return null;
|
|
39
47
|
if (focusMarker && isExcluded(focusMarker)) return null;
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
if (hasQuoteSelectableRegion(anchorMessageElement)) {
|
|
49
|
+
if (!anchorMarker || anchorMarker !== focusMarker) return null;
|
|
50
|
+
}
|
|
51
|
+
const scope = anchorMarker ?? anchorMessageElement;
|
|
52
|
+
for (let i = 0; i < selection.rangeCount; i++) {
|
|
53
|
+
const { commonAncestorContainer } = selection.getRangeAt(i);
|
|
54
|
+
if (!scope.contains(commonAncestorContainer)) return null;
|
|
55
|
+
}
|
|
56
|
+
return intersectsExcluded(scope, selection) ? null : messageId;
|
|
43
57
|
};
|
|
44
58
|
//#endregion
|
|
45
59
|
export { getSelectionMessageId };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getSelectionMessageId.js","names":["QUOTE_SELECTABLE_SELECTOR","getElement","node","Node","HTMLElement","parentElement","findMessageElement","el","id","getAttribute","isExcluded","marker","Element","hasQuoteSelectableRegion","messageElement","matches","querySelectorAll","findQuoteMarker","closest","contains","
|
|
1
|
+
{"version":3,"file":"getSelectionMessageId.js","names":["QUOTE_SELECTABLE_SELECTOR","getElement","node","Node","HTMLElement","parentElement","findMessageElement","el","id","getAttribute","isExcluded","marker","Element","hasQuoteSelectableRegion","messageElement","matches","querySelectorAll","findQuoteMarker","closest","contains","intersectsExcluded","scope","selection","Selection","ranges","Array","from","length","rangeCount","_","i","getRangeAt","some","range","intersectsNode","getSelectionMessageId","anchorNode","focusNode","anchorMessageElement","focusMessageElement","messageId","anchorMarker","focusMarker","commonAncestorContainer"],"sources":["../../src/utils/getSelectionMessageId.ts"],"sourcesContent":["const QUOTE_SELECTABLE_SELECTOR = \"[data-aui-quote-selectable]\";\n\nconst getElement = (node: Node | null): HTMLElement | null => {\n return node instanceof HTMLElement ? node : (node?.parentElement ?? null);\n};\n\nconst findMessageElement = (node: Node | null): HTMLElement | null => {\n let el = node instanceof HTMLElement ? node : (node?.parentElement ?? null);\n while (el) {\n const id = el.getAttribute(\"data-message-id\");\n if (id) return el;\n el = el.parentElement;\n }\n return null;\n};\n\nconst isExcluded = (marker: Element): boolean => {\n return marker.getAttribute(\"data-aui-quote-selectable\") === \"false\";\n};\n\nconst hasQuoteSelectableRegion = (messageElement: HTMLElement) => {\n if (\n messageElement.matches(QUOTE_SELECTABLE_SELECTOR) &&\n !isExcluded(messageElement)\n ) {\n return true;\n }\n for (const marker of messageElement.querySelectorAll(\n QUOTE_SELECTABLE_SELECTOR,\n )) {\n if (!isExcluded(marker)) return true;\n }\n return false;\n};\n\nconst findQuoteMarker = (\n node: Node | null,\n messageElement: HTMLElement,\n): HTMLElement | null => {\n const marker = getElement(node)?.closest(QUOTE_SELECTABLE_SELECTOR);\n if (!(marker instanceof HTMLElement)) return null;\n if (!messageElement.contains(marker)) return null;\n return marker;\n};\n\nconst intersectsExcluded = (scope: Element, selection: Selection): boolean => {\n const ranges = Array.from({ length: selection.rangeCount }, (_, i) =>\n selection.getRangeAt(i),\n );\n for (const marker of scope.querySelectorAll(QUOTE_SELECTABLE_SELECTOR)) {\n if (!isExcluded(marker)) continue;\n if (ranges.some((range) => range.intersectsNode(marker))) return true;\n }\n return false;\n};\n\nexport const getSelectionMessageId = (selection: Selection): string | null => {\n const { anchorNode, focusNode } = selection;\n if (!anchorNode || !focusNode) return null;\n\n const anchorMessageElement = findMessageElement(anchorNode);\n const focusMessageElement = findMessageElement(focusNode);\n\n if (!anchorMessageElement || anchorMessageElement !== focusMessageElement) {\n return null;\n }\n\n const messageId = anchorMessageElement.getAttribute(\"data-message-id\");\n if (!messageId) return null;\n\n const anchorMarker = findQuoteMarker(anchorNode, anchorMessageElement);\n const focusMarker = findQuoteMarker(focusNode, anchorMessageElement);\n\n if (anchorMarker && isExcluded(anchorMarker)) return null;\n if (focusMarker && isExcluded(focusMarker)) return null;\n\n if (hasQuoteSelectableRegion(anchorMessageElement)) {\n if (!anchorMarker || anchorMarker !== focusMarker) return null;\n }\n\n const scope = anchorMarker ?? anchorMessageElement;\n\n for (let i = 0; i < selection.rangeCount; i++) {\n const { commonAncestorContainer } = selection.getRangeAt(i);\n if (!scope.contains(commonAncestorContainer)) return null;\n }\n\n return intersectsExcluded(scope, selection) ? null : messageId;\n};\n"],"mappings":";AAAA,MAAMA,4BAA4B;AAElC,MAAMC,cAAcC,SAA0C;CAC5D,OAAOA,gBAAgBE,cAAcF,OAAQA,MAAMG,iBAAiB;AACtE;AAEA,MAAMC,sBAAsBJ,SAA0C;CACpE,IAAIK,KAAKL,gBAAgBE,cAAcF,OAAQA,MAAMG,iBAAiB;CACtE,OAAOE,IAAI;EAET,IADWA,GAAGE,aAAa,iBACvBD,GAAI,OAAOD;EACfA,KAAKA,GAAGF;CACV;CACA,OAAO;AACT;AAEA,MAAMK,cAAcC,WAA6B;CAC/C,OAAOA,OAAOF,aAAa,2BAA2B,MAAM;AAC9D;AAEA,MAAMI,4BAA4BC,mBAAgC;CAChE,IACEA,eAAeC,QAAQf,yBAAyB,KAChD,CAACU,WAAWI,cAAc,GAE1B,OAAO;CAET,KAAK,MAAMH,UAAUG,eAAeE,iBAClChB,yBACF,GACE,IAAI,CAACU,WAAWC,MAAM,GAAG,OAAO;CAElC,OAAO;AACT;AAEA,MAAMM,mBACJf,MACAY,mBACuB;CACvB,MAAMH,SAASV,WAAWC,IAAI,CAAC,EAAEgB,QAAQlB,yBAAyB;CAClE,IAAI,EAAEW,kBAAkBP,cAAc,OAAO;CAC7C,IAAI,CAACU,eAAeK,SAASR,MAAM,GAAG,OAAO;CAC7C,OAAOA;AACT;AAEA,MAAMS,sBAAsBC,OAAgBC,cAAkC;CAC5E,MAAME,SAASC,MAAMC,KAAK,EAAEC,QAAQL,UAAUM,WAAW,IAAIC,GAAGC,MAC9DR,UAAUS,WAAWD,CAAC,CACxB;CACA,KAAK,MAAMnB,UAAUU,MAAML,iBAAiBhB,yBAAyB,GAAG;EACtE,IAAI,CAACU,WAAWC,MAAM,GAAG;EACzB,IAAIa,OAAOQ,MAAMC,UAAUA,MAAMC,eAAevB,MAAM,CAAC,GAAG,OAAO;CACnE;CACA,OAAO;AACT;AAEA,MAAawB,yBAAyBb,cAAwC;CAC5E,MAAM,EAAEc,YAAYC,cAAcf;CAClC,IAAI,CAACc,cAAc,CAACC,WAAW,OAAO;CAEtC,MAAMC,uBAAuBhC,mBAAmB8B,UAAU;CAC1D,MAAMG,sBAAsBjC,mBAAmB+B,SAAS;CAExD,IAAI,CAACC,wBAAwBA,yBAAyBC,qBACpD,OAAO;CAGT,MAAMC,YAAYF,qBAAqB7B,aAAa,iBAAiB;CACrE,IAAI,CAAC+B,WAAW,OAAO;CAEvB,MAAMC,eAAexB,gBAAgBmB,YAAYE,oBAAoB;CACrE,MAAMI,cAAczB,gBAAgBoB,WAAWC,oBAAoB;CAEnE,IAAIG,gBAAgB/B,WAAW+B,YAAY,GAAG,OAAO;CACrD,IAAIC,eAAehC,WAAWgC,WAAW,GAAG,OAAO;CAEnD,IAAI7B,yBAAyByB,oBAAoB,GAC3C;MAAA,CAACG,gBAAgBA,iBAAiBC,aAAa,OAAO;CAAA;CAG5D,MAAMrB,QAAQoB,gBAAgBH;CAE9B,KAAK,IAAIR,IAAI,GAAGA,IAAIR,UAAUM,YAAYE,KAAK;EAC7C,MAAM,EAAEa,4BAA4BrB,UAAUS,WAAWD,CAAC;EAC1D,IAAI,CAACT,MAAMF,SAASwB,uBAAuB,GAAG,OAAO;CACvD;CAEA,OAAOvB,mBAAmBC,OAAOC,SAAS,IAAI,OAAOkB;AACvD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.19",
|
|
4
4
|
"description": "Open-source TypeScript/React library for building production-grade AI chat experiences",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -55,15 +55,15 @@
|
|
|
55
55
|
],
|
|
56
56
|
"sideEffects": false,
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@assistant-ui/core": "^0.3.
|
|
59
|
-
"@assistant-ui/store": "^0.3.
|
|
60
|
-
"@assistant-ui/tap": "^0.9.
|
|
61
|
-
"assistant-cloud": "^0.
|
|
62
|
-
"assistant-stream": "^0.3.
|
|
58
|
+
"@assistant-ui/core": "^0.3.18",
|
|
59
|
+
"@assistant-ui/store": "^0.3.13",
|
|
60
|
+
"@assistant-ui/tap": "^0.9.17",
|
|
61
|
+
"assistant-cloud": "^0.2.0",
|
|
62
|
+
"assistant-stream": "^0.3.42",
|
|
63
63
|
"radix-ui": "^1.6.7",
|
|
64
64
|
"react-textarea-autosize": "^8.5.9",
|
|
65
|
-
"safe-content-frame": "^0.0.
|
|
66
|
-
"zod": "^4.4
|
|
65
|
+
"safe-content-frame": "^0.0.30",
|
|
66
|
+
"zod": "^4.5.4",
|
|
67
67
|
"zustand": "^5.0.15"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
@@ -81,13 +81,13 @@
|
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
|
-
"@assistant-ui/vite": "0.0.
|
|
85
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
86
|
-
"@testing-library/react": "^16.3.
|
|
84
|
+
"@assistant-ui/vite": "0.0.17",
|
|
85
|
+
"@assistant-ui/x-buildutils": "0.0.26",
|
|
86
|
+
"@testing-library/react": "^16.3.3",
|
|
87
87
|
"@types/json-schema": "^7.0.15",
|
|
88
|
-
"@types/node": "^26.
|
|
88
|
+
"@types/node": "^26.4.1",
|
|
89
89
|
"@types/react": "^19.2.18",
|
|
90
|
-
"@types/react-dom": "^19.2.
|
|
90
|
+
"@types/react-dom": "^19.2.7",
|
|
91
91
|
"jsdom": "^30.0.1",
|
|
92
92
|
"react": "^19.2.8",
|
|
93
93
|
"react-dom": "^19.2.8",
|
|
@@ -25,8 +25,8 @@ const useThreadViewportStoreValue = (options: ThreadViewportStoreOptions) => {
|
|
|
25
25
|
|
|
26
26
|
// Forward scrollToBottom from outer viewport to inner viewport
|
|
27
27
|
useEffect(() => {
|
|
28
|
-
return outerViewport?.getState().onScrollToBottom(() => {
|
|
29
|
-
store.getState().scrollToBottom();
|
|
28
|
+
return outerViewport?.getState().onScrollToBottom((config) => {
|
|
29
|
+
store.getState().scrollToBottom(config);
|
|
30
30
|
});
|
|
31
31
|
}, [outerViewport, store]);
|
|
32
32
|
|
|
@@ -36,6 +36,9 @@ export const useToolCallElapsed = (): number | undefined => {
|
|
|
36
36
|
|
|
37
37
|
useEffect(() => {
|
|
38
38
|
if (!running) return undefined;
|
|
39
|
+
// The clock is an external source; this catches the elapsed value up before
|
|
40
|
+
// the interval takes over.
|
|
41
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
39
42
|
setNow(Date.now());
|
|
40
43
|
const id = setInterval(() => setNow(Date.now()), 1000);
|
|
41
44
|
return () => clearInterval(id);
|
package/src/index.ts
CHANGED
|
@@ -43,8 +43,10 @@ export type {
|
|
|
43
43
|
ThreadListItemState,
|
|
44
44
|
} from "@assistant-ui/core";
|
|
45
45
|
|
|
46
|
+
export { toolApprovalAcceptsText } from "@assistant-ui/core";
|
|
47
|
+
|
|
46
48
|
export { useCloudThreadListRuntime } from "@assistant-ui/core/react";
|
|
47
|
-
export { AssistantCloud } from "assistant-cloud";
|
|
49
|
+
export { AssistantCloud, readAnonymousRefreshToken } from "assistant-cloud";
|
|
48
50
|
|
|
49
51
|
// --- adapters/attachment ---
|
|
50
52
|
export type { AttachmentAdapter } from "@assistant-ui/core";
|
|
@@ -360,6 +362,7 @@ export type {
|
|
|
360
362
|
GenerativeUISpec,
|
|
361
363
|
Unstable_AudioMessagePart,
|
|
362
364
|
RespondToToolApprovalOptions,
|
|
365
|
+
ToolApprovalDisplay,
|
|
363
366
|
ToolApprovalOption,
|
|
364
367
|
ToolApprovalOptionKind,
|
|
365
368
|
ToolApprovalResponse,
|
|
@@ -515,6 +518,7 @@ export {
|
|
|
515
518
|
getMcpAppFromToolPart,
|
|
516
519
|
} from "./mcp-apps";
|
|
517
520
|
export type {
|
|
521
|
+
McpAppPartOptions,
|
|
518
522
|
McpAppRendererOptions,
|
|
519
523
|
McpAppMetadata,
|
|
520
524
|
McpAppResource,
|
|
@@ -531,3 +535,12 @@ export type {
|
|
|
531
535
|
ToolCallMessagePartMcpMetadata,
|
|
532
536
|
} from "./mcp-apps";
|
|
533
537
|
export type { McpAppResourceOutput } from "@assistant-ui/core/react";
|
|
538
|
+
export type { ShimLoadError, ShimLoadErrorCode } from "safe-content-frame";
|
|
539
|
+
|
|
540
|
+
// Unstable - WebMCP provider (exposes frontend tools to a WebMCP-capable browser)
|
|
541
|
+
export {
|
|
542
|
+
unstable_useWebMcpProvider,
|
|
543
|
+
type Unstable_WebMcpProviderOptions,
|
|
544
|
+
type Unstable_WebMcpProviderResult,
|
|
545
|
+
} from "./unstable/webmcp/useWebMcpProvider";
|
|
546
|
+
export { defaultWebMcpFilter as unstable_defaultWebMcpFilter } from "./unstable/webmcp/convertTools";
|
|
@@ -9,16 +9,21 @@ import type {
|
|
|
9
9
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
10
10
|
import type {
|
|
11
11
|
McpAppBridgeHandlers,
|
|
12
|
+
McpAppFrameProps,
|
|
13
|
+
McpAppHostContext,
|
|
12
14
|
McpAppsHost,
|
|
13
15
|
McpAppsRemoteHostOptions,
|
|
14
16
|
} from "./types";
|
|
15
17
|
|
|
16
|
-
const { framePropsMock } = vi.hoisted(() => ({
|
|
18
|
+
const { appendMock, framePropsMock } = vi.hoisted(() => ({
|
|
19
|
+
appendMock: vi.fn(),
|
|
20
|
+
framePropsMock: vi.fn(),
|
|
21
|
+
}));
|
|
17
22
|
|
|
18
23
|
vi.mock("@assistant-ui/store", async (importOriginal) => ({
|
|
19
24
|
...(await importOriginal()),
|
|
20
25
|
useAui: () => ({
|
|
21
|
-
thread:
|
|
26
|
+
thread: { append: appendMock },
|
|
22
27
|
}),
|
|
23
28
|
}));
|
|
24
29
|
|
|
@@ -43,9 +48,12 @@ const createDeferred = <T,>() => {
|
|
|
43
48
|
return { promise, resolve };
|
|
44
49
|
};
|
|
45
50
|
|
|
46
|
-
const createPart = (
|
|
51
|
+
const createPart = (
|
|
52
|
+
serverId?: string,
|
|
53
|
+
toolCallId = "call-1",
|
|
54
|
+
): ToolCallMessagePartProps => ({
|
|
47
55
|
type: "tool-call",
|
|
48
|
-
toolCallId
|
|
56
|
+
toolCallId,
|
|
49
57
|
toolName: "search",
|
|
50
58
|
args: {},
|
|
51
59
|
argsText: "{}",
|
|
@@ -105,6 +113,60 @@ function MemoizedHarness({ host }: { host: McpAppsHost }) {
|
|
|
105
113
|
return <MemoizedPart Renderer={renderer.render} />;
|
|
106
114
|
}
|
|
107
115
|
|
|
116
|
+
const MemoizedPartById = memo(function MemoizedPartById({
|
|
117
|
+
Renderer,
|
|
118
|
+
toolCallId,
|
|
119
|
+
}: {
|
|
120
|
+
Renderer: ToolCallMessagePartComponent;
|
|
121
|
+
toolCallId: string;
|
|
122
|
+
}) {
|
|
123
|
+
return <Renderer {...createPart(undefined, toolCallId)} />;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
function OptionsHarness({
|
|
127
|
+
host,
|
|
128
|
+
hostContext,
|
|
129
|
+
handlers,
|
|
130
|
+
forPart,
|
|
131
|
+
toolCallIds = ["call-1"],
|
|
132
|
+
}: {
|
|
133
|
+
host: McpAppsHost;
|
|
134
|
+
hostContext?: McpAppHostContext;
|
|
135
|
+
handlers?: McpAppRendererOptions["handlers"];
|
|
136
|
+
forPart?: McpAppRendererOptions["forPart"];
|
|
137
|
+
toolCallIds?: string[];
|
|
138
|
+
}) {
|
|
139
|
+
const renderer = useResource(
|
|
140
|
+
McpAppRenderer({
|
|
141
|
+
host: Host({ host }),
|
|
142
|
+
...(hostContext === undefined ? {} : { hostContext }),
|
|
143
|
+
...(handlers === undefined ? {} : { handlers }),
|
|
144
|
+
...(forPart === undefined ? {} : { forPart }),
|
|
145
|
+
}),
|
|
146
|
+
);
|
|
147
|
+
return toolCallIds.map((toolCallId) => (
|
|
148
|
+
<MemoizedPartById
|
|
149
|
+
key={toolCallId}
|
|
150
|
+
Renderer={renderer.render}
|
|
151
|
+
toolCallId={toolCallId}
|
|
152
|
+
/>
|
|
153
|
+
));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const framePropsCalls = () =>
|
|
157
|
+
framePropsMock.mock.calls.map(([props]) => props as McpAppFrameProps);
|
|
158
|
+
|
|
159
|
+
const loadingHost = (): McpAppsHost => ({
|
|
160
|
+
loadResource: vi.fn(async ({ uri }) => ({
|
|
161
|
+
uri,
|
|
162
|
+
mimeType: "text/html;profile=mcp-app" as const,
|
|
163
|
+
html: "",
|
|
164
|
+
})),
|
|
165
|
+
callTool: vi.fn(),
|
|
166
|
+
readResource: vi.fn(),
|
|
167
|
+
listResources: vi.fn(),
|
|
168
|
+
});
|
|
169
|
+
|
|
108
170
|
function RemoteHarness({
|
|
109
171
|
url,
|
|
110
172
|
headers,
|
|
@@ -127,9 +189,146 @@ function RemoteHarness({
|
|
|
127
189
|
|
|
128
190
|
describe("McpAppRenderer", () => {
|
|
129
191
|
beforeEach(() => {
|
|
192
|
+
appendMock.mockReset();
|
|
130
193
|
framePropsMock.mockReset();
|
|
131
194
|
});
|
|
132
195
|
|
|
196
|
+
it("loads newly mounted parts through the committed host", async () => {
|
|
197
|
+
const hostA = loadingHost();
|
|
198
|
+
const hostB = loadingHost();
|
|
199
|
+
const view = render(
|
|
200
|
+
<OptionsHarness host={hostA} toolCallIds={["call-1"]} />,
|
|
201
|
+
);
|
|
202
|
+
await waitFor(() => expect(hostA.loadResource).toHaveBeenCalledOnce());
|
|
203
|
+
vi.mocked(hostA.loadResource).mockClear();
|
|
204
|
+
|
|
205
|
+
view.rerender(
|
|
206
|
+
<OptionsHarness host={hostB} toolCallIds={["call-1", "call-2"]} />,
|
|
207
|
+
);
|
|
208
|
+
await waitFor(() => expect(hostB.loadResource).toHaveBeenCalled());
|
|
209
|
+
|
|
210
|
+
expect(hostA.loadResource).not.toHaveBeenCalled();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("merges forPart handlers over the thread-wide handlers", async () => {
|
|
214
|
+
const onInitialized = vi.fn();
|
|
215
|
+
render(
|
|
216
|
+
<OptionsHarness
|
|
217
|
+
host={loadingHost()}
|
|
218
|
+
handlers={{ onInitialized }}
|
|
219
|
+
forPart={(part) => ({
|
|
220
|
+
handlers: {
|
|
221
|
+
requestDisplayMode: ({ mode }) => {
|
|
222
|
+
void part;
|
|
223
|
+
return { mode };
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
})}
|
|
227
|
+
/>,
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
await waitFor(() => expect(framePropsCalls().length).toBeGreaterThan(0));
|
|
231
|
+
const handlers = framePropsCalls().at(-1)?.handlers;
|
|
232
|
+
expect(handlers?.requestDisplayMode).toBeDefined();
|
|
233
|
+
handlers?.onInitialized?.();
|
|
234
|
+
expect(onInitialized).toHaveBeenCalledOnce();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("leaves mounted parts alone when renderer options are unchanged", async () => {
|
|
238
|
+
const host = loadingHost();
|
|
239
|
+
const hostContext: McpAppHostContext = { displayMode: "inline" };
|
|
240
|
+
const view = () => <OptionsHarness host={host} hostContext={hostContext} />;
|
|
241
|
+
|
|
242
|
+
const rendered = render(view());
|
|
243
|
+
await waitFor(() => expect(framePropsCalls().length).toBeGreaterThan(0));
|
|
244
|
+
const rendersBefore = framePropsCalls().length;
|
|
245
|
+
|
|
246
|
+
rendered.rerender(view());
|
|
247
|
+
expect(framePropsCalls().length).toBe(rendersBefore);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("delivers a changed renderer host context to mounted parts", async () => {
|
|
251
|
+
const host = loadingHost();
|
|
252
|
+
const view = render(
|
|
253
|
+
<OptionsHarness host={host} hostContext={{ displayMode: "inline" }} />,
|
|
254
|
+
);
|
|
255
|
+
await waitFor(() =>
|
|
256
|
+
expect(framePropsCalls().at(-1)?.hostContext).toEqual({
|
|
257
|
+
displayMode: "inline",
|
|
258
|
+
}),
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
view.rerender(
|
|
262
|
+
<OptionsHarness
|
|
263
|
+
host={host}
|
|
264
|
+
hostContext={{ displayMode: "fullscreen" }}
|
|
265
|
+
/>,
|
|
266
|
+
);
|
|
267
|
+
await waitFor(() =>
|
|
268
|
+
expect(framePropsCalls().at(-1)?.hostContext).toEqual({
|
|
269
|
+
displayMode: "fullscreen",
|
|
270
|
+
}),
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("resolves host context per part through forPart", async () => {
|
|
275
|
+
const modes: Record<string, string> = {
|
|
276
|
+
"call-1": "inline",
|
|
277
|
+
"call-2": "fullscreen",
|
|
278
|
+
};
|
|
279
|
+
render(
|
|
280
|
+
<OptionsHarness
|
|
281
|
+
host={loadingHost()}
|
|
282
|
+
hostContext={{ theme: "light" }}
|
|
283
|
+
toolCallIds={["call-1", "call-2"]}
|
|
284
|
+
forPart={(part) => ({
|
|
285
|
+
hostContext: {
|
|
286
|
+
theme: "light",
|
|
287
|
+
displayMode: modes[part.toolCallId] as "inline" | "fullscreen",
|
|
288
|
+
},
|
|
289
|
+
})}
|
|
290
|
+
/>,
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
await waitFor(() => expect(framePropsCalls().length).toBeGreaterThan(1));
|
|
294
|
+
const delivered = framePropsCalls().map((props) => props.hostContext);
|
|
295
|
+
expect(delivered).toContainEqual({
|
|
296
|
+
theme: "light",
|
|
297
|
+
displayMode: "inline",
|
|
298
|
+
});
|
|
299
|
+
expect(delivered).toContainEqual({
|
|
300
|
+
theme: "light",
|
|
301
|
+
displayMode: "fullscreen",
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("gives forPart handlers the part that owns the frame", async () => {
|
|
306
|
+
const promoted: string[] = [];
|
|
307
|
+
render(
|
|
308
|
+
<OptionsHarness
|
|
309
|
+
host={loadingHost()}
|
|
310
|
+
toolCallIds={["call-1", "call-2"]}
|
|
311
|
+
forPart={(part) => ({
|
|
312
|
+
hostContext: { toolCallId: part.toolCallId },
|
|
313
|
+
handlers: {
|
|
314
|
+
requestDisplayMode: ({ mode }) => {
|
|
315
|
+
promoted.push(`${part.toolCallId}:${mode}`);
|
|
316
|
+
return { mode };
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
})}
|
|
320
|
+
/>,
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
await waitFor(() => expect(framePropsCalls().length).toBeGreaterThan(1));
|
|
324
|
+
const second = framePropsCalls().find(
|
|
325
|
+
(props) => props.hostContext?.["toolCallId"] === "call-2",
|
|
326
|
+
);
|
|
327
|
+
await second?.handlers?.requestDisplayMode?.({ mode: "fullscreen" });
|
|
328
|
+
|
|
329
|
+
expect(promoted).toEqual(["call-2:fullscreen"]);
|
|
330
|
+
});
|
|
331
|
+
|
|
133
332
|
it("injects serverId into loadResource and reloads when it changes", async () => {
|
|
134
333
|
const loadResource = vi.fn(async ({ uri }: { uri: string }) => ({
|
|
135
334
|
uri,
|
|
@@ -250,7 +449,7 @@ describe("McpAppRenderer", () => {
|
|
|
250
449
|
framePropsMock.mockClear();
|
|
251
450
|
view.rerender(<MemoizedHarness host={nextHost} />);
|
|
252
451
|
|
|
253
|
-
expect(nextHost.loadResource).toHaveBeenCalledTimes(1);
|
|
452
|
+
await waitFor(() => expect(nextHost.loadResource).toHaveBeenCalledTimes(1));
|
|
254
453
|
expect(framePropsMock).not.toHaveBeenCalled();
|
|
255
454
|
|
|
256
455
|
nextResource.resolve({
|
|
@@ -473,6 +672,55 @@ describe("McpAppRenderer", () => {
|
|
|
473
672
|
expect(listResources).toHaveBeenCalledWith(params);
|
|
474
673
|
});
|
|
475
674
|
|
|
675
|
+
it("appends MCP Apps message content in the default sendMessage handler", async () => {
|
|
676
|
+
render(<Harness host={loadingHost()} />);
|
|
677
|
+
await waitFor(() => expect(framePropsMock).toHaveBeenCalled());
|
|
678
|
+
const handlers = framePropsMock.mock.lastCall?.[0]
|
|
679
|
+
.handlers as McpAppBridgeHandlers;
|
|
680
|
+
|
|
681
|
+
expect(
|
|
682
|
+
handlers.sendMessage?.({
|
|
683
|
+
role: "user",
|
|
684
|
+
content: [
|
|
685
|
+
{ type: "text", text: "Open Grade 5 Math" },
|
|
686
|
+
{ type: "image", data: "image", mimeType: "image/png" },
|
|
687
|
+
{ type: "text", text: "Period 4" },
|
|
688
|
+
],
|
|
689
|
+
}),
|
|
690
|
+
).toEqual({ ok: true });
|
|
691
|
+
expect(appendMock).toHaveBeenCalledWith({
|
|
692
|
+
content: [
|
|
693
|
+
{ type: "text", text: "Open Grade 5 Math" },
|
|
694
|
+
{ type: "text", text: "Period 4" },
|
|
695
|
+
],
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
expect(handlers.sendMessage?.({ text: "typed instead" })).toEqual({
|
|
699
|
+
ok: true,
|
|
700
|
+
});
|
|
701
|
+
expect(appendMock).toHaveBeenLastCalledWith({
|
|
702
|
+
content: [{ type: "text", text: "typed instead" }],
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
expect(
|
|
706
|
+
handlers.sendMessage?.({
|
|
707
|
+
role: "user",
|
|
708
|
+
content: [{ type: "image", data: "image", mimeType: "image/png" }],
|
|
709
|
+
}),
|
|
710
|
+
).toEqual({
|
|
711
|
+
isError: true,
|
|
712
|
+
ok: false,
|
|
713
|
+
reason: "no text content to append",
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
expect(handlers.sendMessage?.({ role: "user" })).toEqual({
|
|
717
|
+
isError: true,
|
|
718
|
+
ok: false,
|
|
719
|
+
reason: "unrecognised params shape",
|
|
720
|
+
});
|
|
721
|
+
expect(appendMock).toHaveBeenCalledTimes(2);
|
|
722
|
+
});
|
|
723
|
+
|
|
476
724
|
it("uses caller UI handlers and keeps data-plane handlers on the host", async () => {
|
|
477
725
|
const host: McpAppsHost = {
|
|
478
726
|
loadResource: vi.fn(async ({ uri }) => ({
|