@frontmcp/uipack 1.8.5 → 1.8.7
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/adapters/index.js +339 -35
- package/adapters/markdown.d.ts +9 -0
- package/adapters/markdown.d.ts.map +1 -0
- package/adapters/template-renderer.d.ts +8 -0
- package/adapters/template-renderer.d.ts.map +1 -1
- package/bridge-runtime/iife-generator.d.ts.map +1 -1
- package/bridge-runtime/index.js +169 -22
- package/component/index.js +223 -26
- package/component/loader.d.ts.map +1 -1
- package/component/transpiler.d.ts.map +1 -1
- package/esm/adapters/index.mjs +339 -35
- package/esm/bridge-runtime/index.mjs +169 -22
- package/esm/component/index.mjs +223 -26
- package/esm/index.mjs +339 -35
- package/esm/package.json +8 -2
- package/esm/shell/index.mjs +169 -22
- package/index.js +339 -35
- package/package.json +8 -2
- package/shell/index.js +169 -22
- package/types/ui-config.d.ts +5 -3
- package/types/ui-config.d.ts.map +1 -1
package/esm/component/index.mjs
CHANGED
|
@@ -469,8 +469,24 @@ function isFrontmcpUiResolvable(...candidatePaths) {
|
|
|
469
469
|
}
|
|
470
470
|
|
|
471
471
|
// libs/uipack/src/component/transpiler.ts
|
|
472
|
+
function loadEsbuild(filename) {
|
|
473
|
+
try {
|
|
474
|
+
return __require("esbuild");
|
|
475
|
+
} catch (err) {
|
|
476
|
+
if (!isMissingEsbuild(err)) throw err;
|
|
477
|
+
const subject = filename ? `FileSource widget "${filename}"` : "A FileSource widget";
|
|
478
|
+
throw new Error(
|
|
479
|
+
`${subject} needs esbuild, which is not installed. @frontmcp/uipack loads esbuild on demand to bundle FileSource widgets (.tsx/.jsx) when the tool is called, so it must be installed where the server runs: \`npm install esbuild\` (in "dependencies", not "devDependencies"). Projects created with \`frontmcp create\` already get it through the \`frontmcp\` package.`,
|
|
480
|
+
{ cause: err }
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function isMissingEsbuild(err) {
|
|
485
|
+
if (!(err instanceof Error)) return false;
|
|
486
|
+
return err.code === "MODULE_NOT_FOUND" && /Cannot find module ['"]esbuild['"]/.test(err.message);
|
|
487
|
+
}
|
|
472
488
|
function transpileReactSource(source, filename) {
|
|
473
|
-
const esbuild =
|
|
489
|
+
const esbuild = loadEsbuild(filename);
|
|
474
490
|
const loader = filename?.endsWith(".tsx") ? "tsx" : "jsx";
|
|
475
491
|
const result = esbuild.transformSync(source, {
|
|
476
492
|
loader,
|
|
@@ -489,7 +505,7 @@ function bundleFileSource(source, filename, resolveDir, componentName, options =
|
|
|
489
505
|
`FileSource widget "${filename}" requires the @frontmcp/ui package, which provides the React bridge mount that's injected at bundle time. Install it (e.g. \`npm install @frontmcp/ui\` or \`yarn add @frontmcp/ui\`) and try again.`
|
|
490
506
|
);
|
|
491
507
|
}
|
|
492
|
-
const esbuild =
|
|
508
|
+
const esbuild = loadEsbuild(filename);
|
|
493
509
|
const mountCode = `
|
|
494
510
|
// --- Auto-generated mount ---
|
|
495
511
|
import { createElement as __h } from 'react';
|
|
@@ -586,7 +602,8 @@ if (__root) {
|
|
|
586
602
|
} catch (err) {
|
|
587
603
|
const message = err instanceof Error ? err.message : String(err);
|
|
588
604
|
throw new Error(
|
|
589
|
-
`Failed to bundle FileSource "${filename}": ${message}. If the error mentions @frontmcp/ui or @frontmcp/uipack, ensure both packages are installed in the consuming project
|
|
605
|
+
`Failed to bundle FileSource "${filename}": ${message}. If the error mentions @frontmcp/ui or @frontmcp/uipack, ensure both packages are installed in the consuming project.`,
|
|
606
|
+
{ cause: err }
|
|
590
607
|
);
|
|
591
608
|
}
|
|
592
609
|
}
|
|
@@ -648,6 +665,37 @@ function resolveImportSource(source) {
|
|
|
648
665
|
peerDependencies: []
|
|
649
666
|
};
|
|
650
667
|
}
|
|
668
|
+
var BUILD_DIR_SEGMENTS = /* @__PURE__ */ new Set(["dist", "build", "out"]);
|
|
669
|
+
function missingWidgetError(filePath, cause) {
|
|
670
|
+
const sourceWidget = findSourceWidget(filePath);
|
|
671
|
+
const error = new Error(
|
|
672
|
+
`FileSource widget not found (ENOENT): no file at "${filePath}". The widget source is read from this path when the tool is called, so it has to exist where the compiled tool runs \u2014 a path built from \`__dirname\` or \`import.meta.url\` points into the build output once the tool is compiled. tsc does not copy \`*.widget.tsx\` / \`*.widget.jsx\` files into its output; \`frontmcp build\` does, and any other build needs a step that copies them next to the compiled tool.` + (sourceWidget ? ` The source widget exists at "${sourceWidget}" \u2014 copy it to "${filePath}", or build with \`frontmcp build\`.` : ""),
|
|
673
|
+
{ cause }
|
|
674
|
+
);
|
|
675
|
+
error.code = "ENOENT";
|
|
676
|
+
error.path = filePath;
|
|
677
|
+
return error;
|
|
678
|
+
}
|
|
679
|
+
function findSourceWidget(filePath) {
|
|
680
|
+
const path = __require("path");
|
|
681
|
+
const fs = __require("fs");
|
|
682
|
+
const parts = filePath.split(path.sep);
|
|
683
|
+
for (let i = parts.length - 2; i >= 0; i--) {
|
|
684
|
+
if (!BUILD_DIR_SEGMENTS.has(parts[i])) continue;
|
|
685
|
+
const candidates = [[...parts.slice(0, i), "src", ...parts.slice(i + 1)]];
|
|
686
|
+
if (parts.length - i > 2) candidates.push([...parts.slice(0, i), "src", ...parts.slice(i + 2)]);
|
|
687
|
+
candidates.push([...parts.slice(0, i), ...parts.slice(i + 1)]);
|
|
688
|
+
for (const candidate of candidates) {
|
|
689
|
+
const candidatePath = candidate.join(path.sep);
|
|
690
|
+
try {
|
|
691
|
+
if (fs.existsSync(candidatePath)) return candidatePath;
|
|
692
|
+
} catch {
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return void 0;
|
|
696
|
+
}
|
|
697
|
+
return void 0;
|
|
698
|
+
}
|
|
651
699
|
function resolveFileSource(source, options = {}) {
|
|
652
700
|
const path = __require("path");
|
|
653
701
|
const ext = path.extname(source.file).toLowerCase();
|
|
@@ -662,9 +710,11 @@ function resolveFileSource(source, options = {}) {
|
|
|
662
710
|
const isNotFound = err?.code === "ENOENT";
|
|
663
711
|
if (isNotFound && wasRelative) {
|
|
664
712
|
throw new Error(
|
|
665
|
-
`FileSource widget "${source.file}" not found at "${filePath}". Relative paths are resolved against process.cwd() ("${process.cwd()}"), not the tool file's directory. To anchor the path to the tool file, pass an absolute path \u2014 e.g. \`{ file: fileURLToPath(new URL('./widget.tsx', import.meta.url)) }\` from \`node:url\` (see issue #444)
|
|
713
|
+
`FileSource widget "${source.file}" not found at "${filePath}". Relative paths are resolved against process.cwd() ("${process.cwd()}"), not the tool file's directory. To anchor the path to the tool file, pass an absolute path \u2014 e.g. \`{ file: fileURLToPath(new URL('./widget.tsx', import.meta.url)) }\` from \`node:url\` (see issue #444).`,
|
|
714
|
+
{ cause: err }
|
|
666
715
|
);
|
|
667
716
|
}
|
|
717
|
+
if (isNotFound) throw missingWidgetError(filePath, err);
|
|
668
718
|
throw err;
|
|
669
719
|
}
|
|
670
720
|
const componentName = source.exportName || extractDefaultExportName(rawSource) || "Component";
|
|
@@ -882,6 +932,23 @@ function __applySizingCss(sizing) {
|
|
|
882
932
|
}
|
|
883
933
|
}
|
|
884
934
|
|
|
935
|
+
function __measureHeight() {
|
|
936
|
+
// <html> at fit-content includes body margins and collapsed child margins;
|
|
937
|
+
// content overflowing a fixed-height body is added; a px max-height clamps.
|
|
938
|
+
var de = document.documentElement;
|
|
939
|
+
var body = document.body;
|
|
940
|
+
if (!de || !body) return 0;
|
|
941
|
+
var prev = de.style.height;
|
|
942
|
+
de.style.height = 'fit-content';
|
|
943
|
+
var dr = de.getBoundingClientRect();
|
|
944
|
+
var br = body.getBoundingClientRect();
|
|
945
|
+
var h = Math.max(dr.height, br.top + body.scrollHeight + Math.max(0, dr.bottom - br.bottom) - dr.top);
|
|
946
|
+
de.style.height = prev;
|
|
947
|
+
var maxHeight = getComputedStyle(de).maxHeight;
|
|
948
|
+
var mx = maxHeight && maxHeight.slice(-2) === 'px' ? parseFloat(maxHeight) : NaN;
|
|
949
|
+
return Math.ceil(isNaN(mx) ? h : Math.min(h, mx));
|
|
950
|
+
}
|
|
951
|
+
|
|
885
952
|
function __initAutoResize() {
|
|
886
953
|
if (typeof window === 'undefined') return;
|
|
887
954
|
// Idempotent: a re-injected IIFE must not stack observers.
|
|
@@ -904,23 +971,30 @@ function __initAutoResize() {
|
|
|
904
971
|
if (typeof ResizeObserver === 'undefined') return;
|
|
905
972
|
|
|
906
973
|
function startObserving() {
|
|
907
|
-
|
|
908
|
-
|
|
974
|
+
if (!document.body) return;
|
|
975
|
+
var targets = [document.documentElement, document.body];
|
|
976
|
+
var root = document.getElementById('root');
|
|
977
|
+
if (root) targets.push(root);
|
|
909
978
|
|
|
910
979
|
var rafId = null;
|
|
911
980
|
var lastReported = -1;
|
|
912
981
|
function report() {
|
|
913
982
|
rafId = null;
|
|
914
983
|
try {
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
984
|
+
// Before the handshake an ext-apps host can't receive the report; the
|
|
985
|
+
// bridge:ready listener below sends it once the bridge is up.
|
|
986
|
+
if (window.FrontMcpBridge && window.FrontMcpBridge.initialized === false) return;
|
|
987
|
+
var height = __measureHeight();
|
|
988
|
+
var width = Math.ceil(document.documentElement.getBoundingClientRect().width);
|
|
918
989
|
if (height === lastReported || height <= 0) return;
|
|
919
990
|
lastReported = height;
|
|
920
991
|
var payload = { height: height, width: width };
|
|
921
992
|
if (sizing.aspectRatio != null) payload.aspectRatio = sizing.aspectRatio;
|
|
922
993
|
if (window.FrontMcpBridge && typeof window.FrontMcpBridge.setSize === 'function') {
|
|
923
|
-
window.FrontMcpBridge.setSize(payload).catch(function() {
|
|
994
|
+
window.FrontMcpBridge.setSize(payload).catch(function() {
|
|
995
|
+
// Forget a rejected report so the same height is sent again.
|
|
996
|
+
if (lastReported === height) lastReported = -1;
|
|
997
|
+
});
|
|
924
998
|
}
|
|
925
999
|
window.dispatchEvent(new CustomEvent('widget:resize', { detail: payload }));
|
|
926
1000
|
} catch (e) {}
|
|
@@ -941,9 +1015,17 @@ function __initAutoResize() {
|
|
|
941
1015
|
window.__mcpResizeObserver.disconnect();
|
|
942
1016
|
}
|
|
943
1017
|
var ro = new ResizeObserver(function() { schedule(); });
|
|
944
|
-
ro.observe(
|
|
1018
|
+
for (var i = 0; i < targets.length; i++) ro.observe(targets[i]);
|
|
945
1019
|
window.__mcpResizeObserver = ro;
|
|
946
1020
|
} catch (e) {}
|
|
1021
|
+
// Report once the bridge is ready (the report scheduled below is skipped
|
|
1022
|
+
// while the handshake is still pending).
|
|
1023
|
+
var readySent = false;
|
|
1024
|
+
window.addEventListener('bridge:ready', function() {
|
|
1025
|
+
if (readySent) return;
|
|
1026
|
+
readySent = true;
|
|
1027
|
+
schedule();
|
|
1028
|
+
});
|
|
947
1029
|
// Report once on init so the host gets an initial measurement.
|
|
948
1030
|
schedule();
|
|
949
1031
|
}
|
|
@@ -991,12 +1073,57 @@ function detectViewport() {
|
|
|
991
1073
|
return undefined;
|
|
992
1074
|
}
|
|
993
1075
|
|
|
1076
|
+
function __applyHostTheme(theme) {
|
|
1077
|
+
// Follow a host-supplied theme: <meta name="color-scheme"> sets the frame's
|
|
1078
|
+
// canvas and form controls (a widget's own :root color-scheme still wins),
|
|
1079
|
+
// <html data-theme> is a styling hook. The OS fallback is never written.
|
|
1080
|
+
if (theme !== 'light' && theme !== 'dark') return;
|
|
1081
|
+
if (typeof document === 'undefined' || !document.documentElement) return;
|
|
1082
|
+
try {
|
|
1083
|
+
var meta = document.querySelector('meta[name="color-scheme"]');
|
|
1084
|
+
var head = document.head || document.getElementsByTagName('head')[0];
|
|
1085
|
+
if (!meta && head) {
|
|
1086
|
+
meta = document.createElement('meta');
|
|
1087
|
+
meta.setAttribute('name', 'color-scheme');
|
|
1088
|
+
head.appendChild(meta);
|
|
1089
|
+
}
|
|
1090
|
+
if (meta) meta.setAttribute('content', theme);
|
|
1091
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
1092
|
+
} catch (e) {}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
function extractToolOutput(params) {
|
|
1096
|
+
// A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
|
|
1097
|
+
if (!params) return undefined;
|
|
1098
|
+
if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
|
|
1099
|
+
var content = params.content;
|
|
1100
|
+
if (Array.isArray(content)) {
|
|
1101
|
+
for (var i = 0; i < content.length; i++) {
|
|
1102
|
+
var item = content[i];
|
|
1103
|
+
if (item && item.type === 'text' && typeof item.text === 'string') {
|
|
1104
|
+
try { return JSON.parse(item.text); } catch (e) { return item.text; }
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
return content;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
994
1111
|
function readInjectedData() {
|
|
995
1112
|
var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
|
|
996
1113
|
if (typeof window !== 'undefined') {
|
|
997
1114
|
if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
|
|
998
1115
|
if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
|
|
999
1116
|
if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
|
|
1117
|
+
// The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
|
|
1118
|
+
// (toolOutput is the tool's structuredContent).
|
|
1119
|
+
var oa = window.openai;
|
|
1120
|
+
if (oa && typeof oa === 'object') {
|
|
1121
|
+
if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
|
|
1122
|
+
if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
|
|
1123
|
+
if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
|
|
1124
|
+
if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1000
1127
|
}
|
|
1001
1128
|
return data;
|
|
1002
1129
|
}
|
|
@@ -1045,7 +1172,24 @@ var OpenAIAdapter = {
|
|
|
1045
1172
|
if (sdk.displayMode) {
|
|
1046
1173
|
context.hostContext.displayMode = sdk.displayMode;
|
|
1047
1174
|
}
|
|
1048
|
-
//
|
|
1175
|
+
// The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
|
|
1176
|
+
window.addEventListener('openai:set_globals', function(event) {
|
|
1177
|
+
var globals = (event && event.detail && event.detail.globals) || window.openai || {};
|
|
1178
|
+
if (globals.toolInput !== undefined && globals.toolInput !== null) {
|
|
1179
|
+
context.toolInput = globals.toolInput;
|
|
1180
|
+
window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
|
|
1181
|
+
}
|
|
1182
|
+
if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
|
|
1183
|
+
context.structuredContent = globals.toolOutput;
|
|
1184
|
+
context.notifyToolResult(globals.toolOutput);
|
|
1185
|
+
}
|
|
1186
|
+
if (globals.theme && globals.theme !== context.hostContext.theme) {
|
|
1187
|
+
context.notifyContextChange({ theme: globals.theme });
|
|
1188
|
+
}
|
|
1189
|
+
if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
|
|
1190
|
+
context.notifyContextChange({ displayMode: globals.displayMode });
|
|
1191
|
+
}
|
|
1192
|
+
});
|
|
1049
1193
|
return Promise.resolve();
|
|
1050
1194
|
},
|
|
1051
1195
|
callTool: function(context, name, args) {
|
|
@@ -1097,6 +1241,8 @@ var ExtAppsAdapter = {
|
|
|
1097
1241
|
pendingRequests: {},
|
|
1098
1242
|
requestId: 0,
|
|
1099
1243
|
hostCapabilities: {},
|
|
1244
|
+
handshakeSettled: false,
|
|
1245
|
+
pendingSize: null,
|
|
1100
1246
|
canHandle: function() {
|
|
1101
1247
|
if (typeof window === 'undefined') return false;
|
|
1102
1248
|
if (window.parent === window) return false;
|
|
@@ -1190,9 +1336,10 @@ var ExtAppsAdapter = {
|
|
|
1190
1336
|
window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
|
|
1191
1337
|
break;
|
|
1192
1338
|
case 'ui/notifications/tool-result':
|
|
1193
|
-
|
|
1339
|
+
var resultOutput = extractToolOutput(params);
|
|
1340
|
+
context.toolOutput = resultOutput;
|
|
1194
1341
|
context.structuredContent = params.structuredContent;
|
|
1195
|
-
context.notifyToolResult(
|
|
1342
|
+
context.notifyToolResult(resultOutput);
|
|
1196
1343
|
window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
|
|
1197
1344
|
break;
|
|
1198
1345
|
case 'ui/notifications/host-context-changed':
|
|
@@ -1246,6 +1393,15 @@ var ExtAppsAdapter = {
|
|
|
1246
1393
|
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
1247
1394
|
});
|
|
1248
1395
|
},
|
|
1396
|
+
sendNotification: function(method, params) {
|
|
1397
|
+
// JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
|
|
1398
|
+
if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
1399
|
+
return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
|
|
1400
|
+
}
|
|
1401
|
+
var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
|
|
1402
|
+
window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
|
|
1403
|
+
return Promise.resolve();
|
|
1404
|
+
},
|
|
1249
1405
|
sendHandshake: function(context) {
|
|
1250
1406
|
// Send ui/initialize using '*' as target origin since TOFU hasn't
|
|
1251
1407
|
// been established yet. The response from the host will establish
|
|
@@ -1262,6 +1418,7 @@ var ExtAppsAdapter = {
|
|
|
1262
1418
|
var timeout = setTimeout(function() {
|
|
1263
1419
|
delete self.pendingRequests[id];
|
|
1264
1420
|
// Handshake timeout is non-fatal \u2014 notifications may still arrive
|
|
1421
|
+
self.settleHandshake();
|
|
1265
1422
|
resolve();
|
|
1266
1423
|
}, 10000);
|
|
1267
1424
|
|
|
@@ -1275,7 +1432,9 @@ var ExtAppsAdapter = {
|
|
|
1275
1432
|
supportsDisplayModes: true
|
|
1276
1433
|
});
|
|
1277
1434
|
if (result.hostContext) {
|
|
1278
|
-
|
|
1435
|
+
// Notify (not a silent merge) so onContextChange listeners and the
|
|
1436
|
+
// host theme see the handshake's context.
|
|
1437
|
+
context.notifyContextChange(result.hostContext);
|
|
1279
1438
|
}
|
|
1280
1439
|
// Send ui/notifications/initialized to tell the host the view is ready.
|
|
1281
1440
|
// Per MCP Apps spec, the host waits for this before sending tool-result.
|
|
@@ -1285,9 +1444,10 @@ var ExtAppsAdapter = {
|
|
|
1285
1444
|
method: 'ui/notifications/initialized',
|
|
1286
1445
|
params: {}
|
|
1287
1446
|
}, targetOrigin);
|
|
1447
|
+
self.settleHandshake();
|
|
1288
1448
|
resolve();
|
|
1289
1449
|
},
|
|
1290
|
-
reject: function(err) { resolve(); }, // Non-fatal
|
|
1450
|
+
reject: function(err) { self.settleHandshake(); resolve(); }, // Non-fatal
|
|
1291
1451
|
timeout: timeout
|
|
1292
1452
|
};
|
|
1293
1453
|
|
|
@@ -1301,6 +1461,18 @@ var ExtAppsAdapter = {
|
|
|
1301
1461
|
performHandshake: function(context) {
|
|
1302
1462
|
return this.sendHandshake(context);
|
|
1303
1463
|
},
|
|
1464
|
+
settleHandshake: function() {
|
|
1465
|
+
// Deliver the latest size requested while no host origin was trusted.
|
|
1466
|
+
this.handshakeSettled = true;
|
|
1467
|
+
var pending = this.pendingSize;
|
|
1468
|
+
if (!pending) return;
|
|
1469
|
+
this.pendingSize = null;
|
|
1470
|
+
this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
|
|
1471
|
+
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
|
|
1472
|
+
}, function(err) {
|
|
1473
|
+
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
|
|
1474
|
+
});
|
|
1475
|
+
},
|
|
1304
1476
|
callTool: function(context, name, args) {
|
|
1305
1477
|
if (!this.hostCapabilities.serverTools && !this.hostCapabilities.serverToolProxy) {
|
|
1306
1478
|
return Promise.reject(new Error('Server tool proxy not supported'));
|
|
@@ -1322,13 +1494,22 @@ var ExtAppsAdapter = {
|
|
|
1322
1494
|
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
1323
1495
|
},
|
|
1324
1496
|
setSize: function(context, size) {
|
|
1325
|
-
//
|
|
1326
|
-
//
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1497
|
+
// Standard ext-apps sizing: the view tells the host its dimensions with the
|
|
1498
|
+
// ui/notifications/size-changed notification ({ width?, height? } in px).
|
|
1499
|
+
var params = {};
|
|
1500
|
+
if (size && typeof size.width === 'number') params.width = size.width;
|
|
1501
|
+
if (size && typeof size.height === 'number') params.height = size.height;
|
|
1502
|
+
if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
1503
|
+
// No host origin yet: keep only the latest size and send it when the
|
|
1504
|
+
// handshake settles; every caller's promise follows that one request.
|
|
1505
|
+
var self = this;
|
|
1506
|
+
if (!this.pendingSize) this.pendingSize = { params: params, waiters: [] };
|
|
1507
|
+
this.pendingSize.params = params;
|
|
1508
|
+
return new Promise(function(resolve, reject) {
|
|
1509
|
+
self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
return this.sendNotification('ui/notifications/size-changed', params);
|
|
1332
1513
|
},
|
|
1333
1514
|
requestClose: function(context) {
|
|
1334
1515
|
return this.sendRequest('ui/close', {});
|
|
@@ -1574,6 +1755,7 @@ function FrontMcpBridge() {
|
|
|
1574
1755
|
toolResultListeners: [],
|
|
1575
1756
|
notifyContextChange: function(changes) {
|
|
1576
1757
|
Object.assign(this.hostContext, changes);
|
|
1758
|
+
if (changes && changes.theme) __applyHostTheme(changes.theme);
|
|
1577
1759
|
for (var i = 0; i < this.contextListeners.length; i++) {
|
|
1578
1760
|
try { this.contextListeners[i](changes); } catch(e) {}
|
|
1579
1761
|
}
|
|
@@ -1750,7 +1932,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
|
1750
1932
|
|
|
1751
1933
|
// Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
|
|
1752
1934
|
// Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
|
|
1753
|
-
// ext-apps sends ui/
|
|
1935
|
+
// ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
|
|
1754
1936
|
FrontMcpBridge.prototype.setSize = function(size) {
|
|
1755
1937
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1756
1938
|
if (!this._adapter.setSize) return Promise.resolve();
|
|
@@ -1852,7 +2034,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
|
|
|
1852
2034
|
target.classList.add('opacity-50', 'cursor-not-allowed');
|
|
1853
2035
|
|
|
1854
2036
|
// Add spinner for buttons
|
|
1855
|
-
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>';
|
|
2037
|
+
var spinner = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 inline" style="width:1em;height:1em;vertical-align:-0.125em" 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>';
|
|
1856
2038
|
if (target.tagName === 'BUTTON') {
|
|
1857
2039
|
target.innerHTML = spinner + 'Loading...';
|
|
1858
2040
|
}
|
|
@@ -1933,7 +2115,22 @@ function minifyJS(code) {
|
|
|
1933
2115
|
if (code.length > MAX_MINIFY_CODE_LENGTH) {
|
|
1934
2116
|
return code;
|
|
1935
2117
|
}
|
|
1936
|
-
|
|
2118
|
+
const out = [];
|
|
2119
|
+
let inBlockComment = false;
|
|
2120
|
+
for (const raw of code.split("\n")) {
|
|
2121
|
+
const line = raw.trim();
|
|
2122
|
+
if (inBlockComment) {
|
|
2123
|
+
if (line.includes("*/")) inBlockComment = false;
|
|
2124
|
+
continue;
|
|
2125
|
+
}
|
|
2126
|
+
if (line === "" || line.startsWith("//")) continue;
|
|
2127
|
+
if (line.startsWith("/*")) {
|
|
2128
|
+
if (!line.includes("*/")) inBlockComment = true;
|
|
2129
|
+
continue;
|
|
2130
|
+
}
|
|
2131
|
+
out.push(line);
|
|
2132
|
+
}
|
|
2133
|
+
return out.join("\n");
|
|
1937
2134
|
}
|
|
1938
2135
|
function generatePlatformBundle(platform, options = {}) {
|
|
1939
2136
|
const platformAdapters = {
|