@assistant-ui/react
Version:
Open-source TypeScript/React library for building production-grade AI chat experiences
310 lines (309 loc) • 9.51 kB
JavaScript
"use client";
import { useIsomorphicLayoutEffect } from "../utils/useIsomorphicLayoutEffect.js";
import { createMcpAppBridge } from "./bridge.js";
import { SandboxHost } from "../sandbox-host/SandboxHost.js";
import { applyMcpAppCsp } from "./csp.js";
import { isRecord } from "@assistant-ui/core/internal";
import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
import { useEffect, useRef } from "@assistant-ui/tap/react-shim";
import { jsx } from "react/jsx-runtime";
// src/mcp-apps/app-frame.tsx
const DEFAULT_PRODUCT = "assistant-ui-mcp-app";
const INIT_TIMEOUT_MS = 5e3;
const DEFAULT_MAX_HEIGHT = 800;
const isPlainObject = (value) => {
if (!isRecord(value)) return false;
const prototype = Object.getPrototypeOf(value);
return prototype === null || prototype === Object.prototype;
};
const isSameHostContext = (a, b, depth = 0) => {
if (Object.is(a, b)) return true;
if (depth > 100) return false;
if (Array.isArray(a) || Array.isArray(b)) {
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) if (!isSameHostContext(a[i], b[i], depth + 1)) return false;
return true;
}
if (!isPlainObject(a) || !isPlainObject(b)) return false;
const aKeys = Object.keys(a);
return aKeys.length === Object.keys(b).length && aKeys.every((key) => Object.hasOwn(b, key) && isSameHostContext(a[key], b[key], depth + 1));
};
function useBridgeNotify(value, bridgeRef, widgetReadyRef, pendingRef, lastSentRef, notify, t0) {
const $ = c(10);
const isEqual = t0 === void 0 ? Object.is : t0;
let t1;
if ($[0] !== bridgeRef || $[1] !== isEqual || $[2] !== lastSentRef || $[3] !== notify || $[4] !== pendingRef || $[5] !== value || $[6] !== widgetReadyRef) {
t1 = () => {
if (!bridgeRef.current) return;
if (value === void 0 || isEqual(lastSentRef.current, value)) {
pendingRef.current = void 0;
return;
}
if (!widgetReadyRef.current) {
pendingRef.current = value;
return;
}
notify(bridgeRef.current, value);
lastSentRef.current = value;
};
$[0] = bridgeRef;
$[1] = isEqual;
$[2] = lastSentRef;
$[3] = notify;
$[4] = pendingRef;
$[5] = value;
$[6] = widgetReadyRef;
$[7] = t1;
} else t1 = $[7];
let t2;
if ($[8] !== value) {
t2 = [value];
$[8] = value;
$[9] = t2;
} else t2 = $[9];
useEffect(t1, t2);
}
function buildLiveHandlers(initial, liveRef, lifecycle) {
const live = () => liveRef.current.handlers;
const has = (key) => initial?.[key] !== void 0;
const out = {};
Object.defineProperty(out, "allowedTools", {
get: () => live()?.allowedTools,
enumerable: true,
configurable: true
});
const liveCall = (key) => ((p) => {
const fn = live()?.[key];
if (!fn) throw new Error(`${key} handler is no longer available`);
return fn(p);
});
if (has("callTool")) out.callTool = liveCall("callTool");
if (has("readResource")) out.readResource = liveCall("readResource");
if (has("listResources")) out.listResources = liveCall("listResources");
if (has("openLink")) out.openLink = liveCall("openLink");
if (has("sendMessage")) out.sendMessage = liveCall("sendMessage");
if (has("updateModelContext")) out.updateModelContext = liveCall("updateModelContext");
if (has("requestDisplayMode")) out.requestDisplayMode = liveCall("requestDisplayMode");
out.onSizeChange = (p) => {
lifecycle.onSizeChange(p);
live()?.onSizeChange?.(p);
};
out.onInitialized = (params) => {
lifecycle.onInitialized();
live()?.onInitialized?.(params);
};
out.onRequestTeardown = (p) => live()?.onRequestTeardown?.(p);
out.onLog = (p) => live()?.onLog?.(p);
out.onError = (e) => live()?.onError?.(e);
return out;
}
function McpAppFrame(t0) {
const $ = c(32);
const { app, resource, input, output, sandbox, handlers, hostInfo, hostContext, maxHeight: t1 } = t0;
const maxHeight = t1 === void 0 ? DEFAULT_MAX_HEIGHT : t1;
const bridgeRef = useRef(null);
const lastSentInputRef = useRef(void 0);
const lastSentOutputRef = useRef(void 0);
const lastSentHostContextRef = useRef(void 0);
const widgetReadyRef = useRef(false);
const pendingInputRef = useRef(void 0);
const pendingOutputRef = useRef(void 0);
const pendingHostContextRef = useRef(void 0);
let t2;
if ($[0] !== handlers || $[1] !== hostContext || $[2] !== hostInfo || $[3] !== input || $[4] !== output) {
t2 = {
handlers,
hostInfo,
hostContext,
input,
output
};
$[0] = handlers;
$[1] = hostContext;
$[2] = hostInfo;
$[3] = input;
$[4] = output;
$[5] = t2;
} else t2 = $[5];
const liveRef = useRef(t2);
let t3;
let t4;
if ($[6] !== handlers || $[7] !== hostContext || $[8] !== hostInfo || $[9] !== input || $[10] !== output) {
t3 = () => {
liveRef.current = {
handlers,
hostInfo,
hostContext,
input,
output
};
};
t4 = [
handlers,
hostInfo,
hostContext,
input,
output
];
$[6] = handlers;
$[7] = hostContext;
$[8] = hostInfo;
$[9] = input;
$[10] = output;
$[11] = t3;
$[12] = t4;
} else {
t3 = $[11];
t4 = $[12];
}
useIsomorphicLayoutEffect(t3, t4);
let t5;
if ($[13] === Symbol.for("react.memo_cache_sentinel")) {
t5 = (frame, host) => {
const current = liveRef.current;
let initTimeoutId = null;
const flushPending = () => {
if (widgetReadyRef.current) return;
widgetReadyRef.current = true;
const b = bridgeRef.current;
if (!b) return;
if (pendingInputRef.current !== void 0) {
b.notifyToolInput(pendingInputRef.current);
lastSentInputRef.current = pendingInputRef.current;
pendingInputRef.current = void 0;
}
if (pendingOutputRef.current !== void 0) {
b.notifyToolResult(pendingOutputRef.current);
lastSentOutputRef.current = pendingOutputRef.current;
pendingOutputRef.current = void 0;
}
if (pendingHostContextRef.current !== void 0) {
b.notifyHostContextChanged(pendingHostContextRef.current);
lastSentHostContextRef.current = pendingHostContextRef.current;
pendingHostContextRef.current = void 0;
}
};
const wrappedHandlers = buildLiveHandlers(current.handlers, liveRef, {
onInitialized: () => {
if (initTimeoutId !== null) {
clearTimeout(initTimeoutId);
initTimeoutId = null;
}
flushPending();
},
onSizeChange: (p) => {
if (p.height != null) host.setHeight(p.height);
}
});
initTimeoutId = setTimeout(() => {
initTimeoutId = null;
flushPending();
}, INIT_TIMEOUT_MS);
const bridge = createMcpAppBridge({
frame,
handlers: wrappedHandlers,
hostInfo: current.hostInfo,
hostContext: current.hostContext
});
bridgeRef.current = bridge;
if (current.input !== void 0) pendingInputRef.current = current.input;
if (current.output !== void 0) pendingOutputRef.current = current.output;
lastSentHostContextRef.current = current.hostContext ?? {};
return {
onMessage: bridge.onMessage,
dispose: () => {
if (initTimeoutId !== null) {
clearTimeout(initTimeoutId);
initTimeoutId = null;
}
bridge.dispose();
bridgeRef.current = null;
lastSentInputRef.current = void 0;
lastSentOutputRef.current = void 0;
lastSentHostContextRef.current = void 0;
widgetReadyRef.current = false;
pendingInputRef.current = void 0;
pendingOutputRef.current = void 0;
pendingHostContextRef.current = void 0;
}
};
};
$[13] = t5;
} else t5 = $[13];
const createBridge = t5;
useBridgeNotify(input, bridgeRef, widgetReadyRef, pendingInputRef, lastSentInputRef, _temp);
useBridgeNotify(output, bridgeRef, widgetReadyRef, pendingOutputRef, lastSentOutputRef, _temp2);
useBridgeNotify(hostContext, bridgeRef, widgetReadyRef, pendingHostContextRef, lastSentHostContextRef, _temp3, isSameHostContext);
const t6 = resource.meta?.csp;
let t7;
if ($[14] !== resource.html || $[15] !== t6) {
t7 = applyMcpAppCsp(resource.html, t6);
$[14] = resource.html;
$[15] = t6;
$[16] = t7;
} else t7 = $[16];
let t8;
if ($[17] !== t7) {
t8 = { html: t7 };
$[17] = t7;
$[18] = t8;
} else t8 = $[18];
const t9 = app.serverId ? `${app.serverId} ${resource.uri}` : resource.uri;
const t10 = sandbox?.product ?? DEFAULT_PRODUCT;
let t11;
if ($[19] !== sandbox || $[20] !== t10) {
t11 = {
...sandbox,
product: t10
};
$[19] = sandbox;
$[20] = t10;
$[21] = t11;
} else t11 = $[21];
let t12;
if ($[22] === Symbol.for("react.memo_cache_sentinel")) {
t12 = (err) => liveRef.current.handlers?.onError?.(err);
$[22] = t12;
} else t12 = $[22];
const t13 = resource.meta?.prefersBorder ? "" : void 0;
let t14;
if ($[23] !== app.resourceUri || $[24] !== t13) {
t14 = {
"data-mcp-app-resource": app.resourceUri,
"data-mcp-app-prefers-border": t13
};
$[23] = app.resourceUri;
$[24] = t13;
$[25] = t14;
} else t14 = $[25];
let t15;
if ($[26] !== maxHeight || $[27] !== t11 || $[28] !== t14 || $[29] !== t8 || $[30] !== t9) {
t15 = /* @__PURE__ */ jsx(SandboxHost, {
content: t8,
contentKey: t9,
sandbox: t11,
maxHeight,
createBridge,
onError: t12,
containerProps: t14
});
$[26] = maxHeight;
$[27] = t11;
$[28] = t14;
$[29] = t8;
$[30] = t9;
$[31] = t15;
} else t15 = $[31];
return t15;
}
function _temp3(b_2, v_1) {
return b_2.notifyHostContextChanged(v_1);
}
function _temp2(b_1, v_0) {
return b_1.notifyToolResult(v_0);
}
function _temp(b_0, v) {
return b_0.notifyToolInput(v);
}
export { McpAppFrame };
//# sourceMappingURL=app-frame.js.map