@frontmcp/uipack 0.12.2 → 1.0.0-beta.10
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/CLAUDE.md +56 -154
- package/README.md +367 -62
- package/adapters/base-template.d.ts +30 -0
- package/adapters/base-template.d.ts.map +1 -0
- package/adapters/cdn-info.d.ts +34 -0
- package/adapters/cdn-info.d.ts.map +1 -0
- package/adapters/constants.d.ts +18 -0
- package/adapters/constants.d.ts.map +1 -0
- package/adapters/content-detector.d.ts +19 -0
- package/adapters/content-detector.d.ts.map +1 -0
- package/adapters/content-renderers.d.ts +27 -0
- package/adapters/content-renderers.d.ts.map +1 -0
- package/adapters/index.d.ts +14 -7
- package/adapters/index.d.ts.map +1 -1
- package/adapters/index.js +2343 -426
- package/adapters/render-failure.d.ts +18 -0
- package/adapters/render-failure.d.ts.map +1 -0
- package/adapters/response-builder.d.ts +34 -104
- package/adapters/response-builder.d.ts.map +1 -1
- package/adapters/serving-mode.d.ts +28 -91
- package/adapters/serving-mode.d.ts.map +1 -1
- package/adapters/template-renderer.d.ts +50 -0
- package/adapters/template-renderer.d.ts.map +1 -0
- package/adapters/type-detector.d.ts +18 -0
- package/adapters/type-detector.d.ts.map +1 -0
- package/bridge-runtime/index.js +1 -1
- package/component/index.d.ts +14 -0
- package/component/index.d.ts.map +1 -0
- package/component/index.js +2043 -0
- package/component/loader.d.ts +36 -0
- package/component/loader.d.ts.map +1 -0
- package/component/renderer.d.ts +30 -0
- package/component/renderer.d.ts.map +1 -0
- package/component/transpiler.d.ts +49 -0
- package/component/transpiler.d.ts.map +1 -0
- package/component/types.d.ts +82 -0
- package/component/types.d.ts.map +1 -0
- package/esm/adapters/index.mjs +2337 -422
- package/esm/bridge-runtime/index.mjs +1 -1
- package/esm/component/index.mjs +2013 -0
- package/esm/index.mjs +3446 -13935
- package/esm/package.json +4 -13
- package/esm/resolver/index.mjs +661 -0
- package/esm/shell/index.mjs +1406 -0
- package/esm/types/index.mjs +11 -11
- package/esm/utils/index.mjs +53 -8
- package/index.d.ts +12 -40
- package/index.d.ts.map +1 -1
- package/index.js +3579 -14218
- package/package.json +4 -13
- package/resolver/cdn-registry.d.ts +39 -0
- package/resolver/cdn-registry.d.ts.map +1 -0
- package/resolver/esm-sh.resolver.d.ts +54 -0
- package/resolver/esm-sh.resolver.d.ts.map +1 -0
- package/resolver/import-map.d.ts +47 -0
- package/resolver/import-map.d.ts.map +1 -0
- package/resolver/import-parser.d.ts +28 -0
- package/resolver/import-parser.d.ts.map +1 -0
- package/resolver/import-rewriter.d.ts +29 -0
- package/resolver/import-rewriter.d.ts.map +1 -0
- package/resolver/index.d.ts +15 -0
- package/resolver/index.d.ts.map +1 -0
- package/resolver/index.js +708 -0
- package/resolver/types.d.ts +191 -0
- package/resolver/types.d.ts.map +1 -0
- package/shell/builder.d.ts +31 -0
- package/shell/builder.d.ts.map +1 -0
- package/shell/csp.d.ts +37 -0
- package/shell/csp.d.ts.map +1 -0
- package/shell/custom-shell-applier.d.ts +33 -0
- package/shell/custom-shell-applier.d.ts.map +1 -0
- package/shell/custom-shell-resolver.d.ts +47 -0
- package/shell/custom-shell-resolver.d.ts.map +1 -0
- package/shell/custom-shell-types.d.ts +75 -0
- package/shell/custom-shell-types.d.ts.map +1 -0
- package/shell/custom-shell-validator.d.ts +26 -0
- package/shell/custom-shell-validator.d.ts.map +1 -0
- package/shell/data-injector.d.ts +40 -0
- package/shell/data-injector.d.ts.map +1 -0
- package/shell/index.d.ts +19 -0
- package/shell/index.d.ts.map +1 -0
- package/shell/index.js +1453 -0
- package/shell/types.d.ts +54 -0
- package/shell/types.d.ts.map +1 -0
- package/types/index.d.ts +1 -3
- package/types/index.d.ts.map +1 -1
- package/types/index.js +11 -11
- package/types/ui-config.d.ts +50 -11
- package/types/ui-config.d.ts.map +1 -1
- package/types/ui-runtime.d.ts +8 -82
- package/types/ui-runtime.d.ts.map +1 -1
- package/utils/index.d.ts +9 -3
- package/utils/index.d.ts.map +1 -1
- package/utils/index.js +59 -7
- package/adapters/platform-meta.constants.d.ts +0 -26
- package/adapters/platform-meta.constants.d.ts.map +0 -1
- package/adapters/platform-meta.d.ts +0 -234
- package/adapters/platform-meta.d.ts.map +0 -1
- package/base-template/bridge.d.ts +0 -90
- package/base-template/bridge.d.ts.map +0 -1
- package/base-template/default-base-template.d.ts +0 -91
- package/base-template/default-base-template.d.ts.map +0 -1
- package/base-template/index.d.ts +0 -15
- package/base-template/index.d.ts.map +0 -1
- package/base-template/index.js +0 -1393
- package/base-template/polyfills.d.ts +0 -31
- package/base-template/polyfills.d.ts.map +0 -1
- package/base-template/theme-styles.d.ts +0 -74
- package/base-template/theme-styles.d.ts.map +0 -1
- package/build/builders/base-builder.d.ts +0 -124
- package/build/builders/base-builder.d.ts.map +0 -1
- package/build/builders/esbuild-config.d.ts +0 -94
- package/build/builders/esbuild-config.d.ts.map +0 -1
- package/build/builders/hybrid-builder.d.ts +0 -93
- package/build/builders/hybrid-builder.d.ts.map +0 -1
- package/build/builders/index.d.ts +0 -17
- package/build/builders/index.d.ts.map +0 -1
- package/build/builders/inline-builder.d.ts +0 -83
- package/build/builders/inline-builder.d.ts.map +0 -1
- package/build/builders/static-builder.d.ts +0 -78
- package/build/builders/static-builder.d.ts.map +0 -1
- package/build/builders/types.d.ts +0 -341
- package/build/builders/types.d.ts.map +0 -1
- package/build/cdn-resources.d.ts +0 -244
- package/build/cdn-resources.d.ts.map +0 -1
- package/build/hybrid-data.d.ts +0 -127
- package/build/hybrid-data.d.ts.map +0 -1
- package/build/index.d.ts +0 -299
- package/build/index.d.ts.map +0 -1
- package/build/index.js +0 -8699
- package/build/ui-components-browser.d.ts +0 -64
- package/build/ui-components-browser.d.ts.map +0 -1
- package/build/widget-manifest.d.ts +0 -362
- package/build/widget-manifest.d.ts.map +0 -1
- package/bundler/cache.d.ts +0 -173
- package/bundler/cache.d.ts.map +0 -1
- package/bundler/file-cache/component-builder.d.ts +0 -167
- package/bundler/file-cache/component-builder.d.ts.map +0 -1
- package/bundler/file-cache/hash-calculator.d.ts +0 -155
- package/bundler/file-cache/hash-calculator.d.ts.map +0 -1
- package/bundler/file-cache/index.d.ts +0 -12
- package/bundler/file-cache/index.d.ts.map +0 -1
- package/bundler/file-cache/storage/filesystem.d.ts +0 -149
- package/bundler/file-cache/storage/filesystem.d.ts.map +0 -1
- package/bundler/file-cache/storage/index.d.ts +0 -11
- package/bundler/file-cache/storage/index.d.ts.map +0 -1
- package/bundler/file-cache/storage/interface.d.ts +0 -152
- package/bundler/file-cache/storage/interface.d.ts.map +0 -1
- package/bundler/file-cache/storage/redis.d.ts +0 -139
- package/bundler/file-cache/storage/redis.d.ts.map +0 -1
- package/bundler/index.d.ts +0 -35
- package/bundler/index.d.ts.map +0 -1
- package/bundler/index.js +0 -2953
- package/bundler/sandbox/enclave-adapter.d.ts +0 -121
- package/bundler/sandbox/enclave-adapter.d.ts.map +0 -1
- package/bundler/sandbox/executor.d.ts +0 -14
- package/bundler/sandbox/executor.d.ts.map +0 -1
- package/bundler/sandbox/policy.d.ts +0 -62
- package/bundler/sandbox/policy.d.ts.map +0 -1
- package/bundler/types.d.ts +0 -702
- package/bundler/types.d.ts.map +0 -1
- package/dependency/cdn-registry.d.ts +0 -98
- package/dependency/cdn-registry.d.ts.map +0 -1
- package/dependency/import-map.d.ts +0 -186
- package/dependency/import-map.d.ts.map +0 -1
- package/dependency/import-parser.d.ts +0 -82
- package/dependency/import-parser.d.ts.map +0 -1
- package/dependency/index.d.ts +0 -17
- package/dependency/index.d.ts.map +0 -1
- package/dependency/index.js +0 -3180
- package/dependency/resolver.d.ts +0 -164
- package/dependency/resolver.d.ts.map +0 -1
- package/dependency/schemas.d.ts +0 -486
- package/dependency/schemas.d.ts.map +0 -1
- package/dependency/template-loader.d.ts +0 -204
- package/dependency/template-loader.d.ts.map +0 -1
- package/dependency/template-processor.d.ts +0 -118
- package/dependency/template-processor.d.ts.map +0 -1
- package/dependency/types.d.ts +0 -739
- package/dependency/types.d.ts.map +0 -1
- package/esm/base-template/index.mjs +0 -1359
- package/esm/build/index.mjs +0 -8601
- package/esm/bundler/index.mjs +0 -2895
- package/esm/dependency/index.mjs +0 -3068
- package/esm/handlebars/index.mjs +0 -587
- package/esm/registry/index.mjs +0 -6305
- package/esm/renderers/index.mjs +0 -1557
- package/esm/runtime/index.mjs +0 -5361
- package/esm/styles/index.mjs +0 -171
- package/esm/theme/index.mjs +0 -756
- package/esm/tool-template/index.mjs +0 -3652
- package/esm/validation/index.mjs +0 -542
- package/handlebars/expression-extractor.d.ts +0 -147
- package/handlebars/expression-extractor.d.ts.map +0 -1
- package/handlebars/helpers.d.ts +0 -339
- package/handlebars/helpers.d.ts.map +0 -1
- package/handlebars/index.d.ts +0 -195
- package/handlebars/index.d.ts.map +0 -1
- package/handlebars/index.js +0 -659
- package/preview/claude-preview.d.ts +0 -67
- package/preview/claude-preview.d.ts.map +0 -1
- package/preview/generic-preview.d.ts +0 -66
- package/preview/generic-preview.d.ts.map +0 -1
- package/preview/index.d.ts +0 -36
- package/preview/index.d.ts.map +0 -1
- package/preview/openai-preview.d.ts +0 -70
- package/preview/openai-preview.d.ts.map +0 -1
- package/preview/types.d.ts +0 -199
- package/preview/types.d.ts.map +0 -1
- package/registry/index.d.ts +0 -46
- package/registry/index.d.ts.map +0 -1
- package/registry/index.js +0 -6342
- package/registry/render-template.d.ts +0 -91
- package/registry/render-template.d.ts.map +0 -1
- package/registry/tool-ui.registry.d.ts +0 -294
- package/registry/tool-ui.registry.d.ts.map +0 -1
- package/registry/uri-utils.d.ts +0 -56
- package/registry/uri-utils.d.ts.map +0 -1
- package/renderers/cache.d.ts +0 -145
- package/renderers/cache.d.ts.map +0 -1
- package/renderers/html.renderer.d.ts +0 -123
- package/renderers/html.renderer.d.ts.map +0 -1
- package/renderers/index.d.ts +0 -36
- package/renderers/index.d.ts.map +0 -1
- package/renderers/index.js +0 -1603
- package/renderers/mdx-client.renderer.d.ts +0 -124
- package/renderers/mdx-client.renderer.d.ts.map +0 -1
- package/renderers/registry.d.ts +0 -133
- package/renderers/registry.d.ts.map +0 -1
- package/renderers/types.d.ts +0 -343
- package/renderers/types.d.ts.map +0 -1
- package/renderers/utils/detect.d.ts +0 -107
- package/renderers/utils/detect.d.ts.map +0 -1
- package/renderers/utils/hash.d.ts +0 -40
- package/renderers/utils/hash.d.ts.map +0 -1
- package/renderers/utils/index.d.ts +0 -9
- package/renderers/utils/index.d.ts.map +0 -1
- package/renderers/utils/transpiler.d.ts +0 -70
- package/renderers/utils/transpiler.d.ts.map +0 -1
- package/runtime/adapters/html.adapter.d.ts +0 -59
- package/runtime/adapters/html.adapter.d.ts.map +0 -1
- package/runtime/adapters/index.d.ts +0 -26
- package/runtime/adapters/index.d.ts.map +0 -1
- package/runtime/adapters/mdx.adapter.d.ts +0 -73
- package/runtime/adapters/mdx.adapter.d.ts.map +0 -1
- package/runtime/adapters/types.d.ts +0 -95
- package/runtime/adapters/types.d.ts.map +0 -1
- package/runtime/csp.d.ts +0 -48
- package/runtime/csp.d.ts.map +0 -1
- package/runtime/index.d.ts +0 -17
- package/runtime/index.d.ts.map +0 -1
- package/runtime/index.js +0 -5432
- package/runtime/mcp-bridge.d.ts +0 -101
- package/runtime/mcp-bridge.d.ts.map +0 -1
- package/runtime/renderer-runtime.d.ts +0 -133
- package/runtime/renderer-runtime.d.ts.map +0 -1
- package/runtime/sanitizer.d.ts +0 -180
- package/runtime/sanitizer.d.ts.map +0 -1
- package/runtime/types.d.ts +0 -415
- package/runtime/types.d.ts.map +0 -1
- package/runtime/wrapper.d.ts +0 -421
- package/runtime/wrapper.d.ts.map +0 -1
- package/styles/index.d.ts +0 -8
- package/styles/index.d.ts.map +0 -1
- package/styles/index.js +0 -222
- package/styles/variants.d.ts +0 -51
- package/styles/variants.d.ts.map +0 -1
- package/theme/cdn.d.ts +0 -195
- package/theme/cdn.d.ts.map +0 -1
- package/theme/css-to-theme.d.ts +0 -64
- package/theme/css-to-theme.d.ts.map +0 -1
- package/theme/index.d.ts +0 -19
- package/theme/index.d.ts.map +0 -1
- package/theme/index.js +0 -814
- package/theme/platforms.d.ts +0 -102
- package/theme/platforms.d.ts.map +0 -1
- package/theme/presets/github-openai.d.ts +0 -50
- package/theme/presets/github-openai.d.ts.map +0 -1
- package/theme/presets/index.d.ts +0 -11
- package/theme/presets/index.d.ts.map +0 -1
- package/theme/theme.d.ts +0 -396
- package/theme/theme.d.ts.map +0 -1
- package/tool-template/builder.d.ts +0 -213
- package/tool-template/builder.d.ts.map +0 -1
- package/tool-template/index.d.ts +0 -16
- package/tool-template/index.d.ts.map +0 -1
- package/tool-template/index.js +0 -3690
- package/validation/error-box.d.ts +0 -56
- package/validation/error-box.d.ts.map +0 -1
- package/validation/index.d.ts +0 -13
- package/validation/index.d.ts.map +0 -1
- package/validation/index.js +0 -576
- package/validation/schema-paths.d.ts +0 -118
- package/validation/schema-paths.d.ts.map +0 -1
- package/validation/template-validator.d.ts +0 -143
- package/validation/template-validator.d.ts.map +0 -1
- package/validation/wrapper.d.ts +0 -97
- package/validation/wrapper.d.ts.map +0 -1
package/adapters/index.js
CHANGED
|
@@ -20,526 +20,2443 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// libs/uipack/src/adapters/index.ts
|
|
21
21
|
var adapters_exports = {};
|
|
22
22
|
__export(adapters_exports, {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
MCP_APPS_EXTENSION_ID: () => MCP_APPS_EXTENSION_ID,
|
|
24
|
+
MCP_APPS_MIME_TYPE: () => MCP_APPS_MIME_TYPE,
|
|
25
|
+
buildCDNInfoForUIType: () => buildCDNInfoForUIType,
|
|
26
|
+
buildChartHtml: () => buildChartHtml,
|
|
27
|
+
buildMermaidHtml: () => buildMermaidHtml,
|
|
28
|
+
buildPdfHtml: () => buildPdfHtml,
|
|
26
29
|
buildToolResponseContent: () => buildToolResponseContent,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
resolveServingMode: () => resolveServingMode
|
|
30
|
+
createDefaultBaseTemplate: () => createDefaultBaseTemplate,
|
|
31
|
+
detectContentType: () => detectContentType,
|
|
32
|
+
detectUIType: () => detectUIType,
|
|
33
|
+
isUIRenderFailure: () => isUIRenderFailure,
|
|
34
|
+
renderToolTemplate: () => renderToolTemplate,
|
|
35
|
+
resolveServingMode: () => resolveServingMode,
|
|
36
|
+
wrapDetectedContent: () => wrapDetectedContent
|
|
35
37
|
});
|
|
36
38
|
module.exports = __toCommonJS(adapters_exports);
|
|
37
39
|
|
|
38
|
-
// libs/uipack/src/adapters/
|
|
39
|
-
var
|
|
40
|
-
|
|
41
|
-
inline: "inline",
|
|
42
|
-
fullscreen: "fullscreen",
|
|
43
|
-
pip: "pip",
|
|
44
|
-
// OpenAI-style aliases
|
|
45
|
-
widget: "inline",
|
|
46
|
-
panel: "fullscreen"
|
|
47
|
-
};
|
|
40
|
+
// libs/uipack/src/adapters/constants.ts
|
|
41
|
+
var MCP_APPS_MIME_TYPE = "text/html;profile=mcp-app";
|
|
42
|
+
var MCP_APPS_EXTENSION_ID = "io.modelcontextprotocol/ui";
|
|
48
43
|
|
|
49
|
-
// libs/uipack/src/adapters/
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return mimeType === "text/html+mcp" || mimeType === "text/html;profile=mcp-app";
|
|
61
|
-
}
|
|
62
|
-
function buildUIMeta(options) {
|
|
63
|
-
const { uiConfig, platformType, html, token, directUrl, rendererType, contentHash, manifestUri } = options;
|
|
64
|
-
const meta = {};
|
|
65
|
-
switch (platformType) {
|
|
66
|
-
case "openai":
|
|
67
|
-
meta["openai/html"] = html;
|
|
68
|
-
meta["openai/mimeType"] = "text/html+skybridge";
|
|
69
|
-
if (rendererType) meta["openai/type"] = rendererType;
|
|
70
|
-
if (contentHash) meta["openai/contentHash"] = contentHash;
|
|
71
|
-
if (manifestUri) meta["openai/manifestUri"] = manifestUri;
|
|
72
|
-
if (token) meta["openai/widgetToken"] = token;
|
|
73
|
-
if (directUrl) meta["openai/directUrl"] = directUrl;
|
|
74
|
-
return buildOpenAIMeta(meta, uiConfig);
|
|
75
|
-
case "ext-apps":
|
|
76
|
-
meta["ui/html"] = html;
|
|
77
|
-
meta["ui/mimeType"] = "text/html+mcp";
|
|
78
|
-
if (rendererType) meta["ui/type"] = rendererType;
|
|
79
|
-
if (contentHash) meta["ui/contentHash"] = contentHash;
|
|
80
|
-
if (manifestUri) meta["ui/manifestUri"] = manifestUri;
|
|
81
|
-
if (token) meta["ui/widgetToken"] = token;
|
|
82
|
-
if (directUrl) meta["ui/directUrl"] = directUrl;
|
|
83
|
-
return buildExtAppsMeta(meta, uiConfig);
|
|
84
|
-
case "claude":
|
|
85
|
-
case "cursor":
|
|
86
|
-
case "continue":
|
|
87
|
-
case "cody":
|
|
88
|
-
case "generic-mcp":
|
|
89
|
-
case "gemini":
|
|
90
|
-
default:
|
|
91
|
-
meta["ui/html"] = html;
|
|
92
|
-
meta["ui/mimeType"] = "text/html+mcp";
|
|
93
|
-
if (rendererType) meta["ui/type"] = rendererType;
|
|
94
|
-
if (contentHash) meta["ui/contentHash"] = contentHash;
|
|
95
|
-
if (manifestUri) meta["ui/manifestUri"] = manifestUri;
|
|
96
|
-
if (token) meta["ui/widgetToken"] = token;
|
|
97
|
-
if (directUrl) meta["ui/directUrl"] = directUrl;
|
|
98
|
-
if (platformType === "claude") {
|
|
99
|
-
return buildClaudeMeta(meta, uiConfig);
|
|
100
|
-
} else if (platformType === "gemini") {
|
|
101
|
-
return buildGeminiMeta(meta, uiConfig);
|
|
102
|
-
} else if (platformType === "cursor" || platformType === "continue" || platformType === "cody") {
|
|
103
|
-
return buildIDEMeta(meta, uiConfig);
|
|
104
|
-
} else if (platformType === "generic-mcp") {
|
|
105
|
-
return buildGenericMeta(meta, uiConfig);
|
|
106
|
-
}
|
|
107
|
-
return meta;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
function buildOpenAIMeta(meta, uiConfig) {
|
|
111
|
-
if (uiConfig.invocationStatus?.invoking) {
|
|
112
|
-
meta["openai/toolInvocation/invoking"] = uiConfig.invocationStatus.invoking;
|
|
44
|
+
// libs/uipack/src/adapters/serving-mode.ts
|
|
45
|
+
function resolveServingMode(options) {
|
|
46
|
+
const { configuredMode, platformType } = options;
|
|
47
|
+
if (platformType === "gemini") {
|
|
48
|
+
return {
|
|
49
|
+
mode: configuredMode,
|
|
50
|
+
supportsUI: false,
|
|
51
|
+
effectiveMode: null,
|
|
52
|
+
useStructuredContent: false,
|
|
53
|
+
reason: "Gemini does not support MCP Apps UI"
|
|
54
|
+
};
|
|
113
55
|
}
|
|
114
|
-
|
|
115
|
-
|
|
56
|
+
const effectiveMode = configuredMode === "auto" ? "inline" : configuredMode;
|
|
57
|
+
if (effectiveMode === "hybrid") {
|
|
58
|
+
const hybridCapable = platformType === "openai" || platformType === "ext-apps" || platformType === "cursor";
|
|
59
|
+
if (!hybridCapable) {
|
|
60
|
+
return {
|
|
61
|
+
mode: configuredMode,
|
|
62
|
+
supportsUI: false,
|
|
63
|
+
effectiveMode: null,
|
|
64
|
+
useStructuredContent: false,
|
|
65
|
+
reason: `Platform ${platformType} does not support hybrid serving mode`
|
|
66
|
+
};
|
|
67
|
+
}
|
|
116
68
|
}
|
|
117
|
-
|
|
69
|
+
const useStructuredContent = platformType === "openai" || platformType === "ext-apps" || platformType === "generic-mcp" || platformType === "unknown";
|
|
70
|
+
return {
|
|
71
|
+
mode: configuredMode,
|
|
72
|
+
supportsUI: true,
|
|
73
|
+
effectiveMode,
|
|
74
|
+
useStructuredContent,
|
|
75
|
+
reason: `Platform ${platformType} supports UI, mode: ${effectiveMode}`
|
|
76
|
+
};
|
|
118
77
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
78
|
+
|
|
79
|
+
// libs/uipack/src/adapters/response-builder.ts
|
|
80
|
+
function buildToolResponseContent(options) {
|
|
81
|
+
const { rawOutput, htmlContent, servingMode, useStructuredContent, platformType } = options;
|
|
82
|
+
const textSummary = typeof rawOutput === "string" ? rawOutput : rawOutput !== null && rawOutput !== void 0 ? JSON.stringify(rawOutput) : "";
|
|
83
|
+
const result = {
|
|
84
|
+
content: [{ type: "text", text: textSummary }]
|
|
85
|
+
};
|
|
86
|
+
if (useStructuredContent && rawOutput !== null && rawOutput !== void 0) {
|
|
87
|
+
const structured = typeof rawOutput === "object" && !Array.isArray(rawOutput) ? rawOutput : { value: rawOutput };
|
|
88
|
+
result.structuredContent = structured;
|
|
123
89
|
}
|
|
124
|
-
if (
|
|
125
|
-
|
|
90
|
+
if (htmlContent && servingMode === "inline") {
|
|
91
|
+
const htmlKey = "ui/html";
|
|
92
|
+
result._meta = {
|
|
93
|
+
[htmlKey]: htmlContent
|
|
94
|
+
};
|
|
95
|
+
result.format = "html";
|
|
126
96
|
}
|
|
127
97
|
return result;
|
|
128
98
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
99
|
+
|
|
100
|
+
// libs/uipack/src/adapters/render-failure.ts
|
|
101
|
+
function isUIRenderFailure(result) {
|
|
102
|
+
if (typeof result !== "object" || result === null) {
|
|
103
|
+
return false;
|
|
132
104
|
}
|
|
133
|
-
|
|
134
|
-
|
|
105
|
+
const rec = result;
|
|
106
|
+
return typeof rec["reason"] === "string" && !("meta" in rec);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// libs/uipack/src/shell/csp.ts
|
|
110
|
+
var DEFAULT_CDN_DOMAINS = [
|
|
111
|
+
"https://cdn.jsdelivr.net",
|
|
112
|
+
"https://cdnjs.cloudflare.com",
|
|
113
|
+
"https://fonts.googleapis.com",
|
|
114
|
+
"https://fonts.gstatic.com",
|
|
115
|
+
"https://esm.sh"
|
|
116
|
+
];
|
|
117
|
+
var DEFAULT_CSP_DIRECTIVES = [
|
|
118
|
+
"default-src 'none'",
|
|
119
|
+
`script-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
120
|
+
`style-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
121
|
+
`img-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
122
|
+
`font-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
123
|
+
`connect-src ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
124
|
+
"object-src 'self' data:"
|
|
125
|
+
];
|
|
126
|
+
function buildCSPDirectives(csp) {
|
|
127
|
+
if (!csp) {
|
|
128
|
+
return [...DEFAULT_CSP_DIRECTIVES];
|
|
135
129
|
}
|
|
136
|
-
|
|
137
|
-
|
|
130
|
+
const validResourceDomains = sanitizeCSPDomains(csp.resourceDomains);
|
|
131
|
+
const validConnectDomains = sanitizeCSPDomains(csp.connectDomains);
|
|
132
|
+
const allResourceDomains = [.../* @__PURE__ */ new Set([...DEFAULT_CDN_DOMAINS, ...validResourceDomains])];
|
|
133
|
+
const directives = [
|
|
134
|
+
"default-src 'none'",
|
|
135
|
+
`script-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`,
|
|
136
|
+
`style-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`
|
|
137
|
+
];
|
|
138
|
+
const imgSources = ["'self'", "data:", ...allResourceDomains];
|
|
139
|
+
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
140
|
+
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
141
|
+
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
142
|
+
if (validConnectDomains.length) {
|
|
143
|
+
directives.push(`connect-src ${validConnectDomains.join(" ")}`);
|
|
144
|
+
} else {
|
|
145
|
+
directives.push(`connect-src ${allResourceDomains.join(" ")}`);
|
|
138
146
|
}
|
|
139
|
-
|
|
140
|
-
|
|
147
|
+
directives.push("object-src 'self' data:");
|
|
148
|
+
return directives;
|
|
149
|
+
}
|
|
150
|
+
function buildCSPMetaTag(csp) {
|
|
151
|
+
const directives = buildCSPDirectives(csp);
|
|
152
|
+
const content = directives.join("; ");
|
|
153
|
+
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
154
|
+
}
|
|
155
|
+
function validateCSPDomain(domain) {
|
|
156
|
+
if (domain.startsWith("https://*.")) {
|
|
157
|
+
const rest = domain.slice(10);
|
|
158
|
+
return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
|
|
141
159
|
}
|
|
142
|
-
|
|
143
|
-
|
|
160
|
+
try {
|
|
161
|
+
const url = new URL(domain);
|
|
162
|
+
return url.protocol === "https:";
|
|
163
|
+
} catch {
|
|
164
|
+
return false;
|
|
144
165
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
166
|
+
}
|
|
167
|
+
function sanitizeCSPDomains(domains) {
|
|
168
|
+
if (!domains) return [];
|
|
169
|
+
const valid = [];
|
|
170
|
+
for (const domain of domains) {
|
|
171
|
+
if (validateCSPDomain(domain)) {
|
|
172
|
+
valid.push(domain);
|
|
173
|
+
} else {
|
|
174
|
+
console.warn(`Invalid CSP domain ignored: ${domain}`);
|
|
149
175
|
}
|
|
150
|
-
|
|
151
|
-
|
|
176
|
+
}
|
|
177
|
+
return valid;
|
|
178
|
+
}
|
|
179
|
+
function escapeAttribute(str) {
|
|
180
|
+
return str.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// libs/uipack/src/utils/index.ts
|
|
184
|
+
function escapeHtml(str) {
|
|
185
|
+
if (str === null || str === void 0) {
|
|
186
|
+
return "";
|
|
187
|
+
}
|
|
188
|
+
const s = String(str);
|
|
189
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
190
|
+
}
|
|
191
|
+
function escapeScriptClose(jsonString) {
|
|
192
|
+
return jsonString.replace(/<\//g, "<\\/");
|
|
193
|
+
}
|
|
194
|
+
function safeJsonForScript(value) {
|
|
195
|
+
if (value === void 0) {
|
|
196
|
+
return "null";
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
const jsonString = JSON.stringify(value, (_key, val) => {
|
|
200
|
+
if (typeof val === "bigint") {
|
|
201
|
+
return val.toString();
|
|
202
|
+
}
|
|
203
|
+
return val;
|
|
204
|
+
});
|
|
205
|
+
if (jsonString === void 0) {
|
|
206
|
+
return "null";
|
|
152
207
|
}
|
|
153
|
-
|
|
154
|
-
|
|
208
|
+
return escapeScriptClose(jsonString);
|
|
209
|
+
} catch {
|
|
210
|
+
return '{"error":"Value could not be serialized"}';
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// libs/uipack/src/shell/data-injector.ts
|
|
215
|
+
function buildDataInjectionScript(options) {
|
|
216
|
+
const { toolName, input, output, structuredContent } = options;
|
|
217
|
+
const lines = [
|
|
218
|
+
`window.__mcpToolName = ${safeJsonForScript(toolName)};`,
|
|
219
|
+
`window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
|
|
220
|
+
`window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
|
|
221
|
+
`window.__mcpStructuredContent = ${safeJsonForScript(structuredContent ?? null)};`
|
|
222
|
+
];
|
|
223
|
+
return `<script>
|
|
224
|
+
${lines.join("\n")}
|
|
225
|
+
</script>`;
|
|
226
|
+
}
|
|
227
|
+
var _uniqueIdCounter = 0;
|
|
228
|
+
function createTemplateHelpers() {
|
|
229
|
+
return {
|
|
230
|
+
escapeHtml: (str) => escapeHtml(str),
|
|
231
|
+
formatDate: (date, format) => {
|
|
232
|
+
const d = date instanceof Date ? date : new Date(date);
|
|
233
|
+
if (isNaN(d.getTime())) return String(date);
|
|
234
|
+
if (format === "iso") return d.toISOString();
|
|
235
|
+
if (format === "date") return d.toLocaleDateString();
|
|
236
|
+
if (format === "time") return d.toLocaleTimeString();
|
|
237
|
+
return d.toLocaleString();
|
|
238
|
+
},
|
|
239
|
+
formatCurrency: (amount, currency = "USD") => {
|
|
240
|
+
return new Intl.NumberFormat("en-US", {
|
|
241
|
+
style: "currency",
|
|
242
|
+
currency
|
|
243
|
+
}).format(amount);
|
|
244
|
+
},
|
|
245
|
+
uniqueId: (prefix = "mcp") => {
|
|
246
|
+
return `${prefix}-${++_uniqueIdCounter}`;
|
|
247
|
+
},
|
|
248
|
+
jsonEmbed: (data) => {
|
|
249
|
+
return escapeScriptClose(JSON.stringify(data));
|
|
155
250
|
}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// libs/uipack/src/bridge-runtime/iife-generator.ts
|
|
255
|
+
function generateBridgeIIFE(options = {}) {
|
|
256
|
+
const { debug = false, trustedOrigins = [], minify = false } = options;
|
|
257
|
+
const adapters = options.adapters || ["openai", "ext-apps", "claude", "gemini", "generic"];
|
|
258
|
+
const parts = [];
|
|
259
|
+
parts.push("(function() {");
|
|
260
|
+
parts.push('"use strict";');
|
|
261
|
+
parts.push("");
|
|
262
|
+
if (debug) {
|
|
263
|
+
parts.push('function log(msg) { console.log("[FrontMcpBridge] " + msg); }');
|
|
264
|
+
} else {
|
|
265
|
+
parts.push("function log() {}");
|
|
266
|
+
}
|
|
267
|
+
parts.push("");
|
|
268
|
+
parts.push("var DEFAULT_SAFE_AREA = { top: 0, bottom: 0, left: 0, right: 0 };");
|
|
269
|
+
parts.push("");
|
|
270
|
+
parts.push(generateContextDetection());
|
|
271
|
+
parts.push("");
|
|
272
|
+
parts.push(generateBaseCapabilities());
|
|
273
|
+
parts.push("");
|
|
274
|
+
if (adapters.includes("openai")) {
|
|
275
|
+
parts.push(generateOpenAIAdapter());
|
|
276
|
+
parts.push("");
|
|
277
|
+
}
|
|
278
|
+
if (adapters.includes("ext-apps")) {
|
|
279
|
+
parts.push(generateExtAppsAdapter(trustedOrigins));
|
|
280
|
+
parts.push("");
|
|
281
|
+
}
|
|
282
|
+
if (adapters.includes("claude")) {
|
|
283
|
+
parts.push(generateClaudeAdapter());
|
|
284
|
+
parts.push("");
|
|
285
|
+
}
|
|
286
|
+
if (adapters.includes("gemini")) {
|
|
287
|
+
parts.push(generateGeminiAdapter());
|
|
288
|
+
parts.push("");
|
|
156
289
|
}
|
|
157
|
-
|
|
290
|
+
if (adapters.includes("generic")) {
|
|
291
|
+
parts.push(generateGenericAdapter());
|
|
292
|
+
parts.push("");
|
|
293
|
+
}
|
|
294
|
+
parts.push(generatePlatformDetection(adapters));
|
|
295
|
+
parts.push("");
|
|
296
|
+
parts.push(generateBridgeClass());
|
|
297
|
+
parts.push("");
|
|
298
|
+
parts.push("var bridge = new FrontMcpBridge();");
|
|
299
|
+
parts.push("bridge.initialize().then(function() {");
|
|
300
|
+
parts.push(' log("Bridge initialized with adapter: " + bridge.adapterId);');
|
|
301
|
+
parts.push(' window.dispatchEvent(new CustomEvent("bridge:ready", { detail: { adapter: bridge.adapterId } }));');
|
|
302
|
+
parts.push("}).catch(function(err) {");
|
|
303
|
+
parts.push(' console.error("[FrontMcpBridge] Init failed:", err);');
|
|
304
|
+
parts.push(' window.dispatchEvent(new CustomEvent("bridge:error", { detail: { error: err } }));');
|
|
305
|
+
parts.push("});");
|
|
306
|
+
parts.push("");
|
|
307
|
+
parts.push("window.FrontMcpBridge = bridge;");
|
|
308
|
+
parts.push("})();");
|
|
309
|
+
const code = parts.join("\n");
|
|
310
|
+
if (minify) {
|
|
311
|
+
return minifyJS(code);
|
|
312
|
+
}
|
|
313
|
+
return code;
|
|
158
314
|
}
|
|
159
|
-
function
|
|
160
|
-
|
|
161
|
-
|
|
315
|
+
function generateContextDetection() {
|
|
316
|
+
return `
|
|
317
|
+
function detectTheme() {
|
|
318
|
+
if (typeof window !== 'undefined' && window.matchMedia) {
|
|
319
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
162
320
|
}
|
|
163
|
-
return
|
|
321
|
+
return 'light';
|
|
164
322
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
323
|
+
|
|
324
|
+
function detectLocale() {
|
|
325
|
+
if (typeof navigator !== 'undefined') {
|
|
326
|
+
return navigator.language || 'en-US';
|
|
168
327
|
}
|
|
169
|
-
return
|
|
328
|
+
return 'en-US';
|
|
170
329
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (
|
|
174
|
-
|
|
330
|
+
|
|
331
|
+
function detectUserAgent() {
|
|
332
|
+
if (typeof navigator === 'undefined') {
|
|
333
|
+
return { type: 'web', hover: true, touch: false };
|
|
175
334
|
}
|
|
176
|
-
|
|
177
|
-
|
|
335
|
+
var ua = navigator.userAgent || '';
|
|
336
|
+
var isMobile = /iPhone|iPad|iPod|Android/i.test(ua);
|
|
337
|
+
var hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
338
|
+
var hasHover = window.matchMedia && window.matchMedia('(hover: hover)').matches;
|
|
339
|
+
return { type: isMobile ? 'mobile' : 'web', hover: hasHover !== false, touch: hasTouch };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function detectViewport() {
|
|
343
|
+
if (typeof window !== 'undefined') {
|
|
344
|
+
return { width: window.innerWidth, height: window.innerHeight };
|
|
178
345
|
}
|
|
179
|
-
return
|
|
346
|
+
return undefined;
|
|
180
347
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
348
|
+
|
|
349
|
+
function readInjectedData() {
|
|
350
|
+
var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
|
|
351
|
+
if (typeof window !== 'undefined') {
|
|
352
|
+
if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
|
|
353
|
+
if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
|
|
354
|
+
if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
|
|
355
|
+
}
|
|
356
|
+
return data;
|
|
357
|
+
}
|
|
358
|
+
`.trim();
|
|
359
|
+
}
|
|
360
|
+
function generateBaseCapabilities() {
|
|
361
|
+
return `
|
|
362
|
+
var DEFAULT_CAPABILITIES = {
|
|
363
|
+
canCallTools: false,
|
|
364
|
+
canSendMessages: false,
|
|
365
|
+
canOpenLinks: false,
|
|
366
|
+
canPersistState: true,
|
|
367
|
+
hasNetworkAccess: true,
|
|
368
|
+
supportsDisplayModes: false,
|
|
369
|
+
supportsTheme: true
|
|
370
|
+
};
|
|
371
|
+
`.trim();
|
|
372
|
+
}
|
|
373
|
+
function generateOpenAIAdapter() {
|
|
374
|
+
return `
|
|
375
|
+
var OpenAIAdapter = {
|
|
376
|
+
id: 'openai',
|
|
377
|
+
name: 'OpenAI ChatGPT',
|
|
378
|
+
priority: 100,
|
|
379
|
+
capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
|
|
380
|
+
canCallTools: true,
|
|
381
|
+
canSendMessages: true,
|
|
382
|
+
canOpenLinks: true,
|
|
383
|
+
supportsDisplayModes: true
|
|
384
|
+
}),
|
|
385
|
+
canHandle: function() {
|
|
386
|
+
if (typeof window === 'undefined') return false;
|
|
387
|
+
// Check for window.openai.callTool (the actual OpenAI SDK API)
|
|
388
|
+
if (window.openai && typeof window.openai.callTool === 'function') return true;
|
|
389
|
+
// Also check if we're being injected with tool metadata (OpenAI injects toolOutput)
|
|
390
|
+
if (window.openai && (window.openai.toolOutput !== undefined || window.openai.toolInput !== undefined)) return true;
|
|
391
|
+
return false;
|
|
392
|
+
},
|
|
393
|
+
initialize: function(context) {
|
|
394
|
+
var sdk = window.openai;
|
|
395
|
+
context.sdk = sdk;
|
|
396
|
+
// OpenAI SDK exposes theme and displayMode directly as properties
|
|
397
|
+
if (sdk.theme) {
|
|
398
|
+
context.hostContext.theme = sdk.theme;
|
|
186
399
|
}
|
|
187
|
-
if (
|
|
188
|
-
|
|
400
|
+
if (sdk.displayMode) {
|
|
401
|
+
context.hostContext.displayMode = sdk.displayMode;
|
|
189
402
|
}
|
|
190
|
-
|
|
191
|
-
|
|
403
|
+
// Note: OpenAI SDK does not have an onContextChange equivalent
|
|
404
|
+
return Promise.resolve();
|
|
405
|
+
},
|
|
406
|
+
callTool: function(context, name, args) {
|
|
407
|
+
return context.sdk.callTool(name, args);
|
|
408
|
+
},
|
|
409
|
+
sendMessage: function(context, content) {
|
|
410
|
+
if (typeof context.sdk.sendFollowUpMessage === 'function') {
|
|
411
|
+
return context.sdk.sendFollowUpMessage(content);
|
|
192
412
|
}
|
|
413
|
+
return Promise.reject(new Error('Messages not supported'));
|
|
414
|
+
},
|
|
415
|
+
openLink: function(context, url) {
|
|
416
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
417
|
+
return Promise.resolve();
|
|
418
|
+
},
|
|
419
|
+
requestDisplayMode: function(context, mode) {
|
|
420
|
+
return Promise.resolve();
|
|
421
|
+
},
|
|
422
|
+
requestClose: function(context) {
|
|
423
|
+
return Promise.resolve();
|
|
193
424
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
425
|
+
};
|
|
426
|
+
`.trim();
|
|
427
|
+
}
|
|
428
|
+
function generateExtAppsAdapter(trustedOrigins) {
|
|
429
|
+
const originsArray = trustedOrigins.length > 0 ? JSON.stringify(trustedOrigins) : "[]";
|
|
430
|
+
return `
|
|
431
|
+
var ExtAppsAdapter = {
|
|
432
|
+
id: 'ext-apps',
|
|
433
|
+
name: 'ext-apps (SEP-1865)',
|
|
434
|
+
priority: 80,
|
|
435
|
+
capabilities: Object.assign({}, DEFAULT_CAPABILITIES, { canPersistState: true, hasNetworkAccess: true }),
|
|
436
|
+
trustedOrigins: ${originsArray},
|
|
437
|
+
trustedOrigin: null,
|
|
438
|
+
originTrustPending: false,
|
|
439
|
+
pendingRequests: {},
|
|
440
|
+
requestId: 0,
|
|
441
|
+
hostCapabilities: {},
|
|
442
|
+
canHandle: function() {
|
|
443
|
+
if (typeof window === 'undefined') return false;
|
|
444
|
+
if (window.parent === window) return false;
|
|
445
|
+
|
|
446
|
+
// Check for OpenAI SDK - defer to OpenAIAdapter
|
|
447
|
+
if (window.openai && window.openai.canvas) return false;
|
|
448
|
+
if (window.openai && typeof window.openai.callTool === 'function') return false;
|
|
449
|
+
|
|
450
|
+
// Explicit ext-apps marker
|
|
451
|
+
if (window.__mcpPlatform === 'ext-apps') return true;
|
|
452
|
+
if (window.__extAppsInitialized) return true;
|
|
453
|
+
|
|
454
|
+
// Claude MCP Apps mode (2026+) - uses ext-apps protocol
|
|
455
|
+
if (window.__mcpAppsEnabled) return true;
|
|
456
|
+
|
|
457
|
+
// Legacy Claude detection - defer to ClaudeAdapter
|
|
458
|
+
if (window.claude) return false;
|
|
459
|
+
if (window.__claudeArtifact) return false;
|
|
460
|
+
if (window.__mcpPlatform === 'claude') return false;
|
|
461
|
+
if (typeof location !== 'undefined') {
|
|
462
|
+
try {
|
|
463
|
+
var url = new URL(location.href);
|
|
464
|
+
var hostname = url.hostname.toLowerCase();
|
|
465
|
+
var isClaudeHost = hostname === 'claude.ai' || (hostname.length > 10 && hostname.slice(-10) === '.claude.ai');
|
|
466
|
+
var isAnthropicHost = hostname === 'anthropic.com' || (hostname.length > 14 && hostname.slice(-14) === '.anthropic.com');
|
|
467
|
+
if (isClaudeHost || isAnthropicHost) return false;
|
|
468
|
+
} catch (e) {
|
|
469
|
+
// If URL parsing fails, fall through to other checks
|
|
470
|
+
}
|
|
198
471
|
}
|
|
472
|
+
|
|
473
|
+
// Do NOT default to true for any iframe
|
|
474
|
+
return false;
|
|
475
|
+
},
|
|
476
|
+
initialize: function(context) {
|
|
477
|
+
var self = this;
|
|
478
|
+
context.extApps = this;
|
|
479
|
+
|
|
480
|
+
window.addEventListener('message', function(event) {
|
|
481
|
+
self.handleMessage(context, event);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
return self.performHandshake(context);
|
|
485
|
+
},
|
|
486
|
+
handleMessage: function(context, event) {
|
|
487
|
+
if (!this.isOriginTrusted(event.origin)) return;
|
|
488
|
+
var data = event.data;
|
|
489
|
+
if (!data || typeof data !== 'object' || data.jsonrpc !== '2.0') return;
|
|
490
|
+
|
|
491
|
+
if ('id' in data && (data.result !== undefined || data.error !== undefined)) {
|
|
492
|
+
var pending = this.pendingRequests[data.id];
|
|
493
|
+
if (pending) {
|
|
494
|
+
clearTimeout(pending.timeout);
|
|
495
|
+
delete this.pendingRequests[data.id];
|
|
496
|
+
if (data.error) {
|
|
497
|
+
pending.reject(new Error(data.error.message + ' (code: ' + data.error.code + ')'));
|
|
498
|
+
} else {
|
|
499
|
+
pending.resolve(data.result);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if ('method' in data && !('id' in data)) {
|
|
506
|
+
this.handleNotification(context, data);
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
handleNotification: function(context, notification) {
|
|
510
|
+
var params = notification.params || {};
|
|
511
|
+
switch (notification.method) {
|
|
512
|
+
case 'ui/notifications/tool-input':
|
|
513
|
+
context.toolInput = params.arguments || {};
|
|
514
|
+
window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
|
|
515
|
+
break;
|
|
516
|
+
case 'ui/notifications/tool-input-partial':
|
|
517
|
+
// Streaming: merge partial input with existing
|
|
518
|
+
context.toolInput = Object.assign({}, context.toolInput, params.arguments || {});
|
|
519
|
+
window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
|
|
520
|
+
break;
|
|
521
|
+
case 'ui/notifications/tool-result':
|
|
522
|
+
context.toolOutput = params.content;
|
|
523
|
+
context.structuredContent = params.structuredContent;
|
|
524
|
+
context.notifyToolResult(params.content);
|
|
525
|
+
window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
|
|
526
|
+
break;
|
|
527
|
+
case 'ui/notifications/host-context-changed':
|
|
528
|
+
Object.assign(context.hostContext, params);
|
|
529
|
+
context.notifyContextChange(params);
|
|
530
|
+
break;
|
|
531
|
+
case 'ui/notifications/cancelled':
|
|
532
|
+
window.dispatchEvent(new CustomEvent('tool:cancelled', { detail: { reason: params.reason } }));
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
},
|
|
536
|
+
isOriginTrusted: function(origin) {
|
|
537
|
+
if (this.trustedOrigins.length > 0) {
|
|
538
|
+
return this.trustedOrigins.indexOf(origin) !== -1;
|
|
539
|
+
}
|
|
540
|
+
// Trust-on-first-use: trust first message origin.
|
|
541
|
+
// SECURITY WARNING: For production, always configure trustedOrigins.
|
|
542
|
+
if (!this.trustedOrigin) {
|
|
543
|
+
// Guard against race condition where multiple messages arrive simultaneously
|
|
544
|
+
if (this.originTrustPending) {
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
if (window.parent !== window && origin) {
|
|
548
|
+
this.originTrustPending = true;
|
|
549
|
+
this.trustedOrigin = origin;
|
|
550
|
+
this.originTrustPending = false; // Reset after successful trust establishment
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
return this.trustedOrigin === origin;
|
|
556
|
+
},
|
|
557
|
+
sendRequest: function(method, params) {
|
|
558
|
+
var self = this;
|
|
559
|
+
return new Promise(function(resolve, reject) {
|
|
560
|
+
// Security: Require trusted origin before sending requests to prevent message leaks
|
|
561
|
+
if (!self.trustedOrigin && self.trustedOrigins.length === 0) {
|
|
562
|
+
reject(new Error('Cannot send request: no trusted origin established'));
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
var id = ++self.requestId;
|
|
567
|
+
var timeout = setTimeout(function() {
|
|
568
|
+
delete self.pendingRequests[id];
|
|
569
|
+
reject(new Error('Request ' + method + ' timed out'));
|
|
570
|
+
}, 10000);
|
|
571
|
+
|
|
572
|
+
self.pendingRequests[id] = { resolve: resolve, reject: reject, timeout: timeout };
|
|
573
|
+
|
|
574
|
+
var targetOrigin = self.trustedOrigin || self.trustedOrigins[0];
|
|
575
|
+
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
576
|
+
});
|
|
577
|
+
},
|
|
578
|
+
performHandshake: function(context) {
|
|
579
|
+
var self = this;
|
|
580
|
+
var params = {
|
|
581
|
+
appInfo: { name: 'FrontMCP Widget', version: '1.0.0' },
|
|
582
|
+
appCapabilities: { tools: { listChanged: false } },
|
|
583
|
+
protocolVersion: '2024-11-05'
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
return this.sendRequest('ui/initialize', params).then(function(result) {
|
|
587
|
+
self.hostCapabilities = result.hostCapabilities || {};
|
|
588
|
+
self.capabilities = Object.assign({}, self.capabilities, {
|
|
589
|
+
canCallTools: Boolean(self.hostCapabilities.serverToolProxy),
|
|
590
|
+
canSendMessages: true,
|
|
591
|
+
canOpenLinks: Boolean(self.hostCapabilities.openLink),
|
|
592
|
+
supportsDisplayModes: true
|
|
593
|
+
});
|
|
594
|
+
if (result.hostContext) {
|
|
595
|
+
Object.assign(context.hostContext, result.hostContext);
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
},
|
|
599
|
+
callTool: function(context, name, args) {
|
|
600
|
+
if (!this.hostCapabilities.serverToolProxy) {
|
|
601
|
+
return Promise.reject(new Error('Server tool proxy not supported'));
|
|
602
|
+
}
|
|
603
|
+
return this.sendRequest('ui/callServerTool', { name: name, arguments: args });
|
|
604
|
+
},
|
|
605
|
+
sendMessage: function(context, content) {
|
|
606
|
+
return this.sendRequest('ui/message', { content: content });
|
|
607
|
+
},
|
|
608
|
+
openLink: function(context, url) {
|
|
609
|
+
if (!this.hostCapabilities.openLink) {
|
|
610
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
611
|
+
return Promise.resolve();
|
|
612
|
+
}
|
|
613
|
+
return this.sendRequest('ui/openLink', { url: url });
|
|
614
|
+
},
|
|
615
|
+
requestDisplayMode: function(context, mode) {
|
|
616
|
+
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
617
|
+
},
|
|
618
|
+
requestClose: function(context) {
|
|
619
|
+
return this.sendRequest('ui/close', {});
|
|
620
|
+
},
|
|
621
|
+
// Extended ext-apps methods (full specification)
|
|
622
|
+
updateModelContext: function(context, data, merge) {
|
|
623
|
+
if (!this.hostCapabilities.modelContextUpdate) {
|
|
624
|
+
return Promise.reject(new Error('Model context update not supported'));
|
|
625
|
+
}
|
|
626
|
+
return this.sendRequest('ui/updateModelContext', { context: data, merge: merge !== false });
|
|
627
|
+
},
|
|
628
|
+
log: function(context, level, message, data) {
|
|
629
|
+
if (!this.hostCapabilities.logging) {
|
|
630
|
+
// Fallback to console logging if host doesn't support it
|
|
631
|
+
var logFn = console[level] || console.log;
|
|
632
|
+
logFn('[Widget] ' + message, data);
|
|
633
|
+
return Promise.resolve();
|
|
634
|
+
}
|
|
635
|
+
return this.sendRequest('ui/log', { level: level, message: message, data: data });
|
|
636
|
+
},
|
|
637
|
+
registerTool: function(context, name, description, inputSchema) {
|
|
638
|
+
if (!this.hostCapabilities.widgetTools) {
|
|
639
|
+
return Promise.reject(new Error('Widget tool registration not supported'));
|
|
640
|
+
}
|
|
641
|
+
return this.sendRequest('ui/registerTool', { name: name, description: description, inputSchema: inputSchema });
|
|
642
|
+
},
|
|
643
|
+
unregisterTool: function(context, name) {
|
|
644
|
+
if (!this.hostCapabilities.widgetTools) {
|
|
645
|
+
return Promise.reject(new Error('Widget tool unregistration not supported'));
|
|
646
|
+
}
|
|
647
|
+
return this.sendRequest('ui/unregisterTool', { name: name });
|
|
199
648
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
649
|
+
};
|
|
650
|
+
`.trim();
|
|
651
|
+
}
|
|
652
|
+
function generateClaudeAdapter() {
|
|
653
|
+
return `
|
|
654
|
+
var ClaudeAdapter = {
|
|
655
|
+
id: 'claude',
|
|
656
|
+
name: 'Claude (Anthropic)',
|
|
657
|
+
priority: 60,
|
|
658
|
+
capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
|
|
659
|
+
canCallTools: false,
|
|
660
|
+
canSendMessages: false,
|
|
661
|
+
canOpenLinks: true,
|
|
662
|
+
hasNetworkAccess: false,
|
|
663
|
+
supportsDisplayModes: false
|
|
664
|
+
}),
|
|
665
|
+
canHandle: function() {
|
|
666
|
+
if (typeof window === 'undefined') return false;
|
|
667
|
+
|
|
668
|
+
// If MCP Apps is enabled, let ext-apps adapter handle it
|
|
669
|
+
if (window.__mcpAppsEnabled) return false;
|
|
670
|
+
if (window.__mcpPlatform === 'ext-apps') return false;
|
|
671
|
+
if (window.__extAppsInitialized) return false;
|
|
672
|
+
|
|
673
|
+
// Legacy Claude detection
|
|
674
|
+
if (window.__mcpPlatform === 'claude') return true;
|
|
675
|
+
if (window.claude) return true;
|
|
676
|
+
if (window.__claudeArtifact) return true;
|
|
677
|
+
if (typeof location !== 'undefined') {
|
|
678
|
+
try {
|
|
679
|
+
var url = new URL(location.href);
|
|
680
|
+
var hostname = url.hostname.toLowerCase();
|
|
681
|
+
var isClaudeHost = hostname === 'claude.ai' || (hostname.length > 10 && hostname.slice(-10) === '.claude.ai');
|
|
682
|
+
var isAnthropicHost = hostname === 'anthropic.com' || (hostname.length > 14 && hostname.slice(-14) === '.anthropic.com');
|
|
683
|
+
if (isClaudeHost || isAnthropicHost) return true;
|
|
684
|
+
} catch (e) {
|
|
685
|
+
// If URL parsing fails, fall through
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return false;
|
|
689
|
+
},
|
|
690
|
+
initialize: function(context) {
|
|
691
|
+
return Promise.resolve();
|
|
692
|
+
},
|
|
693
|
+
callTool: function() {
|
|
694
|
+
return Promise.reject(new Error('Tool calls not supported in Claude'));
|
|
695
|
+
},
|
|
696
|
+
sendMessage: function() {
|
|
697
|
+
return Promise.reject(new Error('Messages not supported in Claude'));
|
|
698
|
+
},
|
|
699
|
+
openLink: function(context, url) {
|
|
700
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
701
|
+
return Promise.resolve();
|
|
702
|
+
},
|
|
703
|
+
requestDisplayMode: function() {
|
|
704
|
+
return Promise.resolve();
|
|
705
|
+
},
|
|
706
|
+
requestClose: function() {
|
|
707
|
+
return Promise.resolve();
|
|
205
708
|
}
|
|
206
|
-
|
|
709
|
+
};
|
|
710
|
+
`.trim();
|
|
207
711
|
}
|
|
208
|
-
function
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
712
|
+
function generateGeminiAdapter() {
|
|
713
|
+
return `
|
|
714
|
+
var GeminiAdapter = {
|
|
715
|
+
id: 'gemini',
|
|
716
|
+
name: 'Google Gemini',
|
|
717
|
+
priority: 40,
|
|
718
|
+
capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
|
|
719
|
+
canOpenLinks: true,
|
|
720
|
+
hasNetworkAccess: true
|
|
721
|
+
}),
|
|
722
|
+
canHandle: function() {
|
|
723
|
+
if (typeof window === 'undefined') return false;
|
|
724
|
+
if (window.__mcpPlatform === 'gemini') return true;
|
|
725
|
+
if (window.gemini) return true;
|
|
726
|
+
if (typeof location !== 'undefined') {
|
|
727
|
+
var href = location.href;
|
|
728
|
+
if (href.indexOf('gemini.google.com') !== -1 || href.indexOf('bard.google.com') !== -1) return true;
|
|
214
729
|
}
|
|
215
|
-
|
|
216
|
-
|
|
730
|
+
return false;
|
|
731
|
+
},
|
|
732
|
+
initialize: function(context) {
|
|
733
|
+
if (window.gemini && window.gemini.ui && window.gemini.ui.getTheme) {
|
|
734
|
+
context.hostContext.theme = window.gemini.ui.getTheme() === 'dark' ? 'dark' : 'light';
|
|
217
735
|
}
|
|
218
|
-
|
|
219
|
-
|
|
736
|
+
return Promise.resolve();
|
|
737
|
+
},
|
|
738
|
+
callTool: function() {
|
|
739
|
+
return Promise.reject(new Error('Tool calls not supported in Gemini'));
|
|
740
|
+
},
|
|
741
|
+
sendMessage: function(context, content) {
|
|
742
|
+
if (window.gemini && window.gemini.ui && window.gemini.ui.sendMessage) {
|
|
743
|
+
return window.gemini.ui.sendMessage(content);
|
|
220
744
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (
|
|
225
|
-
|
|
745
|
+
return Promise.reject(new Error('Messages not supported in Gemini'));
|
|
746
|
+
},
|
|
747
|
+
openLink: function(context, url) {
|
|
748
|
+
if (window.gemini && window.gemini.ui && window.gemini.ui.openLink) {
|
|
749
|
+
return window.gemini.ui.openLink(url);
|
|
226
750
|
}
|
|
751
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
752
|
+
return Promise.resolve();
|
|
753
|
+
},
|
|
754
|
+
requestDisplayMode: function() {
|
|
755
|
+
return Promise.resolve();
|
|
756
|
+
},
|
|
757
|
+
requestClose: function() {
|
|
758
|
+
return Promise.resolve();
|
|
227
759
|
}
|
|
228
|
-
|
|
229
|
-
|
|
760
|
+
};
|
|
761
|
+
`.trim();
|
|
762
|
+
}
|
|
763
|
+
function generateGenericAdapter() {
|
|
764
|
+
return `
|
|
765
|
+
var GenericAdapter = {
|
|
766
|
+
id: 'generic',
|
|
767
|
+
name: 'Generic Web',
|
|
768
|
+
priority: 0,
|
|
769
|
+
capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
|
|
770
|
+
canOpenLinks: true,
|
|
771
|
+
hasNetworkAccess: true
|
|
772
|
+
}),
|
|
773
|
+
canHandle: function() {
|
|
774
|
+
return typeof window !== 'undefined';
|
|
775
|
+
},
|
|
776
|
+
initialize: function(context) {
|
|
777
|
+
return Promise.resolve();
|
|
778
|
+
},
|
|
779
|
+
callTool: function() {
|
|
780
|
+
return Promise.reject(new Error('Tool calls not supported'));
|
|
781
|
+
},
|
|
782
|
+
sendMessage: function() {
|
|
783
|
+
return Promise.reject(new Error('Messages not supported'));
|
|
784
|
+
},
|
|
785
|
+
openLink: function(context, url) {
|
|
786
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
787
|
+
return Promise.resolve();
|
|
788
|
+
},
|
|
789
|
+
requestDisplayMode: function() {
|
|
790
|
+
return Promise.resolve();
|
|
791
|
+
},
|
|
792
|
+
requestClose: function() {
|
|
793
|
+
return Promise.resolve();
|
|
230
794
|
}
|
|
231
|
-
|
|
232
|
-
|
|
795
|
+
};
|
|
796
|
+
`.trim();
|
|
797
|
+
}
|
|
798
|
+
function generatePlatformDetection(adapters) {
|
|
799
|
+
const adapterVars = adapters.map((a) => {
|
|
800
|
+
switch (a) {
|
|
801
|
+
case "openai":
|
|
802
|
+
return "OpenAIAdapter";
|
|
803
|
+
case "ext-apps":
|
|
804
|
+
return "ExtAppsAdapter";
|
|
805
|
+
case "claude":
|
|
806
|
+
return "ClaudeAdapter";
|
|
807
|
+
case "gemini":
|
|
808
|
+
return "GeminiAdapter";
|
|
809
|
+
case "generic":
|
|
810
|
+
return "GenericAdapter";
|
|
811
|
+
default:
|
|
812
|
+
return "";
|
|
813
|
+
}
|
|
814
|
+
}).filter(Boolean);
|
|
815
|
+
return `
|
|
816
|
+
var ADAPTERS = [${adapterVars.join(", ")}].sort(function(a, b) { return b.priority - a.priority; });
|
|
817
|
+
|
|
818
|
+
function detectPlatform() {
|
|
819
|
+
for (var i = 0; i < ADAPTERS.length; i++) {
|
|
820
|
+
if (ADAPTERS[i].canHandle()) {
|
|
821
|
+
log('Detected platform: ' + ADAPTERS[i].id);
|
|
822
|
+
return ADAPTERS[i];
|
|
823
|
+
}
|
|
233
824
|
}
|
|
234
|
-
|
|
825
|
+
log('No platform detected, using generic');
|
|
826
|
+
return GenericAdapter;
|
|
235
827
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
828
|
+
`.trim();
|
|
829
|
+
}
|
|
830
|
+
function generateBridgeClass() {
|
|
831
|
+
return `
|
|
832
|
+
function FrontMcpBridge() {
|
|
833
|
+
this._adapter = null;
|
|
834
|
+
this._initialized = false;
|
|
835
|
+
this._context = {
|
|
836
|
+
hostContext: {
|
|
837
|
+
theme: detectTheme(),
|
|
838
|
+
displayMode: 'inline',
|
|
839
|
+
locale: detectLocale(),
|
|
840
|
+
userAgent: detectUserAgent(),
|
|
841
|
+
safeArea: DEFAULT_SAFE_AREA,
|
|
842
|
+
viewport: detectViewport()
|
|
843
|
+
},
|
|
844
|
+
toolInput: {},
|
|
845
|
+
toolOutput: undefined,
|
|
846
|
+
structuredContent: undefined,
|
|
847
|
+
widgetState: {},
|
|
848
|
+
contextListeners: [],
|
|
849
|
+
toolResultListeners: [],
|
|
850
|
+
notifyContextChange: function(changes) {
|
|
851
|
+
Object.assign(this.hostContext, changes);
|
|
852
|
+
for (var i = 0; i < this.contextListeners.length; i++) {
|
|
853
|
+
try { this.contextListeners[i](changes); } catch(e) {}
|
|
251
854
|
}
|
|
252
|
-
|
|
253
|
-
|
|
855
|
+
},
|
|
856
|
+
notifyToolResult: function(result) {
|
|
857
|
+
this.toolOutput = result;
|
|
858
|
+
for (var i = 0; i < this.toolResultListeners.length; i++) {
|
|
859
|
+
try { this.toolResultListeners[i](result); } catch(e) {}
|
|
254
860
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
var injected = readInjectedData();
|
|
865
|
+
this._context.toolInput = injected.toolInput;
|
|
866
|
+
this._context.toolOutput = injected.toolOutput;
|
|
867
|
+
this._context.structuredContent = injected.structuredContent;
|
|
868
|
+
|
|
869
|
+
this._loadWidgetState();
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
FrontMcpBridge.prototype._loadWidgetState = function() {
|
|
873
|
+
try {
|
|
874
|
+
var key = 'frontmcp:widget:' + (window.__mcpToolName || 'unknown');
|
|
875
|
+
var stored = localStorage.getItem(key);
|
|
876
|
+
if (stored) this._context.widgetState = JSON.parse(stored);
|
|
877
|
+
} catch(e) {}
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
FrontMcpBridge.prototype._saveWidgetState = function() {
|
|
881
|
+
try {
|
|
882
|
+
var key = 'frontmcp:widget:' + (window.__mcpToolName || 'unknown');
|
|
883
|
+
localStorage.setItem(key, JSON.stringify(this._context.widgetState));
|
|
884
|
+
} catch(e) {}
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
FrontMcpBridge.prototype.initialize = function() {
|
|
888
|
+
if (this._initialized) return Promise.resolve();
|
|
889
|
+
var self = this;
|
|
890
|
+
this._adapter = detectPlatform();
|
|
891
|
+
return this._adapter.initialize(this._context).then(function() {
|
|
892
|
+
self._initialized = true;
|
|
893
|
+
// Set up the data-tool-call click handler after initialization
|
|
894
|
+
self._setupDataToolCallHandler();
|
|
895
|
+
});
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
Object.defineProperty(FrontMcpBridge.prototype, 'initialized', { get: function() { return this._initialized; } });
|
|
899
|
+
Object.defineProperty(FrontMcpBridge.prototype, 'adapterId', { get: function() { return this._adapter ? this._adapter.id : undefined; } });
|
|
900
|
+
Object.defineProperty(FrontMcpBridge.prototype, 'capabilities', { get: function() { return this._adapter ? this._adapter.capabilities : DEFAULT_CAPABILITIES; } });
|
|
901
|
+
|
|
902
|
+
FrontMcpBridge.prototype.getTheme = function() { return this._context.hostContext.theme; };
|
|
903
|
+
FrontMcpBridge.prototype.getDisplayMode = function() { return this._context.hostContext.displayMode; };
|
|
904
|
+
FrontMcpBridge.prototype.getToolInput = function() { return this._context.toolInput; };
|
|
905
|
+
FrontMcpBridge.prototype.getToolOutput = function() { return this._context.toolOutput; };
|
|
906
|
+
FrontMcpBridge.prototype.getStructuredContent = function() { return this._context.structuredContent; };
|
|
907
|
+
FrontMcpBridge.prototype.getWidgetState = function() { return this._context.widgetState; };
|
|
908
|
+
FrontMcpBridge.prototype.getHostContext = function() { return Object.assign({}, this._context.hostContext); };
|
|
909
|
+
FrontMcpBridge.prototype.hasCapability = function(cap) { return this._adapter && this._adapter.capabilities[cap] === true; };
|
|
910
|
+
|
|
911
|
+
// Get tool response metadata (platform-agnostic)
|
|
912
|
+
// Used by inline mode widgets to detect when ui/html arrives
|
|
913
|
+
FrontMcpBridge.prototype.getToolResponseMetadata = function() {
|
|
914
|
+
// OpenAI injects toolResponseMetadata for widget-producing tools
|
|
915
|
+
if (typeof window !== 'undefined' && window.openai && window.openai.toolResponseMetadata) {
|
|
916
|
+
return window.openai.toolResponseMetadata;
|
|
917
|
+
}
|
|
918
|
+
// Claude (future support)
|
|
919
|
+
if (typeof window !== 'undefined' && window.claude && window.claude.toolResponseMetadata) {
|
|
920
|
+
return window.claude.toolResponseMetadata;
|
|
921
|
+
}
|
|
922
|
+
// FrontMCP direct injection (for testing/ext-apps)
|
|
923
|
+
if (typeof window !== 'undefined' && window.__mcpToolResponseMetadata) {
|
|
924
|
+
return window.__mcpToolResponseMetadata;
|
|
925
|
+
}
|
|
926
|
+
return null;
|
|
927
|
+
};
|
|
928
|
+
|
|
929
|
+
// Subscribe to tool response metadata changes (for inline mode injection)
|
|
930
|
+
FrontMcpBridge.prototype.onToolResponseMetadata = function(callback) {
|
|
931
|
+
var self = this;
|
|
932
|
+
var called = false;
|
|
933
|
+
|
|
934
|
+
// Check if already available
|
|
935
|
+
var existing = self.getToolResponseMetadata();
|
|
936
|
+
if (existing) {
|
|
937
|
+
called = true;
|
|
938
|
+
callback(existing);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// Set up property interceptors for OpenAI
|
|
942
|
+
if (typeof window !== 'undefined') {
|
|
943
|
+
// OpenAI: Intercept toolResponseMetadata assignment
|
|
944
|
+
if (!window.__frontmcpMetadataIntercepted) {
|
|
945
|
+
window.__frontmcpMetadataIntercepted = true;
|
|
946
|
+
window.__frontmcpMetadataCallbacks = [];
|
|
947
|
+
|
|
948
|
+
// Create openai object if it doesn't exist
|
|
949
|
+
if (!window.openai) window.openai = {};
|
|
950
|
+
|
|
951
|
+
var originalMetadata = window.openai.toolResponseMetadata;
|
|
952
|
+
Object.defineProperty(window.openai, 'toolResponseMetadata', {
|
|
953
|
+
get: function() { return originalMetadata; },
|
|
954
|
+
set: function(val) {
|
|
955
|
+
originalMetadata = val;
|
|
956
|
+
log('toolResponseMetadata set, notifying ' + window.__frontmcpMetadataCallbacks.length + ' listeners');
|
|
957
|
+
for (var i = 0; i < window.__frontmcpMetadataCallbacks.length; i++) {
|
|
958
|
+
try { window.__frontmcpMetadataCallbacks[i](val); } catch(e) {}
|
|
959
|
+
}
|
|
960
|
+
},
|
|
961
|
+
configurable: true
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// Register callback wrapper (store reference for unsubscribe)
|
|
966
|
+
var wrapper = function(metadata) {
|
|
967
|
+
if (!called) {
|
|
968
|
+
called = true;
|
|
969
|
+
callback(metadata);
|
|
261
970
|
}
|
|
262
|
-
|
|
263
|
-
|
|
971
|
+
};
|
|
972
|
+
window.__frontmcpMetadataCallbacks.push(wrapper);
|
|
973
|
+
|
|
974
|
+
// Return unsubscribe function that removes the wrapper (not the original callback)
|
|
975
|
+
return function() {
|
|
976
|
+
if (window.__frontmcpMetadataCallbacks) {
|
|
977
|
+
var idx = window.__frontmcpMetadataCallbacks.indexOf(wrapper);
|
|
978
|
+
if (idx !== -1) window.__frontmcpMetadataCallbacks.splice(idx, 1);
|
|
264
979
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// Return no-op unsubscribe for non-window environments
|
|
984
|
+
return function() {};
|
|
985
|
+
};
|
|
986
|
+
|
|
987
|
+
FrontMcpBridge.prototype.callTool = function(name, args) {
|
|
988
|
+
// Priority 1: Direct OpenAI SDK call (most reliable in OpenAI iframe)
|
|
989
|
+
// This bypasses adapter abstraction for maximum compatibility
|
|
990
|
+
if (typeof window !== 'undefined' && window.openai && typeof window.openai.callTool === 'function') {
|
|
991
|
+
log('callTool: Using OpenAI SDK directly');
|
|
992
|
+
return window.openai.callTool(name, args);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// Priority 2: Use adapter (if initialized and supports tool calls)
|
|
996
|
+
if (this._adapter && this._adapter.capabilities && this._adapter.capabilities.canCallTools) {
|
|
997
|
+
log('callTool: Using adapter ' + this._adapter.id);
|
|
998
|
+
return this._adapter.callTool(this._context, name, args);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// Not initialized or no tool support
|
|
1002
|
+
if (!this._adapter) {
|
|
1003
|
+
return Promise.reject(new Error('Bridge not initialized. Wait for bridge:ready event.'));
|
|
1004
|
+
}
|
|
1005
|
+
return Promise.reject(new Error('Tool calls not supported on this platform (' + this._adapter.id + ')'));
|
|
1006
|
+
};
|
|
1007
|
+
|
|
1008
|
+
FrontMcpBridge.prototype.sendMessage = function(content) {
|
|
1009
|
+
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1010
|
+
return this._adapter.sendMessage(this._context, content);
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
FrontMcpBridge.prototype.openLink = function(url) {
|
|
1014
|
+
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1015
|
+
return this._adapter.openLink(this._context, url);
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
1019
|
+
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1020
|
+
var self = this;
|
|
1021
|
+
return this._adapter.requestDisplayMode(this._context, mode).then(function() {
|
|
1022
|
+
self._context.hostContext.displayMode = mode;
|
|
1023
|
+
});
|
|
1024
|
+
};
|
|
1025
|
+
|
|
1026
|
+
FrontMcpBridge.prototype.requestClose = function() {
|
|
1027
|
+
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1028
|
+
return this._adapter.requestClose(this._context);
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
// Extended ext-apps methods (full specification)
|
|
1032
|
+
FrontMcpBridge.prototype.updateModelContext = function(context, merge) {
|
|
1033
|
+
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1034
|
+
if (!this._adapter.updateModelContext) {
|
|
1035
|
+
return Promise.reject(new Error('updateModelContext not supported on this platform'));
|
|
1036
|
+
}
|
|
1037
|
+
return this._adapter.updateModelContext(this._context, context, merge);
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
FrontMcpBridge.prototype.log = function(level, message, data) {
|
|
1041
|
+
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1042
|
+
if (!this._adapter.log) {
|
|
1043
|
+
// Fallback to console
|
|
1044
|
+
var logFn = console[level] || console.log;
|
|
1045
|
+
logFn('[Widget] ' + message, data);
|
|
1046
|
+
return Promise.resolve();
|
|
1047
|
+
}
|
|
1048
|
+
return this._adapter.log(this._context, level, message, data);
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
FrontMcpBridge.prototype.registerTool = function(name, description, inputSchema) {
|
|
1052
|
+
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1053
|
+
if (!this._adapter.registerTool) {
|
|
1054
|
+
return Promise.reject(new Error('registerTool not supported on this platform'));
|
|
1055
|
+
}
|
|
1056
|
+
return this._adapter.registerTool(this._context, name, description, inputSchema);
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
FrontMcpBridge.prototype.unregisterTool = function(name) {
|
|
1060
|
+
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1061
|
+
if (!this._adapter.unregisterTool) {
|
|
1062
|
+
return Promise.reject(new Error('unregisterTool not supported on this platform'));
|
|
1063
|
+
}
|
|
1064
|
+
return this._adapter.unregisterTool(this._context, name);
|
|
1065
|
+
};
|
|
1066
|
+
|
|
1067
|
+
FrontMcpBridge.prototype.setWidgetState = function(state) {
|
|
1068
|
+
Object.assign(this._context.widgetState, state);
|
|
1069
|
+
this._saveWidgetState();
|
|
1070
|
+
};
|
|
1071
|
+
|
|
1072
|
+
FrontMcpBridge.prototype.onContextChange = function(callback) {
|
|
1073
|
+
var listeners = this._context.contextListeners;
|
|
1074
|
+
listeners.push(callback);
|
|
1075
|
+
return function() {
|
|
1076
|
+
var idx = listeners.indexOf(callback);
|
|
1077
|
+
if (idx !== -1) listeners.splice(idx, 1);
|
|
1078
|
+
};
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
FrontMcpBridge.prototype.onToolResult = function(callback) {
|
|
1082
|
+
var listeners = this._context.toolResultListeners;
|
|
1083
|
+
listeners.push(callback);
|
|
1084
|
+
return function() {
|
|
1085
|
+
var idx = listeners.indexOf(callback);
|
|
1086
|
+
if (idx !== -1) listeners.splice(idx, 1);
|
|
1087
|
+
};
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
// ==================== data-tool-call Click Handler ====================
|
|
1091
|
+
|
|
1092
|
+
FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
|
|
1093
|
+
var self = this;
|
|
1094
|
+
|
|
1095
|
+
document.addEventListener('click', function(e) {
|
|
1096
|
+
// Find the closest element with data-tool-call attribute
|
|
1097
|
+
var target = e.target;
|
|
1098
|
+
while (target && target !== document) {
|
|
1099
|
+
if (target.hasAttribute && target.hasAttribute('data-tool-call')) {
|
|
1100
|
+
var toolName = target.getAttribute('data-tool-call');
|
|
1101
|
+
var argsAttr = target.getAttribute('data-tool-args');
|
|
1102
|
+
var args = {};
|
|
1103
|
+
|
|
1104
|
+
try {
|
|
1105
|
+
if (argsAttr) {
|
|
1106
|
+
args = JSON.parse(argsAttr);
|
|
1107
|
+
}
|
|
1108
|
+
} catch (parseErr) {
|
|
1109
|
+
console.error('[frontmcp] Failed to parse data-tool-args:', parseErr);
|
|
273
1110
|
}
|
|
274
|
-
|
|
275
|
-
|
|
1111
|
+
|
|
1112
|
+
log('data-tool-call clicked: ' + toolName);
|
|
1113
|
+
|
|
1114
|
+
// Show loading state - save original content first
|
|
1115
|
+
var originalContent = target.innerHTML;
|
|
1116
|
+
var originalDisabled = target.disabled;
|
|
1117
|
+
target.disabled = true;
|
|
1118
|
+
target.classList.add('opacity-50', 'cursor-not-allowed');
|
|
1119
|
+
|
|
1120
|
+
// Add spinner for buttons
|
|
1121
|
+
var spinner = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
|
|
1122
|
+
if (target.tagName === 'BUTTON') {
|
|
1123
|
+
target.innerHTML = spinner + 'Loading...';
|
|
276
1124
|
}
|
|
277
|
-
|
|
278
|
-
|
|
1125
|
+
|
|
1126
|
+
// Helper to reset button state
|
|
1127
|
+
function resetButton() {
|
|
1128
|
+
target.innerHTML = originalContent;
|
|
1129
|
+
target.disabled = originalDisabled;
|
|
1130
|
+
target.classList.remove('opacity-50', 'cursor-not-allowed');
|
|
279
1131
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
1132
|
+
|
|
1133
|
+
// Determine how to call the tool
|
|
1134
|
+
var toolCallPromise;
|
|
1135
|
+
|
|
1136
|
+
// Priority 1: Direct OpenAI SDK call (bypasses adapter abstraction)
|
|
1137
|
+
if (typeof window !== 'undefined' && window.openai && typeof window.openai.callTool === 'function') {
|
|
1138
|
+
log('Using OpenAI SDK directly for tool call');
|
|
1139
|
+
toolCallPromise = window.openai.callTool(toolName, args);
|
|
1140
|
+
}
|
|
1141
|
+
// Priority 2: Use adapter (if it supports tool calls)
|
|
1142
|
+
else if (self.hasCapability('canCallTools')) {
|
|
1143
|
+
log('Using adapter for tool call');
|
|
1144
|
+
toolCallPromise = self.callTool(toolName, args);
|
|
285
1145
|
}
|
|
1146
|
+
// No tool call capability
|
|
1147
|
+
else {
|
|
1148
|
+
console.error('[frontmcp] Tool calls not supported on this platform (' + self.adapterId + ')');
|
|
1149
|
+
resetButton();
|
|
1150
|
+
target.dispatchEvent(new CustomEvent('tool:error', {
|
|
1151
|
+
detail: { name: toolName, args: args, error: 'Tool calls not supported on this platform' },
|
|
1152
|
+
bubbles: true
|
|
1153
|
+
}));
|
|
1154
|
+
e.preventDefault();
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// Handle the tool call result
|
|
1159
|
+
toolCallPromise.then(function(result) {
|
|
1160
|
+
log('Tool call succeeded: ' + toolName);
|
|
1161
|
+
resetButton();
|
|
1162
|
+
|
|
1163
|
+
// Update bridge state to trigger widget re-render
|
|
1164
|
+
// React isn't hydrated in OpenAI iframe, so useState doesn't work
|
|
1165
|
+
// Instead, we use the bridge's reactive state system
|
|
1166
|
+
if (result && window.__frontmcp && window.__frontmcp.bridge && typeof window.__frontmcp.bridge.setWidgetState === 'function') {
|
|
1167
|
+
var newData = result.structuredContent || result;
|
|
1168
|
+
log('Updating bridge state with new data');
|
|
1169
|
+
window.__frontmcp.bridge.setWidgetState(newData);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// Dispatch success event
|
|
1173
|
+
target.dispatchEvent(new CustomEvent('tool:success', {
|
|
1174
|
+
detail: { name: toolName, args: args, result: result },
|
|
1175
|
+
bubbles: true
|
|
1176
|
+
}));
|
|
1177
|
+
}).catch(function(err) {
|
|
1178
|
+
console.error('[frontmcp] Tool call failed: ' + toolName, err);
|
|
1179
|
+
resetButton();
|
|
1180
|
+
// Dispatch error event
|
|
1181
|
+
target.dispatchEvent(new CustomEvent('tool:error', {
|
|
1182
|
+
detail: { name: toolName, args: args, error: err.message || err },
|
|
1183
|
+
bubbles: true
|
|
1184
|
+
}));
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
// Prevent default behavior (e.g., form submission)
|
|
1188
|
+
e.preventDefault();
|
|
1189
|
+
return;
|
|
286
1190
|
}
|
|
287
|
-
|
|
288
|
-
|
|
1191
|
+
target = target.parentElement;
|
|
1192
|
+
}
|
|
1193
|
+
}, true); // Use capture phase to handle before React handlers
|
|
1194
|
+
};
|
|
1195
|
+
`.trim();
|
|
1196
|
+
}
|
|
1197
|
+
var MAX_MINIFY_CODE_LENGTH = 5e5;
|
|
1198
|
+
function minifyJS(code) {
|
|
1199
|
+
if (code.length > MAX_MINIFY_CODE_LENGTH) {
|
|
1200
|
+
return code;
|
|
1201
|
+
}
|
|
1202
|
+
return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1").replace(/\s+/g, " ").replace(/\s*([{};,:()[\]])\s*/g, "$1").replace(/;\}/g, "}").trim();
|
|
1203
|
+
}
|
|
1204
|
+
function generatePlatformBundle(platform, options = {}) {
|
|
1205
|
+
const platformAdapters = {
|
|
1206
|
+
chatgpt: ["openai", "generic"],
|
|
1207
|
+
claude: ["claude", "generic"],
|
|
1208
|
+
gemini: ["gemini", "generic"],
|
|
1209
|
+
universal: ["openai", "ext-apps", "claude", "gemini", "generic"]
|
|
1210
|
+
};
|
|
1211
|
+
return generateBridgeIIFE({
|
|
1212
|
+
...options,
|
|
1213
|
+
adapters: platformAdapters[platform]
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
var UNIVERSAL_BRIDGE_SCRIPT = generateBridgeIIFE();
|
|
1217
|
+
var BRIDGE_SCRIPT_TAGS = {
|
|
1218
|
+
universal: `<script>${UNIVERSAL_BRIDGE_SCRIPT}</script>`,
|
|
1219
|
+
chatgpt: `<script>${generatePlatformBundle("chatgpt")}</script>`,
|
|
1220
|
+
claude: `<script>${generatePlatformBundle("claude")}</script>`,
|
|
1221
|
+
gemini: `<script>${generatePlatformBundle("gemini")}</script>`
|
|
1222
|
+
};
|
|
1223
|
+
|
|
1224
|
+
// libs/uipack/src/shell/custom-shell-types.ts
|
|
1225
|
+
var SHELL_PLACEHOLDER_NAMES = ["CSP", "DATA", "BRIDGE", "CONTENT", "TITLE"];
|
|
1226
|
+
var SHELL_PLACEHOLDERS = {
|
|
1227
|
+
CSP: "{{CSP}}",
|
|
1228
|
+
DATA: "{{DATA}}",
|
|
1229
|
+
BRIDGE: "{{BRIDGE}}",
|
|
1230
|
+
CONTENT: "{{CONTENT}}",
|
|
1231
|
+
TITLE: "{{TITLE}}"
|
|
1232
|
+
};
|
|
1233
|
+
var REQUIRED_PLACEHOLDERS = ["CONTENT"];
|
|
1234
|
+
var OPTIONAL_PLACEHOLDERS = ["CSP", "DATA", "BRIDGE", "TITLE"];
|
|
1235
|
+
|
|
1236
|
+
// libs/uipack/src/shell/custom-shell-validator.ts
|
|
1237
|
+
function validateShellTemplate(template) {
|
|
1238
|
+
const found = {};
|
|
1239
|
+
for (const name of SHELL_PLACEHOLDER_NAMES) {
|
|
1240
|
+
found[name] = template.includes(SHELL_PLACEHOLDERS[name]);
|
|
1241
|
+
}
|
|
1242
|
+
const missingRequired = REQUIRED_PLACEHOLDERS.filter((name) => !found[name]);
|
|
1243
|
+
const missingOptional = OPTIONAL_PLACEHOLDERS.filter((name) => !found[name]);
|
|
1244
|
+
return {
|
|
1245
|
+
valid: missingRequired.length === 0,
|
|
1246
|
+
found,
|
|
1247
|
+
missingRequired,
|
|
1248
|
+
missingOptional
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// libs/uipack/src/shell/custom-shell-applier.ts
|
|
1253
|
+
function applyShellTemplate(template, values) {
|
|
1254
|
+
let result = template;
|
|
1255
|
+
result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
|
|
1256
|
+
result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
|
|
1257
|
+
result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
|
|
1258
|
+
result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
|
|
1259
|
+
result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
|
|
1260
|
+
return result;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// libs/uipack/src/shell/builder.ts
|
|
1264
|
+
function buildShell(content, config) {
|
|
1265
|
+
const { toolName, csp, withShell = true, input, output, structuredContent, includeBridge = true, title } = config;
|
|
1266
|
+
const { customShell } = config;
|
|
1267
|
+
const dataScript = buildDataInjectionScript({ toolName, input, output, structuredContent });
|
|
1268
|
+
if (!withShell) {
|
|
1269
|
+
const html2 = `${dataScript}
|
|
1270
|
+
${content}`;
|
|
1271
|
+
return {
|
|
1272
|
+
html: html2,
|
|
1273
|
+
hash: simpleHash(html2),
|
|
1274
|
+
size: Buffer.byteLength(html2, "utf-8")
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
if (customShell) {
|
|
1278
|
+
return buildCustomShell(content, customShell, {
|
|
1279
|
+
csp,
|
|
1280
|
+
toolName,
|
|
1281
|
+
input,
|
|
1282
|
+
output,
|
|
1283
|
+
structuredContent,
|
|
1284
|
+
includeBridge,
|
|
1285
|
+
title
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
const headParts = [
|
|
1289
|
+
'<meta charset="UTF-8">',
|
|
1290
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1.0">'
|
|
1291
|
+
];
|
|
1292
|
+
if (title) {
|
|
1293
|
+
headParts.push(`<title>${escapeHtmlForTag(title)}</title>`);
|
|
1294
|
+
}
|
|
1295
|
+
headParts.push(buildCSPMetaTag(csp));
|
|
1296
|
+
headParts.push(dataScript);
|
|
1297
|
+
if (includeBridge) {
|
|
1298
|
+
const bridgeScript = generateBridgeIIFE({ minify: true });
|
|
1299
|
+
headParts.push(`<script>${bridgeScript}</script>`);
|
|
1300
|
+
}
|
|
1301
|
+
const html = `<!DOCTYPE html>
|
|
1302
|
+
<html lang="en">
|
|
1303
|
+
<head>
|
|
1304
|
+
${headParts.map((p) => ` ${p}`).join("\n")}
|
|
1305
|
+
</head>
|
|
1306
|
+
<body>
|
|
1307
|
+
${content}
|
|
1308
|
+
</body>
|
|
1309
|
+
</html>`;
|
|
1310
|
+
return {
|
|
1311
|
+
html,
|
|
1312
|
+
hash: simpleHash(html),
|
|
1313
|
+
size: Buffer.byteLength(html, "utf-8")
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
function buildCustomShell(content, customShell, ctx) {
|
|
1317
|
+
let template;
|
|
1318
|
+
if (typeof customShell === "string") {
|
|
1319
|
+
const validation = validateShellTemplate(customShell);
|
|
1320
|
+
if (!validation.valid) {
|
|
1321
|
+
throw new Error(
|
|
1322
|
+
`Custom shell template is missing required placeholder(s): ${validation.missingRequired.map((n) => `{{${n}}}`).join(", ")}`
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
template = customShell;
|
|
1326
|
+
} else {
|
|
1327
|
+
template = customShell.template;
|
|
1328
|
+
}
|
|
1329
|
+
const cspTag = buildCSPMetaTag(ctx.csp);
|
|
1330
|
+
const dataScript = buildDataInjectionScript({
|
|
1331
|
+
toolName: ctx.toolName,
|
|
1332
|
+
input: ctx.input,
|
|
1333
|
+
output: ctx.output,
|
|
1334
|
+
structuredContent: ctx.structuredContent
|
|
1335
|
+
});
|
|
1336
|
+
let bridgeHtml = "";
|
|
1337
|
+
if (ctx.includeBridge) {
|
|
1338
|
+
const bridgeScript = generateBridgeIIFE({ minify: true });
|
|
1339
|
+
bridgeHtml = `<script>${bridgeScript}</script>`;
|
|
1340
|
+
}
|
|
1341
|
+
const html = applyShellTemplate(template, {
|
|
1342
|
+
csp: cspTag,
|
|
1343
|
+
data: dataScript,
|
|
1344
|
+
bridge: bridgeHtml,
|
|
1345
|
+
content,
|
|
1346
|
+
title: ctx.title ? escapeHtmlForTag(ctx.title) : ""
|
|
1347
|
+
});
|
|
1348
|
+
return {
|
|
1349
|
+
html,
|
|
1350
|
+
hash: simpleHash(html),
|
|
1351
|
+
size: Buffer.byteLength(html, "utf-8")
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
function simpleHash(str) {
|
|
1355
|
+
let hash = 0;
|
|
1356
|
+
for (let i = 0; i < str.length; i++) {
|
|
1357
|
+
const char = str.charCodeAt(i);
|
|
1358
|
+
hash = (hash << 5) - hash + char | 0;
|
|
1359
|
+
}
|
|
1360
|
+
return Math.abs(hash).toString(16).padStart(8, "0");
|
|
1361
|
+
}
|
|
1362
|
+
function escapeHtmlForTag(str) {
|
|
1363
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
// libs/uipack/src/resolver/cdn-registry.ts
|
|
1367
|
+
var DEFAULT_CDN_REGISTRY = {
|
|
1368
|
+
react: {
|
|
1369
|
+
packageName: "react",
|
|
1370
|
+
defaultVersion: "19.2.4",
|
|
1371
|
+
providers: {
|
|
1372
|
+
cloudflare: {
|
|
1373
|
+
url: "https://cdnjs.cloudflare.com/ajax/libs/react/19.2.4/umd/react.production.min.js",
|
|
1374
|
+
global: "React",
|
|
1375
|
+
crossorigin: "anonymous"
|
|
1376
|
+
},
|
|
1377
|
+
jsdelivr: {
|
|
1378
|
+
url: "https://cdn.jsdelivr.net/npm/react@19.2.4/umd/react.production.min.js",
|
|
1379
|
+
global: "React",
|
|
1380
|
+
crossorigin: "anonymous"
|
|
1381
|
+
},
|
|
1382
|
+
unpkg: {
|
|
1383
|
+
url: "https://unpkg.com/react@19.2.4/umd/react.production.min.js",
|
|
1384
|
+
global: "React",
|
|
1385
|
+
crossorigin: "anonymous"
|
|
1386
|
+
},
|
|
1387
|
+
"esm.sh": {
|
|
1388
|
+
url: "https://esm.sh/react@19.2.4",
|
|
1389
|
+
esm: true,
|
|
1390
|
+
crossorigin: "anonymous"
|
|
289
1391
|
}
|
|
290
|
-
|
|
291
|
-
|
|
1392
|
+
},
|
|
1393
|
+
preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
|
|
1394
|
+
metadata: {
|
|
1395
|
+
description: "A JavaScript library for building user interfaces",
|
|
1396
|
+
homepage: "https://react.dev",
|
|
1397
|
+
license: "MIT"
|
|
1398
|
+
}
|
|
1399
|
+
},
|
|
1400
|
+
"react-dom": {
|
|
1401
|
+
packageName: "react-dom",
|
|
1402
|
+
defaultVersion: "19.2.4",
|
|
1403
|
+
providers: {
|
|
1404
|
+
cloudflare: {
|
|
1405
|
+
url: "https://cdnjs.cloudflare.com/ajax/libs/react-dom/19.2.4/umd/react-dom.production.min.js",
|
|
1406
|
+
global: "ReactDOM",
|
|
1407
|
+
crossorigin: "anonymous",
|
|
1408
|
+
peerDependencies: ["react"]
|
|
1409
|
+
},
|
|
1410
|
+
jsdelivr: {
|
|
1411
|
+
url: "https://cdn.jsdelivr.net/npm/react-dom@19.2.4/umd/react-dom.production.min.js",
|
|
1412
|
+
global: "ReactDOM",
|
|
1413
|
+
crossorigin: "anonymous",
|
|
1414
|
+
peerDependencies: ["react"]
|
|
1415
|
+
},
|
|
1416
|
+
unpkg: {
|
|
1417
|
+
url: "https://unpkg.com/react-dom@19.2.4/umd/react-dom.production.min.js",
|
|
1418
|
+
global: "ReactDOM",
|
|
1419
|
+
crossorigin: "anonymous",
|
|
1420
|
+
peerDependencies: ["react"]
|
|
1421
|
+
},
|
|
1422
|
+
"esm.sh": {
|
|
1423
|
+
url: "https://esm.sh/react-dom@19.2.4",
|
|
1424
|
+
esm: true,
|
|
1425
|
+
crossorigin: "anonymous",
|
|
1426
|
+
peerDependencies: ["react"]
|
|
292
1427
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
1428
|
+
},
|
|
1429
|
+
preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
|
|
1430
|
+
metadata: {
|
|
1431
|
+
description: "React package for working with the DOM",
|
|
1432
|
+
homepage: "https://react.dev",
|
|
1433
|
+
license: "MIT"
|
|
1434
|
+
}
|
|
1435
|
+
},
|
|
1436
|
+
"chart.js": {
|
|
1437
|
+
packageName: "chart.js",
|
|
1438
|
+
defaultVersion: "4.5.1",
|
|
1439
|
+
providers: {
|
|
1440
|
+
cloudflare: {
|
|
1441
|
+
url: "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.5.1/chart.umd.min.js",
|
|
1442
|
+
global: "Chart",
|
|
1443
|
+
crossorigin: "anonymous"
|
|
1444
|
+
},
|
|
1445
|
+
jsdelivr: {
|
|
1446
|
+
url: "https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js",
|
|
1447
|
+
global: "Chart",
|
|
1448
|
+
crossorigin: "anonymous"
|
|
1449
|
+
},
|
|
1450
|
+
unpkg: {
|
|
1451
|
+
url: "https://unpkg.com/chart.js@4.5.1/dist/chart.umd.min.js",
|
|
1452
|
+
global: "Chart",
|
|
1453
|
+
crossorigin: "anonymous"
|
|
302
1454
|
}
|
|
303
|
-
|
|
304
|
-
|
|
1455
|
+
},
|
|
1456
|
+
preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
|
|
1457
|
+
metadata: {
|
|
1458
|
+
description: "Simple yet flexible JavaScript charting library",
|
|
1459
|
+
homepage: "https://www.chartjs.org",
|
|
1460
|
+
license: "MIT"
|
|
1461
|
+
}
|
|
1462
|
+
},
|
|
1463
|
+
d3: {
|
|
1464
|
+
packageName: "d3",
|
|
1465
|
+
defaultVersion: "7.9.0",
|
|
1466
|
+
providers: {
|
|
1467
|
+
cloudflare: {
|
|
1468
|
+
url: "https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js",
|
|
1469
|
+
integrity: "sha512-vc58qvvBdrDR4etbxMdlTt4GBQk1qjvyORR2nrsPsFPyrs+/u5c3+1Ct6upOgdZoIl7eq6k3a1UPDSNAQi/32A==",
|
|
1470
|
+
global: "d3",
|
|
1471
|
+
crossorigin: "anonymous"
|
|
1472
|
+
},
|
|
1473
|
+
jsdelivr: {
|
|
1474
|
+
url: "https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js",
|
|
1475
|
+
global: "d3",
|
|
1476
|
+
crossorigin: "anonymous"
|
|
1477
|
+
},
|
|
1478
|
+
unpkg: {
|
|
1479
|
+
url: "https://unpkg.com/d3@7.9.0/dist/d3.min.js",
|
|
1480
|
+
global: "d3",
|
|
1481
|
+
crossorigin: "anonymous"
|
|
305
1482
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
1483
|
+
},
|
|
1484
|
+
preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
|
|
1485
|
+
metadata: {
|
|
1486
|
+
description: "Data-Driven Documents",
|
|
1487
|
+
homepage: "https://d3js.org",
|
|
1488
|
+
license: "ISC"
|
|
1489
|
+
}
|
|
1490
|
+
},
|
|
1491
|
+
lodash: {
|
|
1492
|
+
packageName: "lodash",
|
|
1493
|
+
defaultVersion: "4.17.21",
|
|
1494
|
+
providers: {
|
|
1495
|
+
cloudflare: {
|
|
1496
|
+
url: "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js",
|
|
1497
|
+
integrity: "sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==",
|
|
1498
|
+
global: "_",
|
|
1499
|
+
crossorigin: "anonymous"
|
|
1500
|
+
},
|
|
1501
|
+
jsdelivr: {
|
|
1502
|
+
url: "https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js",
|
|
1503
|
+
global: "_",
|
|
1504
|
+
crossorigin: "anonymous"
|
|
1505
|
+
},
|
|
1506
|
+
unpkg: {
|
|
1507
|
+
url: "https://unpkg.com/lodash@4.17.21/lodash.min.js",
|
|
1508
|
+
global: "_",
|
|
1509
|
+
crossorigin: "anonymous"
|
|
317
1510
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
1511
|
+
},
|
|
1512
|
+
preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
|
|
1513
|
+
metadata: {
|
|
1514
|
+
description: "A modern JavaScript utility library",
|
|
1515
|
+
homepage: "https://lodash.com",
|
|
1516
|
+
license: "MIT"
|
|
1517
|
+
}
|
|
323
1518
|
}
|
|
324
|
-
|
|
1519
|
+
};
|
|
1520
|
+
function lookupPackage(packageName, registry = DEFAULT_CDN_REGISTRY) {
|
|
1521
|
+
return registry[packageName];
|
|
325
1522
|
}
|
|
326
1523
|
|
|
327
|
-
// libs/uipack/src/
|
|
328
|
-
var
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
},
|
|
335
|
-
"
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
1524
|
+
// libs/uipack/src/resolver/import-parser.ts
|
|
1525
|
+
var IMPORT_PATTERNS = {
|
|
1526
|
+
named: /import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,
|
|
1527
|
+
default: /import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
|
|
1528
|
+
namespace: /import\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
|
|
1529
|
+
sideEffect: /import\s*['"]([^'"]+)['"]/g,
|
|
1530
|
+
dynamic: /(?:await\s+)?import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
1531
|
+
defaultAndNamed: /import\s+(\w+)\s*,\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,
|
|
1532
|
+
defaultAndNamespace: /import\s+(\w+)\s*,\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
|
|
1533
|
+
reExport: /export\s*\{[^}]+\}\s*from\s*['"]([^'"]+)['"]/g,
|
|
1534
|
+
reExportAll: /export\s*\*\s*from\s*['"]([^'"]+)['"]/g
|
|
1535
|
+
};
|
|
1536
|
+
function parseNamedImports(namedString) {
|
|
1537
|
+
return namedString.split(",").map((s) => s.trim()).filter((s) => s.length > 0).map((s) => {
|
|
1538
|
+
const asMatch = s.match(/^(\w+)\s+as\s+\w+$/);
|
|
1539
|
+
return asMatch ? asMatch[1] : s;
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
function isRelativeImport(specifier) {
|
|
1543
|
+
return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/");
|
|
1544
|
+
}
|
|
1545
|
+
function isExternalImport(specifier) {
|
|
1546
|
+
return !isRelativeImport(specifier) && !specifier.startsWith("#");
|
|
1547
|
+
}
|
|
1548
|
+
function getPackageName(specifier) {
|
|
1549
|
+
if (specifier.startsWith("@")) {
|
|
1550
|
+
const parts = specifier.split("/");
|
|
1551
|
+
if (parts.length >= 2) {
|
|
1552
|
+
return `${parts[0]}/${parts[1]}`;
|
|
1553
|
+
}
|
|
1554
|
+
return specifier;
|
|
1555
|
+
}
|
|
1556
|
+
const firstSlash = specifier.indexOf("/");
|
|
1557
|
+
return firstSlash === -1 ? specifier : specifier.slice(0, firstSlash);
|
|
1558
|
+
}
|
|
1559
|
+
function getLineNumber(source, index) {
|
|
1560
|
+
let line = 1;
|
|
1561
|
+
for (let i = 0; i < index && i < source.length; i++) {
|
|
1562
|
+
if (source[i] === "\n") {
|
|
1563
|
+
line++;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
return line;
|
|
1567
|
+
}
|
|
1568
|
+
function getColumnNumber(source, index) {
|
|
1569
|
+
let column = 0;
|
|
1570
|
+
for (let i = index - 1; i >= 0 && source[i] !== "\n"; i--) {
|
|
1571
|
+
column++;
|
|
1572
|
+
}
|
|
1573
|
+
return column;
|
|
1574
|
+
}
|
|
1575
|
+
function parseImports(source) {
|
|
1576
|
+
const imports = [];
|
|
1577
|
+
const seenStatements = /* @__PURE__ */ new Set();
|
|
1578
|
+
const addImport = (imp) => {
|
|
1579
|
+
const key = `${imp.type}:${imp.specifier}:${imp.statement}`;
|
|
1580
|
+
if (!seenStatements.has(key)) {
|
|
1581
|
+
seenStatements.add(key);
|
|
1582
|
+
imports.push(imp);
|
|
1583
|
+
}
|
|
1584
|
+
};
|
|
1585
|
+
let match;
|
|
1586
|
+
const defaultAndNamedRegex = new RegExp(IMPORT_PATTERNS.defaultAndNamed.source, "g");
|
|
1587
|
+
while ((match = defaultAndNamedRegex.exec(source)) !== null) {
|
|
1588
|
+
const [statement, defaultName, namedString, specifier] = match;
|
|
1589
|
+
addImport({
|
|
1590
|
+
statement,
|
|
1591
|
+
specifier,
|
|
1592
|
+
type: "default",
|
|
1593
|
+
defaultImport: defaultName,
|
|
1594
|
+
namedImports: parseNamedImports(namedString),
|
|
1595
|
+
line: getLineNumber(source, match.index),
|
|
1596
|
+
column: getColumnNumber(source, match.index)
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
const defaultAndNamespaceRegex = new RegExp(IMPORT_PATTERNS.defaultAndNamespace.source, "g");
|
|
1600
|
+
while ((match = defaultAndNamespaceRegex.exec(source)) !== null) {
|
|
1601
|
+
const [statement, defaultName, namespaceName, specifier] = match;
|
|
1602
|
+
addImport({
|
|
1603
|
+
statement,
|
|
1604
|
+
specifier,
|
|
1605
|
+
type: "default",
|
|
1606
|
+
defaultImport: defaultName,
|
|
1607
|
+
namespaceImport: namespaceName,
|
|
1608
|
+
line: getLineNumber(source, match.index),
|
|
1609
|
+
column: getColumnNumber(source, match.index)
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
const namedRegex = new RegExp(IMPORT_PATTERNS.named.source, "g");
|
|
1613
|
+
while ((match = namedRegex.exec(source)) !== null) {
|
|
1614
|
+
const [statement, namedString, specifier] = match;
|
|
1615
|
+
addImport({
|
|
1616
|
+
statement,
|
|
1617
|
+
specifier,
|
|
1618
|
+
type: "named",
|
|
1619
|
+
namedImports: parseNamedImports(namedString),
|
|
1620
|
+
line: getLineNumber(source, match.index),
|
|
1621
|
+
column: getColumnNumber(source, match.index)
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
const defaultRegex = new RegExp(IMPORT_PATTERNS.default.source, "g");
|
|
1625
|
+
while ((match = defaultRegex.exec(source)) !== null) {
|
|
1626
|
+
const [statement, defaultName, specifier] = match;
|
|
1627
|
+
const afterMatch = source.slice(match.index + match[0].length - specifier.length - 2);
|
|
1628
|
+
if (afterMatch.startsWith(",")) continue;
|
|
1629
|
+
addImport({
|
|
1630
|
+
statement,
|
|
1631
|
+
specifier,
|
|
1632
|
+
type: "default",
|
|
1633
|
+
defaultImport: defaultName,
|
|
1634
|
+
line: getLineNumber(source, match.index),
|
|
1635
|
+
column: getColumnNumber(source, match.index)
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
const namespaceRegex = new RegExp(IMPORT_PATTERNS.namespace.source, "g");
|
|
1639
|
+
while ((match = namespaceRegex.exec(source)) !== null) {
|
|
1640
|
+
const [statement, namespaceName, specifier] = match;
|
|
1641
|
+
addImport({
|
|
1642
|
+
statement,
|
|
1643
|
+
specifier,
|
|
1644
|
+
type: "namespace",
|
|
1645
|
+
namespaceImport: namespaceName,
|
|
1646
|
+
line: getLineNumber(source, match.index),
|
|
1647
|
+
column: getColumnNumber(source, match.index)
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
const sideEffectRegex = new RegExp(IMPORT_PATTERNS.sideEffect.source, "g");
|
|
1651
|
+
while ((match = sideEffectRegex.exec(source)) !== null) {
|
|
1652
|
+
const [statement, specifier] = match;
|
|
1653
|
+
const beforeMatch = source.slice(Math.max(0, match.index - 50), match.index);
|
|
1654
|
+
if (beforeMatch.includes("from")) continue;
|
|
1655
|
+
addImport({
|
|
1656
|
+
statement,
|
|
1657
|
+
specifier,
|
|
1658
|
+
type: "side-effect",
|
|
1659
|
+
line: getLineNumber(source, match.index),
|
|
1660
|
+
column: getColumnNumber(source, match.index)
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
const dynamicRegex = new RegExp(IMPORT_PATTERNS.dynamic.source, "g");
|
|
1664
|
+
while ((match = dynamicRegex.exec(source)) !== null) {
|
|
1665
|
+
const [statement, specifier] = match;
|
|
1666
|
+
addImport({
|
|
1667
|
+
statement,
|
|
1668
|
+
specifier,
|
|
1669
|
+
type: "dynamic",
|
|
1670
|
+
line: getLineNumber(source, match.index),
|
|
1671
|
+
column: getColumnNumber(source, match.index)
|
|
1672
|
+
});
|
|
390
1673
|
}
|
|
1674
|
+
const reExportRegex = new RegExp(IMPORT_PATTERNS.reExport.source, "g");
|
|
1675
|
+
while ((match = reExportRegex.exec(source)) !== null) {
|
|
1676
|
+
const [statement, specifier] = match;
|
|
1677
|
+
addImport({
|
|
1678
|
+
statement,
|
|
1679
|
+
specifier,
|
|
1680
|
+
type: "named",
|
|
1681
|
+
line: getLineNumber(source, match.index),
|
|
1682
|
+
column: getColumnNumber(source, match.index)
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1685
|
+
const reExportAllRegex = new RegExp(IMPORT_PATTERNS.reExportAll.source, "g");
|
|
1686
|
+
while ((match = reExportAllRegex.exec(source)) !== null) {
|
|
1687
|
+
const [statement, specifier] = match;
|
|
1688
|
+
addImport({
|
|
1689
|
+
statement,
|
|
1690
|
+
specifier,
|
|
1691
|
+
type: "namespace",
|
|
1692
|
+
line: getLineNumber(source, match.index),
|
|
1693
|
+
column: getColumnNumber(source, match.index)
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
const externalImports = imports.filter((imp) => isExternalImport(imp.specifier));
|
|
1697
|
+
const relativeImports = imports.filter((imp) => isRelativeImport(imp.specifier));
|
|
1698
|
+
const externalPackages = [...new Set(externalImports.map((imp) => getPackageName(imp.specifier)))];
|
|
1699
|
+
return {
|
|
1700
|
+
imports,
|
|
1701
|
+
externalImports,
|
|
1702
|
+
relativeImports,
|
|
1703
|
+
externalPackages
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
// libs/uipack/src/resolver/esm-sh.resolver.ts
|
|
1708
|
+
var DEFAULT_FALLBACK_CDN = "https://esm.sh";
|
|
1709
|
+
function createEsmShResolver(options = {}) {
|
|
1710
|
+
const {
|
|
1711
|
+
fallbackCdnBase = DEFAULT_FALLBACK_CDN,
|
|
1712
|
+
registry = DEFAULT_CDN_REGISTRY,
|
|
1713
|
+
providerOrder = ["esm.sh", "cloudflare", "jsdelivr", "unpkg"]
|
|
1714
|
+
} = options;
|
|
1715
|
+
return {
|
|
1716
|
+
resolve(specifier, _context) {
|
|
1717
|
+
if (specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/")) {
|
|
1718
|
+
return null;
|
|
1719
|
+
}
|
|
1720
|
+
if (specifier.startsWith("node:") || specifier.startsWith("#")) {
|
|
1721
|
+
return null;
|
|
1722
|
+
}
|
|
1723
|
+
const pkgName = getPackageName(specifier);
|
|
1724
|
+
const entry = lookupPackage(pkgName, registry);
|
|
1725
|
+
if (entry) {
|
|
1726
|
+
for (const provider of providerOrder) {
|
|
1727
|
+
const config = entry.providers[provider];
|
|
1728
|
+
if (config?.url) {
|
|
1729
|
+
const subpath = specifier.slice(pkgName.length);
|
|
1730
|
+
if (subpath && config.url.includes("esm.sh/")) {
|
|
1731
|
+
return {
|
|
1732
|
+
value: config.url + subpath,
|
|
1733
|
+
type: "url",
|
|
1734
|
+
integrity: config.integrity
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
if (subpath) {
|
|
1738
|
+
return {
|
|
1739
|
+
value: `${fallbackCdnBase}/${specifier}`,
|
|
1740
|
+
type: "url"
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
if (config.global && !config.esm) {
|
|
1744
|
+
return {
|
|
1745
|
+
value: config.url,
|
|
1746
|
+
type: "url",
|
|
1747
|
+
integrity: config.integrity
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
return {
|
|
1751
|
+
value: config.url,
|
|
1752
|
+
type: "url",
|
|
1753
|
+
integrity: config.integrity
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
return {
|
|
1759
|
+
value: `${fallbackCdnBase}/${specifier}`,
|
|
1760
|
+
type: "url"
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
// libs/uipack/src/component/types.ts
|
|
1767
|
+
function isNpmSource(source) {
|
|
1768
|
+
return typeof source === "object" && source !== null && "npm" in source;
|
|
1769
|
+
}
|
|
1770
|
+
function isFileSource(source) {
|
|
1771
|
+
return typeof source === "object" && source !== null && "file" in source;
|
|
1772
|
+
}
|
|
1773
|
+
function isImportSource(source) {
|
|
1774
|
+
return typeof source === "object" && source !== null && "import" in source;
|
|
1775
|
+
}
|
|
1776
|
+
function isFunctionSource(source) {
|
|
1777
|
+
return typeof source === "function";
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
// libs/uipack/src/component/transpiler.ts
|
|
1781
|
+
function bundleFileSource(source, filename, resolveDir, componentName) {
|
|
1782
|
+
const esbuild = require("esbuild");
|
|
1783
|
+
const mountCode = `
|
|
1784
|
+
// --- Auto-generated mount ---
|
|
1785
|
+
import { createRoot } from 'react-dom/client';
|
|
1786
|
+
import { McpBridgeProvider } from '@frontmcp/ui/react';
|
|
1787
|
+
import React from 'react';
|
|
1788
|
+
const __root = document.getElementById('root');
|
|
1789
|
+
if (__root) {
|
|
1790
|
+
createRoot(__root).render(
|
|
1791
|
+
React.createElement(McpBridgeProvider, null,
|
|
1792
|
+
React.createElement(${componentName})
|
|
1793
|
+
)
|
|
1794
|
+
);
|
|
1795
|
+
}`;
|
|
1796
|
+
const loader = {
|
|
1797
|
+
".tsx": "tsx",
|
|
1798
|
+
".jsx": "jsx"
|
|
1799
|
+
};
|
|
1800
|
+
const stdinLoader = filename.endsWith(".tsx") ? "tsx" : "jsx";
|
|
1801
|
+
try {
|
|
1802
|
+
const result = esbuild.buildSync({
|
|
1803
|
+
stdin: {
|
|
1804
|
+
contents: source + "\n" + mountCode,
|
|
1805
|
+
loader: stdinLoader,
|
|
1806
|
+
resolveDir,
|
|
1807
|
+
sourcefile: filename
|
|
1808
|
+
},
|
|
1809
|
+
bundle: true,
|
|
1810
|
+
write: false,
|
|
1811
|
+
format: "esm",
|
|
1812
|
+
target: "es2020",
|
|
1813
|
+
jsx: "transform",
|
|
1814
|
+
jsxFactory: "React.createElement",
|
|
1815
|
+
jsxFragment: "React.Fragment",
|
|
1816
|
+
external: ["react", "react-dom"],
|
|
1817
|
+
define: { "process.env.NODE_ENV": '"production"' },
|
|
1818
|
+
platform: "browser",
|
|
1819
|
+
treeShaking: true,
|
|
1820
|
+
logLevel: "warning",
|
|
1821
|
+
loader
|
|
1822
|
+
});
|
|
1823
|
+
return { code: result.outputFiles[0].text };
|
|
1824
|
+
} catch (err) {
|
|
1825
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1826
|
+
throw new Error(
|
|
1827
|
+
`Failed to bundle FileSource "${filename}": ${message}. Ensure workspace packages are built (e.g. nx build ui).`
|
|
1828
|
+
);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
function extractDefaultExportName(code) {
|
|
1832
|
+
let match = code.match(/export\s+default\s+(?:function|class)\s+(\w+)/);
|
|
1833
|
+
if (match) return match[1];
|
|
1834
|
+
match = code.match(/export\s+default\s+(\w+)\s*;/);
|
|
1835
|
+
if (match) return match[1];
|
|
1836
|
+
return null;
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// libs/uipack/src/component/loader.ts
|
|
1840
|
+
var DEFAULT_META = {
|
|
1841
|
+
mcpAware: false,
|
|
1842
|
+
renderer: "auto"
|
|
391
1843
|
};
|
|
392
|
-
function
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
1844
|
+
function resolveUISource(source, options) {
|
|
1845
|
+
const resolver = options?.resolver ?? createEsmShResolver();
|
|
1846
|
+
if (isNpmSource(source)) {
|
|
1847
|
+
return resolveNpmSource(source, resolver);
|
|
1848
|
+
}
|
|
1849
|
+
if (isImportSource(source)) {
|
|
1850
|
+
return resolveImportSource(source);
|
|
1851
|
+
}
|
|
1852
|
+
if (isFileSource(source)) {
|
|
1853
|
+
return resolveFileSource(source);
|
|
1854
|
+
}
|
|
1855
|
+
if (isFunctionSource(source)) {
|
|
1856
|
+
return resolveFunctionSource(source, options?.input, options?.output);
|
|
1857
|
+
}
|
|
1858
|
+
throw new Error("Unknown UISource type");
|
|
1859
|
+
}
|
|
1860
|
+
function resolveNpmSource(source, resolver) {
|
|
1861
|
+
const specifier = source.version ? `${source.npm}@${source.version}` : source.npm;
|
|
1862
|
+
const pkgName = getPackageName(source.npm);
|
|
1863
|
+
const resolved = resolver.resolve(specifier);
|
|
1864
|
+
const url = resolved?.value ?? `https://esm.sh/${specifier}`;
|
|
1865
|
+
const peerDeps = [];
|
|
1866
|
+
if (pkgName !== "react" && pkgName !== "react-dom") {
|
|
1867
|
+
peerDeps.push("react", "react-dom");
|
|
402
1868
|
}
|
|
403
|
-
|
|
1869
|
+
return {
|
|
1870
|
+
mode: "module",
|
|
1871
|
+
url,
|
|
1872
|
+
exportName: source.exportName ?? "default",
|
|
1873
|
+
meta: { ...DEFAULT_META, renderer: "react" },
|
|
1874
|
+
peerDependencies: peerDeps
|
|
1875
|
+
};
|
|
1876
|
+
}
|
|
1877
|
+
function resolveImportSource(source) {
|
|
1878
|
+
return {
|
|
1879
|
+
mode: "module",
|
|
1880
|
+
url: source.import,
|
|
1881
|
+
exportName: source.exportName ?? "default",
|
|
1882
|
+
meta: { ...DEFAULT_META },
|
|
1883
|
+
peerDependencies: []
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
function resolveFileSource(source) {
|
|
1887
|
+
const path = require("path");
|
|
1888
|
+
const ext = path.extname(source.file).toLowerCase();
|
|
1889
|
+
if (ext === ".tsx" || ext === ".jsx") {
|
|
1890
|
+
const fs = require("fs");
|
|
1891
|
+
const filePath = path.isAbsolute(source.file) ? source.file : path.resolve(process.cwd(), source.file);
|
|
1892
|
+
const rawSource = fs.readFileSync(filePath, "utf-8");
|
|
1893
|
+
const componentName = source.exportName || extractDefaultExportName(rawSource) || "Component";
|
|
1894
|
+
const bundled = bundleFileSource(rawSource, source.file, path.dirname(filePath), componentName);
|
|
1895
|
+
const parsed = parseImports(bundled.code);
|
|
404
1896
|
return {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
1897
|
+
mode: "module",
|
|
1898
|
+
code: bundled.code,
|
|
1899
|
+
imports: [...new Set(parsed.externalImports.map((i) => i.specifier))],
|
|
1900
|
+
exportName: componentName,
|
|
1901
|
+
meta: { mcpAware: true, renderer: "react" },
|
|
1902
|
+
peerDependencies: source.peerDependencies || ["react", "react-dom"],
|
|
1903
|
+
bundled: true
|
|
409
1904
|
};
|
|
410
1905
|
}
|
|
411
|
-
if (
|
|
1906
|
+
if (source.inline) {
|
|
412
1907
|
return {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
1908
|
+
mode: "inline",
|
|
1909
|
+
html: `<!-- File source: ${source.file} (inline mode - content to be resolved at build time) -->`,
|
|
1910
|
+
exportName: "default",
|
|
1911
|
+
meta: { ...DEFAULT_META, renderer: "html" },
|
|
1912
|
+
peerDependencies: []
|
|
417
1913
|
};
|
|
418
1914
|
}
|
|
419
1915
|
return {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
1916
|
+
mode: "module",
|
|
1917
|
+
url: source.file,
|
|
1918
|
+
exportName: "default",
|
|
1919
|
+
meta: { ...DEFAULT_META },
|
|
1920
|
+
peerDependencies: []
|
|
424
1921
|
};
|
|
425
1922
|
}
|
|
426
|
-
function
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
|
|
1923
|
+
function resolveFunctionSource(source, input, output) {
|
|
1924
|
+
const html = source(input, output);
|
|
1925
|
+
return {
|
|
1926
|
+
mode: "inline",
|
|
1927
|
+
html,
|
|
1928
|
+
exportName: "default",
|
|
1929
|
+
meta: { mcpAware: false, renderer: "html" },
|
|
1930
|
+
peerDependencies: []
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
function generateMountScript(resolved, propsMapping) {
|
|
1934
|
+
if (resolved.mode !== "module" || !resolved.url) {
|
|
1935
|
+
return "";
|
|
1936
|
+
}
|
|
1937
|
+
const componentImport = resolved.exportName === "default" ? `import Component from '${resolved.url}';` : `import { ${resolved.exportName} as Component } from '${resolved.url}';`;
|
|
1938
|
+
if (resolved.meta.mcpAware) {
|
|
1939
|
+
return `${componentImport}
|
|
1940
|
+
import React from 'react';
|
|
1941
|
+
import { createRoot } from 'react-dom/client';
|
|
1942
|
+
createRoot(document.getElementById('root'))
|
|
1943
|
+
.render(React.createElement(Component));`;
|
|
430
1944
|
}
|
|
431
|
-
|
|
1945
|
+
const propsCode = propsMapping ? generateMappedPropsCode(propsMapping) : "window.__mcpToolOutput";
|
|
1946
|
+
return `${componentImport}
|
|
1947
|
+
import React from 'react';
|
|
1948
|
+
import { createRoot } from 'react-dom/client';
|
|
1949
|
+
const output = window.__mcpToolOutput;
|
|
1950
|
+
const props = ${propsCode};
|
|
1951
|
+
createRoot(document.getElementById('root'))
|
|
1952
|
+
.render(React.createElement(Component, props));`;
|
|
432
1953
|
}
|
|
433
|
-
function
|
|
434
|
-
const
|
|
435
|
-
|
|
1954
|
+
function generateMappedPropsCode(mapping) {
|
|
1955
|
+
const entries = Object.entries(mapping).map(([prop, path]) => {
|
|
1956
|
+
const accessPath = path.split(".").map((part) => `[${safeJsonForScript(part)}]`).join("");
|
|
1957
|
+
return `${safeJsonForScript(prop)}: output${accessPath}`;
|
|
1958
|
+
}).join(", ");
|
|
1959
|
+
return `({ ${entries} })`;
|
|
436
1960
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
1961
|
+
|
|
1962
|
+
// libs/uipack/src/resolver/import-map.ts
|
|
1963
|
+
function createImportMapFromResolved(resolved) {
|
|
1964
|
+
const imports = {};
|
|
1965
|
+
const integrity = {};
|
|
1966
|
+
for (const [specifier, res] of Object.entries(resolved)) {
|
|
1967
|
+
if (res.type === "url") {
|
|
1968
|
+
imports[specifier] = res.value;
|
|
1969
|
+
if (res.integrity) {
|
|
1970
|
+
integrity[res.value] = res.integrity;
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
return {
|
|
1975
|
+
imports,
|
|
1976
|
+
integrity: Object.keys(integrity).length > 0 ? integrity : void 0
|
|
1977
|
+
};
|
|
440
1978
|
}
|
|
441
|
-
function
|
|
442
|
-
const
|
|
443
|
-
return
|
|
1979
|
+
function generateImportMapScriptTag(map) {
|
|
1980
|
+
const json = JSON.stringify(map, null, 2).replace(/<\//g, "<\\/");
|
|
1981
|
+
return `<script type="importmap">
|
|
1982
|
+
${json}
|
|
1983
|
+
</script>`;
|
|
444
1984
|
}
|
|
445
1985
|
|
|
446
|
-
// libs/uipack/src/
|
|
447
|
-
|
|
1986
|
+
// libs/uipack/src/component/renderer.ts
|
|
1987
|
+
function renderComponent(config, shellConfig) {
|
|
1988
|
+
const resolver = shellConfig.resolver ?? createEsmShResolver();
|
|
1989
|
+
const resolved = resolveUISource(config.source, {
|
|
1990
|
+
resolver,
|
|
1991
|
+
input: shellConfig.input,
|
|
1992
|
+
output: shellConfig.output
|
|
1993
|
+
});
|
|
1994
|
+
const mergedShellConfig = {
|
|
1995
|
+
...shellConfig,
|
|
1996
|
+
withShell: config.withShell ?? shellConfig.withShell,
|
|
1997
|
+
includeBridge: config.includeBridge ?? shellConfig.includeBridge,
|
|
1998
|
+
customShell: config.customShell ?? shellConfig.customShell
|
|
1999
|
+
};
|
|
2000
|
+
if (resolved.mode === "inline") {
|
|
2001
|
+
return buildShell(resolved.html ?? "", mergedShellConfig);
|
|
2002
|
+
}
|
|
2003
|
+
const content = buildModuleContent(resolved, resolver, config.props);
|
|
2004
|
+
return buildShell(content, mergedShellConfig);
|
|
2005
|
+
}
|
|
2006
|
+
function buildModuleContent(resolved, resolver, propsMapping) {
|
|
2007
|
+
const parts = [];
|
|
2008
|
+
if (resolved.code && resolved.bundled) {
|
|
2009
|
+
const importEntries = {};
|
|
2010
|
+
for (const spec of resolved.imports || []) {
|
|
2011
|
+
const depResolved = resolver.resolve(spec);
|
|
2012
|
+
if (depResolved) {
|
|
2013
|
+
importEntries[spec] = depResolved;
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
if (Object.keys(importEntries).length > 0) {
|
|
2017
|
+
const importMap = createImportMapFromResolved(importEntries);
|
|
2018
|
+
parts.push(generateImportMapScriptTag(importMap));
|
|
2019
|
+
}
|
|
2020
|
+
parts.push('<div id="root"></div>');
|
|
2021
|
+
parts.push(`<script type="module">
|
|
2022
|
+
${resolved.code}
|
|
2023
|
+
</script>`);
|
|
2024
|
+
} else if (resolved.code) {
|
|
2025
|
+
const importEntries = {};
|
|
2026
|
+
const allSpecifiers = /* @__PURE__ */ new Set([
|
|
2027
|
+
...resolved.imports || [],
|
|
2028
|
+
"react-dom/client",
|
|
2029
|
+
// needed by mount script
|
|
2030
|
+
"@frontmcp/ui/react",
|
|
2031
|
+
// needed for McpBridgeProvider
|
|
2032
|
+
// React subpath entries needed by esm.sh externalized modules:
|
|
2033
|
+
"react/jsx-runtime",
|
|
2034
|
+
"react/jsx-dev-runtime",
|
|
2035
|
+
"react-dom/server",
|
|
2036
|
+
"react-dom/static"
|
|
2037
|
+
]);
|
|
2038
|
+
const coreDeps = /* @__PURE__ */ new Set([
|
|
2039
|
+
"react",
|
|
2040
|
+
"react-dom",
|
|
2041
|
+
"react-dom/client",
|
|
2042
|
+
"react/jsx-runtime",
|
|
2043
|
+
"react/jsx-dev-runtime",
|
|
2044
|
+
"react-dom/server",
|
|
2045
|
+
"react-dom/static"
|
|
2046
|
+
]);
|
|
2047
|
+
for (const spec of allSpecifiers) {
|
|
2048
|
+
const depResolved = resolver.resolve(spec);
|
|
2049
|
+
if (depResolved) {
|
|
2050
|
+
if (!coreDeps.has(spec) && depResolved.value?.includes("esm.sh")) {
|
|
2051
|
+
importEntries[spec] = {
|
|
2052
|
+
...depResolved,
|
|
2053
|
+
value: addExternalParam(depResolved.value, ["react", "react-dom"])
|
|
2054
|
+
};
|
|
2055
|
+
} else {
|
|
2056
|
+
importEntries[spec] = depResolved;
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
if (importEntries["react-dom"] && !importEntries["react-dom/client"]) {
|
|
2061
|
+
const rdc = resolver.resolve("react-dom/client");
|
|
2062
|
+
if (rdc) importEntries["react-dom/client"] = rdc;
|
|
2063
|
+
}
|
|
2064
|
+
if (Object.keys(importEntries).length > 0) {
|
|
2065
|
+
const importMap = createImportMapFromResolved(importEntries);
|
|
2066
|
+
parts.push(generateImportMapScriptTag(importMap));
|
|
2067
|
+
}
|
|
2068
|
+
parts.push('<div id="root"></div>');
|
|
2069
|
+
const mountCode = generateInlineMountCode(resolved.exportName);
|
|
2070
|
+
parts.push(`<script type="module">
|
|
2071
|
+
${resolved.code}
|
|
2072
|
+
${mountCode}
|
|
2073
|
+
</script>`);
|
|
2074
|
+
} else {
|
|
2075
|
+
const importEntries = {};
|
|
2076
|
+
for (const dep of resolved.peerDependencies) {
|
|
2077
|
+
const depResolved = resolver.resolve(dep);
|
|
2078
|
+
if (depResolved) {
|
|
2079
|
+
importEntries[dep] = depResolved;
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
if (importEntries["react-dom"]) {
|
|
2083
|
+
const reactDomClient = resolver.resolve("react-dom/client");
|
|
2084
|
+
if (reactDomClient) {
|
|
2085
|
+
importEntries["react-dom/client"] = reactDomClient;
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
if (Object.keys(importEntries).length > 0) {
|
|
2089
|
+
const importMap = createImportMapFromResolved(importEntries);
|
|
2090
|
+
parts.push(generateImportMapScriptTag(importMap));
|
|
2091
|
+
}
|
|
2092
|
+
parts.push('<div id="root"></div>');
|
|
2093
|
+
const mountScript = generateMountScript(resolved, propsMapping);
|
|
2094
|
+
if (mountScript) {
|
|
2095
|
+
parts.push(`<script type="module">
|
|
2096
|
+
${mountScript}
|
|
2097
|
+
</script>`);
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
return parts.join("\n");
|
|
2101
|
+
}
|
|
2102
|
+
function addExternalParam(url, externals) {
|
|
2103
|
+
const sep = url.includes("?") ? "&" : "?";
|
|
2104
|
+
return `${url}${sep}external=${externals.join(",")}`;
|
|
2105
|
+
}
|
|
2106
|
+
function generateInlineMountCode(componentName) {
|
|
2107
|
+
return `
|
|
2108
|
+
// --- Mount ---
|
|
2109
|
+
import { createRoot as __createRoot } from 'react-dom/client';
|
|
2110
|
+
import { McpBridgeProvider as __McpBridgeProvider } from '@frontmcp/ui/react';
|
|
2111
|
+
const __root = document.getElementById('root');
|
|
2112
|
+
if (__root) {
|
|
2113
|
+
__createRoot(__root).render(
|
|
2114
|
+
React.createElement(__McpBridgeProvider, null,
|
|
2115
|
+
React.createElement(${componentName})
|
|
2116
|
+
)
|
|
2117
|
+
);
|
|
2118
|
+
}`;
|
|
2119
|
+
}
|
|
448
2120
|
|
|
449
|
-
// libs/uipack/src/adapters/
|
|
450
|
-
function
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
return {
|
|
454
|
-
content: [{ type: "text", text: (0, import_utils.safeStringify)(rawOutput) }],
|
|
455
|
-
structuredContent: useStructuredContent ? rawOutput : void 0,
|
|
456
|
-
contentCleared: false,
|
|
457
|
-
format: "json-only"
|
|
458
|
-
};
|
|
2121
|
+
// libs/uipack/src/adapters/type-detector.ts
|
|
2122
|
+
function detectUIType(template) {
|
|
2123
|
+
if (template === null || template === void 0) {
|
|
2124
|
+
return "auto";
|
|
459
2125
|
}
|
|
460
|
-
if (
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
structuredContent: useStructuredContent ? rawOutput : void 0,
|
|
464
|
-
contentCleared: false,
|
|
465
|
-
format: "json-only"
|
|
466
|
-
};
|
|
2126
|
+
if (typeof template === "object" && template !== null && "file" in template) {
|
|
2127
|
+
const file = template.file;
|
|
2128
|
+
if (/\.(tsx|jsx)$/i.test(file)) return "react";
|
|
467
2129
|
}
|
|
468
|
-
if (
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
2130
|
+
if (typeof template === "function") {
|
|
2131
|
+
const proto = template.prototype;
|
|
2132
|
+
if (proto && typeof proto.render === "function") {
|
|
2133
|
+
return "react";
|
|
2134
|
+
}
|
|
2135
|
+
const asRecord = template;
|
|
2136
|
+
if (asRecord["$$typeof"] !== void 0) {
|
|
2137
|
+
return "react";
|
|
2138
|
+
}
|
|
2139
|
+
if (template.name && /^[A-Z]/.test(template.name)) {
|
|
2140
|
+
return "react";
|
|
2141
|
+
}
|
|
2142
|
+
return "html";
|
|
2143
|
+
}
|
|
2144
|
+
if (typeof template === "string") {
|
|
2145
|
+
if (template.includes("<") && template.includes(">")) {
|
|
2146
|
+
return "html";
|
|
2147
|
+
}
|
|
2148
|
+
return "markdown";
|
|
2149
|
+
}
|
|
2150
|
+
return "auto";
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
// libs/uipack/src/adapters/content-detector.ts
|
|
2154
|
+
var CHART_TYPES = /* @__PURE__ */ new Set(["bar", "line", "pie", "area", "scatter", "doughnut", "radar", "polarArea", "bubble"]);
|
|
2155
|
+
var MERMAID_PREFIXES = [
|
|
2156
|
+
"flowchart",
|
|
2157
|
+
"graph",
|
|
2158
|
+
"sequenceDiagram",
|
|
2159
|
+
"classDiagram",
|
|
2160
|
+
"stateDiagram",
|
|
2161
|
+
"erDiagram",
|
|
2162
|
+
"gantt",
|
|
2163
|
+
"pie",
|
|
2164
|
+
"gitGraph",
|
|
2165
|
+
"journey",
|
|
2166
|
+
"mindmap",
|
|
2167
|
+
"timeline",
|
|
2168
|
+
"quadrantChart",
|
|
2169
|
+
"sankey",
|
|
2170
|
+
"xychart"
|
|
2171
|
+
];
|
|
2172
|
+
var PDF_MAGIC = "JVBERi";
|
|
2173
|
+
function detectContentType(value) {
|
|
2174
|
+
if (value === null || value === void 0) {
|
|
2175
|
+
return "text";
|
|
2176
|
+
}
|
|
2177
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
2178
|
+
const obj = value;
|
|
2179
|
+
if (typeof obj["type"] === "string" && CHART_TYPES.has(obj["type"])) {
|
|
2180
|
+
return "chart";
|
|
2181
|
+
}
|
|
2182
|
+
return "json";
|
|
2183
|
+
}
|
|
2184
|
+
if (typeof value === "string") {
|
|
2185
|
+
const trimmed = value.trimStart();
|
|
2186
|
+
if (trimmed.startsWith(PDF_MAGIC)) {
|
|
2187
|
+
return "pdf";
|
|
2188
|
+
}
|
|
2189
|
+
for (const prefix of MERMAID_PREFIXES) {
|
|
2190
|
+
if (trimmed.startsWith(prefix)) {
|
|
2191
|
+
return "mermaid";
|
|
485
2192
|
}
|
|
486
2193
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
format: "json-only"
|
|
492
|
-
};
|
|
2194
|
+
if (trimmed.includes("<") && trimmed.includes(">")) {
|
|
2195
|
+
return "html";
|
|
2196
|
+
}
|
|
2197
|
+
return "text";
|
|
493
2198
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
2199
|
+
return "text";
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
// libs/uipack/src/adapters/content-renderers.ts
|
|
2203
|
+
var CHART_JS_CDN = "https://esm.sh/chart.js@4/auto";
|
|
2204
|
+
var MERMAID_CDN = "https://esm.sh/mermaid@11/dist/mermaid.esm.min.mjs";
|
|
2205
|
+
function buildChartHtml(chartJson) {
|
|
2206
|
+
const chartData = safeJsonForScript(chartJson);
|
|
2207
|
+
const content = `
|
|
2208
|
+
<canvas id="chart" style="max-width:100%;max-height:80vh;"></canvas>
|
|
2209
|
+
<script type="module">
|
|
2210
|
+
import Chart from '${CHART_JS_CDN}';
|
|
2211
|
+
const ctx = document.getElementById('chart').getContext('2d');
|
|
2212
|
+
new Chart(ctx, ${chartData});
|
|
2213
|
+
</script>`;
|
|
2214
|
+
const result = buildShell(content, {
|
|
2215
|
+
toolName: "chart",
|
|
2216
|
+
csp: {
|
|
2217
|
+
resourceDomains: ["https://esm.sh"],
|
|
2218
|
+
connectDomains: ["https://esm.sh"]
|
|
2219
|
+
},
|
|
2220
|
+
includeBridge: false
|
|
2221
|
+
});
|
|
2222
|
+
return result.html;
|
|
2223
|
+
}
|
|
2224
|
+
function buildMermaidHtml(mermaidCode) {
|
|
2225
|
+
const escaped = escapeHtml(mermaidCode);
|
|
2226
|
+
const content = `
|
|
2227
|
+
<pre class="mermaid">${escaped}</pre>
|
|
2228
|
+
<script type="module">
|
|
2229
|
+
import mermaid from '${MERMAID_CDN}';
|
|
2230
|
+
mermaid.initialize({ startOnLoad: true, theme: 'default' });
|
|
2231
|
+
</script>`;
|
|
2232
|
+
const result = buildShell(content, {
|
|
2233
|
+
toolName: "mermaid",
|
|
2234
|
+
csp: {
|
|
2235
|
+
resourceDomains: ["https://esm.sh"],
|
|
2236
|
+
connectDomains: ["https://esm.sh"]
|
|
2237
|
+
},
|
|
2238
|
+
includeBridge: false
|
|
2239
|
+
});
|
|
2240
|
+
return result.html;
|
|
2241
|
+
}
|
|
2242
|
+
function buildPdfHtml(base64) {
|
|
2243
|
+
const content = `
|
|
2244
|
+
<div id="pdf-container" style="width:100%;overflow:auto;text-align:center;"></div>
|
|
2245
|
+
<script type="module">
|
|
2246
|
+
import * as pdfjsLib from 'https://esm.sh/pdfjs-dist@4/build/pdf.min.mjs';
|
|
2247
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://esm.sh/pdfjs-dist@4/build/pdf.worker.min.mjs';
|
|
2248
|
+
|
|
2249
|
+
const base64 = ${JSON.stringify(base64)};
|
|
2250
|
+
const binaryStr = atob(base64);
|
|
2251
|
+
const bytes = new Uint8Array(binaryStr.length);
|
|
2252
|
+
for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
|
|
2253
|
+
|
|
2254
|
+
const pdf = await pdfjsLib.getDocument({ data: bytes }).promise;
|
|
2255
|
+
const container = document.getElementById('pdf-container');
|
|
2256
|
+
for (let i = 1; i <= pdf.numPages; i++) {
|
|
2257
|
+
const page = await pdf.getPage(i);
|
|
2258
|
+
const viewport = page.getViewport({ scale: 1.5 });
|
|
2259
|
+
const canvas = document.createElement('canvas');
|
|
2260
|
+
canvas.width = viewport.width;
|
|
2261
|
+
canvas.height = viewport.height;
|
|
2262
|
+
canvas.style.marginBottom = '8px';
|
|
2263
|
+
container.appendChild(canvas);
|
|
2264
|
+
await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise;
|
|
2265
|
+
}
|
|
2266
|
+
</script>`;
|
|
2267
|
+
const result = buildShell(content, {
|
|
2268
|
+
toolName: "pdf",
|
|
2269
|
+
csp: {
|
|
2270
|
+
resourceDomains: ["https://esm.sh"],
|
|
2271
|
+
connectDomains: ["https://esm.sh"]
|
|
2272
|
+
},
|
|
2273
|
+
includeBridge: false
|
|
2274
|
+
});
|
|
2275
|
+
return result.html;
|
|
2276
|
+
}
|
|
2277
|
+
function wrapDetectedContent(value) {
|
|
2278
|
+
const contentType = detectContentType(value);
|
|
2279
|
+
switch (contentType) {
|
|
2280
|
+
case "chart":
|
|
2281
|
+
return buildChartHtml(value);
|
|
2282
|
+
case "mermaid":
|
|
2283
|
+
return buildMermaidHtml(value);
|
|
2284
|
+
case "pdf":
|
|
2285
|
+
return buildPdfHtml(value);
|
|
2286
|
+
case "html":
|
|
2287
|
+
return void 0;
|
|
2288
|
+
default:
|
|
2289
|
+
return void 0;
|
|
501
2290
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
// libs/uipack/src/adapters/template-renderer.ts
|
|
2294
|
+
function renderToolTemplate(options) {
|
|
2295
|
+
const { toolName, input, output, template, platformType, resolver } = options;
|
|
2296
|
+
const uiType = detectUIType(template);
|
|
2297
|
+
const shellConfig = {
|
|
2298
|
+
toolName,
|
|
2299
|
+
input,
|
|
2300
|
+
output,
|
|
2301
|
+
includeBridge: true,
|
|
2302
|
+
resolver
|
|
2303
|
+
};
|
|
2304
|
+
let html;
|
|
2305
|
+
let hash = "";
|
|
2306
|
+
let size = 0;
|
|
2307
|
+
if (typeof template === "object" && template !== null && "file" in template) {
|
|
2308
|
+
const cspResourceDomains = ["https://esm.sh"];
|
|
2309
|
+
const cspConnectDomains = ["https://esm.sh"];
|
|
2310
|
+
if (resolver && "overrides" in resolver) {
|
|
2311
|
+
const overrides = resolver.overrides;
|
|
2312
|
+
if (overrides) {
|
|
2313
|
+
for (const url of Object.values(overrides)) {
|
|
2314
|
+
try {
|
|
2315
|
+
const origin = new URL(url).origin;
|
|
2316
|
+
if (!cspResourceDomains.includes(origin)) cspResourceDomains.push(origin);
|
|
2317
|
+
if (!cspConnectDomains.includes(origin)) cspConnectDomains.push(origin);
|
|
2318
|
+
} catch {
|
|
2319
|
+
}
|
|
519
2320
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
const cspConfig = {
|
|
2324
|
+
resourceDomains: cspResourceDomains,
|
|
2325
|
+
connectDomains: cspConnectDomains
|
|
523
2326
|
};
|
|
2327
|
+
const result = renderComponent({ source: template }, { ...shellConfig, csp: cspConfig });
|
|
2328
|
+
html = result.html;
|
|
2329
|
+
hash = result.hash;
|
|
2330
|
+
size = result.size;
|
|
2331
|
+
} else if (typeof template === "function") {
|
|
2332
|
+
if (uiType === "react") {
|
|
2333
|
+
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
2334
|
+
html = shellResult.html;
|
|
2335
|
+
hash = shellResult.hash;
|
|
2336
|
+
size = shellResult.size;
|
|
2337
|
+
} else {
|
|
2338
|
+
const helpers = createTemplateHelpers();
|
|
2339
|
+
const ctx = { input, output, helpers };
|
|
2340
|
+
const rawResult = template(ctx);
|
|
2341
|
+
const wrapped = wrapDetectedContent(rawResult);
|
|
2342
|
+
if (wrapped) {
|
|
2343
|
+
html = wrapped;
|
|
2344
|
+
} else {
|
|
2345
|
+
const textContent = typeof rawResult === "string" ? rawResult : `<pre>${JSON.stringify(rawResult, null, 2)}</pre>`;
|
|
2346
|
+
const shellResult = buildShell(textContent, shellConfig);
|
|
2347
|
+
html = shellResult.html;
|
|
2348
|
+
hash = shellResult.hash;
|
|
2349
|
+
size = shellResult.size;
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
} else if (typeof template === "string") {
|
|
2353
|
+
const shellResult = buildShell(template, shellConfig);
|
|
2354
|
+
html = shellResult.html;
|
|
2355
|
+
hash = shellResult.hash;
|
|
2356
|
+
size = shellResult.size;
|
|
2357
|
+
} else {
|
|
2358
|
+
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
2359
|
+
html = shellResult.html;
|
|
2360
|
+
hash = shellResult.hash;
|
|
2361
|
+
size = shellResult.size;
|
|
2362
|
+
}
|
|
2363
|
+
const htmlKey = "ui/html";
|
|
2364
|
+
const meta = {
|
|
2365
|
+
[htmlKey]: html,
|
|
2366
|
+
"ui/type": uiType,
|
|
2367
|
+
"ui/mimeType": MCP_APPS_MIME_TYPE
|
|
2368
|
+
};
|
|
2369
|
+
return {
|
|
2370
|
+
html,
|
|
2371
|
+
uiType,
|
|
2372
|
+
hash,
|
|
2373
|
+
size: size || Buffer.byteLength(html, "utf8"),
|
|
2374
|
+
meta
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
// libs/uipack/src/adapters/base-template.ts
|
|
2379
|
+
function createDefaultBaseTemplate(options) {
|
|
2380
|
+
const { toolName } = options;
|
|
2381
|
+
const bridgeScript = `<script>${generateBridgeIIFE({ minify: true })}</script>`;
|
|
2382
|
+
const content = `
|
|
2383
|
+
${bridgeScript}
|
|
2384
|
+
<div id="root">
|
|
2385
|
+
<div style="font-family:system-ui,sans-serif;padding:1rem;color:#374151;">
|
|
2386
|
+
<p style="color:#6b7280;font-size:0.875rem;">Waiting for tool output...</p>
|
|
2387
|
+
<p style="color:#9ca3af;font-size:0.75rem;">Tool: <code>${toolName}</code></p>
|
|
2388
|
+
</div>
|
|
2389
|
+
</div>
|
|
2390
|
+
<script>
|
|
2391
|
+
(function() {
|
|
2392
|
+
var bridge = window.FrontMcpBridge;
|
|
2393
|
+
if (bridge && typeof bridge.onToolResult === 'function') {
|
|
2394
|
+
bridge.onToolResult(function(data) {
|
|
2395
|
+
var root = document.getElementById('root');
|
|
2396
|
+
if (root && data) {
|
|
2397
|
+
root.innerHTML = '<pre style="font-family:monospace;white-space:pre-wrap;padding:1rem;">' +
|
|
2398
|
+
JSON.stringify(data, null, 2).replace(/</g, '<').replace(/>/g, '>') +
|
|
2399
|
+
'</pre>';
|
|
2400
|
+
}
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2403
|
+
})();
|
|
2404
|
+
</script>`;
|
|
2405
|
+
const result = buildShell(content, {
|
|
2406
|
+
toolName,
|
|
2407
|
+
includeBridge: false,
|
|
2408
|
+
// Bridge already included via inline script above
|
|
2409
|
+
title: `Widget: ${toolName}`
|
|
2410
|
+
});
|
|
2411
|
+
return result.html;
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
// libs/uipack/src/adapters/cdn-info.ts
|
|
2415
|
+
var ESM_SH_BASE = "https://esm.sh";
|
|
2416
|
+
var REACT_DEPS = [
|
|
2417
|
+
{ name: "react", url: `${ESM_SH_BASE}/react@18` },
|
|
2418
|
+
{ name: "react-dom", url: `${ESM_SH_BASE}/react-dom@18` }
|
|
2419
|
+
];
|
|
2420
|
+
function buildCDNInfoForUIType(uiType) {
|
|
2421
|
+
const deps = [];
|
|
2422
|
+
switch (uiType) {
|
|
2423
|
+
case "react":
|
|
2424
|
+
deps.push(...REACT_DEPS);
|
|
2425
|
+
break;
|
|
2426
|
+
case "mdx":
|
|
2427
|
+
deps.push(...REACT_DEPS);
|
|
2428
|
+
deps.push({ name: "marked", url: `${ESM_SH_BASE}/marked@latest` });
|
|
2429
|
+
break;
|
|
2430
|
+
case "markdown":
|
|
2431
|
+
deps.push({ name: "marked", url: `${ESM_SH_BASE}/marked@latest` });
|
|
2432
|
+
break;
|
|
2433
|
+
case "auto":
|
|
2434
|
+
deps.push(...REACT_DEPS);
|
|
2435
|
+
deps.push({ name: "marked", url: `${ESM_SH_BASE}/marked@latest` });
|
|
2436
|
+
break;
|
|
2437
|
+
case "html":
|
|
2438
|
+
default:
|
|
2439
|
+
break;
|
|
524
2440
|
}
|
|
525
2441
|
return {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
format: "json-only"
|
|
2442
|
+
base: ESM_SH_BASE,
|
|
2443
|
+
dependencies: deps
|
|
529
2444
|
};
|
|
530
2445
|
}
|
|
531
2446
|
// Annotate the CommonJS export names for ESM import in node:
|
|
532
2447
|
0 && (module.exports = {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
2448
|
+
MCP_APPS_EXTENSION_ID,
|
|
2449
|
+
MCP_APPS_MIME_TYPE,
|
|
2450
|
+
buildCDNInfoForUIType,
|
|
2451
|
+
buildChartHtml,
|
|
2452
|
+
buildMermaidHtml,
|
|
2453
|
+
buildPdfHtml,
|
|
536
2454
|
buildToolResponseContent,
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
resolveServingMode
|
|
2455
|
+
createDefaultBaseTemplate,
|
|
2456
|
+
detectContentType,
|
|
2457
|
+
detectUIType,
|
|
2458
|
+
isUIRenderFailure,
|
|
2459
|
+
renderToolTemplate,
|
|
2460
|
+
resolveServingMode,
|
|
2461
|
+
wrapDetectedContent
|
|
545
2462
|
});
|