@frontmcp/uipack 1.8.6 → 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 +197 -24
- 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 +81 -15
- package/component/index.js +81 -15
- package/esm/adapters/index.mjs +197 -24
- package/esm/bridge-runtime/index.mjs +81 -15
- package/esm/component/index.mjs +81 -15
- package/esm/index.mjs +197 -24
- package/esm/package.json +2 -2
- package/esm/shell/index.mjs +81 -15
- package/index.js +197 -24
- package/package.json +2 -2
- package/shell/index.js +81 -15
- package/types/ui-config.d.ts +1 -1
package/component/index.js
CHANGED
|
@@ -1121,12 +1121,38 @@ function __applyHostTheme(theme) {
|
|
|
1121
1121
|
} catch (e) {}
|
|
1122
1122
|
}
|
|
1123
1123
|
|
|
1124
|
+
function extractToolOutput(params) {
|
|
1125
|
+
// A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
|
|
1126
|
+
if (!params) return undefined;
|
|
1127
|
+
if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
|
|
1128
|
+
var content = params.content;
|
|
1129
|
+
if (Array.isArray(content)) {
|
|
1130
|
+
for (var i = 0; i < content.length; i++) {
|
|
1131
|
+
var item = content[i];
|
|
1132
|
+
if (item && item.type === 'text' && typeof item.text === 'string') {
|
|
1133
|
+
try { return JSON.parse(item.text); } catch (e) { return item.text; }
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
return content;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1124
1140
|
function readInjectedData() {
|
|
1125
1141
|
var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
|
|
1126
1142
|
if (typeof window !== 'undefined') {
|
|
1127
1143
|
if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
|
|
1128
1144
|
if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
|
|
1129
1145
|
if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
|
|
1146
|
+
// The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
|
|
1147
|
+
// (toolOutput is the tool's structuredContent).
|
|
1148
|
+
var oa = window.openai;
|
|
1149
|
+
if (oa && typeof oa === 'object') {
|
|
1150
|
+
if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
|
|
1151
|
+
if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
|
|
1152
|
+
if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
|
|
1153
|
+
if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1130
1156
|
}
|
|
1131
1157
|
return data;
|
|
1132
1158
|
}
|
|
@@ -1175,7 +1201,24 @@ var OpenAIAdapter = {
|
|
|
1175
1201
|
if (sdk.displayMode) {
|
|
1176
1202
|
context.hostContext.displayMode = sdk.displayMode;
|
|
1177
1203
|
}
|
|
1178
|
-
//
|
|
1204
|
+
// The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
|
|
1205
|
+
window.addEventListener('openai:set_globals', function(event) {
|
|
1206
|
+
var globals = (event && event.detail && event.detail.globals) || window.openai || {};
|
|
1207
|
+
if (globals.toolInput !== undefined && globals.toolInput !== null) {
|
|
1208
|
+
context.toolInput = globals.toolInput;
|
|
1209
|
+
window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
|
|
1210
|
+
}
|
|
1211
|
+
if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
|
|
1212
|
+
context.structuredContent = globals.toolOutput;
|
|
1213
|
+
context.notifyToolResult(globals.toolOutput);
|
|
1214
|
+
}
|
|
1215
|
+
if (globals.theme && globals.theme !== context.hostContext.theme) {
|
|
1216
|
+
context.notifyContextChange({ theme: globals.theme });
|
|
1217
|
+
}
|
|
1218
|
+
if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
|
|
1219
|
+
context.notifyContextChange({ displayMode: globals.displayMode });
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1179
1222
|
return Promise.resolve();
|
|
1180
1223
|
},
|
|
1181
1224
|
callTool: function(context, name, args) {
|
|
@@ -1322,9 +1365,10 @@ var ExtAppsAdapter = {
|
|
|
1322
1365
|
window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
|
|
1323
1366
|
break;
|
|
1324
1367
|
case 'ui/notifications/tool-result':
|
|
1325
|
-
|
|
1368
|
+
var resultOutput = extractToolOutput(params);
|
|
1369
|
+
context.toolOutput = resultOutput;
|
|
1326
1370
|
context.structuredContent = params.structuredContent;
|
|
1327
|
-
context.notifyToolResult(
|
|
1371
|
+
context.notifyToolResult(resultOutput);
|
|
1328
1372
|
window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
|
|
1329
1373
|
break;
|
|
1330
1374
|
case 'ui/notifications/host-context-changed':
|
|
@@ -1378,6 +1422,15 @@ var ExtAppsAdapter = {
|
|
|
1378
1422
|
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
1379
1423
|
});
|
|
1380
1424
|
},
|
|
1425
|
+
sendNotification: function(method, params) {
|
|
1426
|
+
// JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
|
|
1427
|
+
if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
1428
|
+
return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
|
|
1429
|
+
}
|
|
1430
|
+
var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
|
|
1431
|
+
window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
|
|
1432
|
+
return Promise.resolve();
|
|
1433
|
+
},
|
|
1381
1434
|
sendHandshake: function(context) {
|
|
1382
1435
|
// Send ui/initialize using '*' as target origin since TOFU hasn't
|
|
1383
1436
|
// been established yet. The response from the host will establish
|
|
@@ -1443,7 +1496,7 @@ var ExtAppsAdapter = {
|
|
|
1443
1496
|
var pending = this.pendingSize;
|
|
1444
1497
|
if (!pending) return;
|
|
1445
1498
|
this.pendingSize = null;
|
|
1446
|
-
this.
|
|
1499
|
+
this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
|
|
1447
1500
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
|
|
1448
1501
|
}, function(err) {
|
|
1449
1502
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
|
|
@@ -1470,13 +1523,11 @@ var ExtAppsAdapter = {
|
|
|
1470
1523
|
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
1471
1524
|
},
|
|
1472
1525
|
setSize: function(context, size) {
|
|
1473
|
-
//
|
|
1474
|
-
//
|
|
1475
|
-
var params = {
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
aspectRatio: size && size.aspectRatio
|
|
1479
|
-
};
|
|
1526
|
+
// Standard ext-apps sizing: the view tells the host its dimensions with the
|
|
1527
|
+
// ui/notifications/size-changed notification ({ width?, height? } in px).
|
|
1528
|
+
var params = {};
|
|
1529
|
+
if (size && typeof size.width === 'number') params.width = size.width;
|
|
1530
|
+
if (size && typeof size.height === 'number') params.height = size.height;
|
|
1480
1531
|
if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
1481
1532
|
// No host origin yet: keep only the latest size and send it when the
|
|
1482
1533
|
// handshake settles; every caller's promise follows that one request.
|
|
@@ -1487,7 +1538,7 @@ var ExtAppsAdapter = {
|
|
|
1487
1538
|
self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
|
|
1488
1539
|
});
|
|
1489
1540
|
}
|
|
1490
|
-
return this.
|
|
1541
|
+
return this.sendNotification('ui/notifications/size-changed', params);
|
|
1491
1542
|
},
|
|
1492
1543
|
requestClose: function(context) {
|
|
1493
1544
|
return this.sendRequest('ui/close', {});
|
|
@@ -1910,7 +1961,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
|
1910
1961
|
|
|
1911
1962
|
// Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
|
|
1912
1963
|
// Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
|
|
1913
|
-
// ext-apps sends ui/
|
|
1964
|
+
// ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
|
|
1914
1965
|
FrontMcpBridge.prototype.setSize = function(size) {
|
|
1915
1966
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1916
1967
|
if (!this._adapter.setSize) return Promise.resolve();
|
|
@@ -2012,7 +2063,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
|
|
|
2012
2063
|
target.classList.add('opacity-50', 'cursor-not-allowed');
|
|
2013
2064
|
|
|
2014
2065
|
// Add spinner for buttons
|
|
2015
|
-
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>';
|
|
2066
|
+
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>';
|
|
2016
2067
|
if (target.tagName === 'BUTTON') {
|
|
2017
2068
|
target.innerHTML = spinner + 'Loading...';
|
|
2018
2069
|
}
|
|
@@ -2093,7 +2144,22 @@ function minifyJS(code) {
|
|
|
2093
2144
|
if (code.length > MAX_MINIFY_CODE_LENGTH) {
|
|
2094
2145
|
return code;
|
|
2095
2146
|
}
|
|
2096
|
-
|
|
2147
|
+
const out = [];
|
|
2148
|
+
let inBlockComment = false;
|
|
2149
|
+
for (const raw of code.split("\n")) {
|
|
2150
|
+
const line = raw.trim();
|
|
2151
|
+
if (inBlockComment) {
|
|
2152
|
+
if (line.includes("*/")) inBlockComment = false;
|
|
2153
|
+
continue;
|
|
2154
|
+
}
|
|
2155
|
+
if (line === "" || line.startsWith("//")) continue;
|
|
2156
|
+
if (line.startsWith("/*")) {
|
|
2157
|
+
if (!line.includes("*/")) inBlockComment = true;
|
|
2158
|
+
continue;
|
|
2159
|
+
}
|
|
2160
|
+
out.push(line);
|
|
2161
|
+
}
|
|
2162
|
+
return out.join("\n");
|
|
2097
2163
|
}
|
|
2098
2164
|
function generatePlatformBundle(platform, options = {}) {
|
|
2099
2165
|
const platformAdapters = {
|
package/esm/adapters/index.mjs
CHANGED
|
@@ -797,12 +797,38 @@ function __applyHostTheme(theme) {
|
|
|
797
797
|
} catch (e) {}
|
|
798
798
|
}
|
|
799
799
|
|
|
800
|
+
function extractToolOutput(params) {
|
|
801
|
+
// A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
|
|
802
|
+
if (!params) return undefined;
|
|
803
|
+
if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
|
|
804
|
+
var content = params.content;
|
|
805
|
+
if (Array.isArray(content)) {
|
|
806
|
+
for (var i = 0; i < content.length; i++) {
|
|
807
|
+
var item = content[i];
|
|
808
|
+
if (item && item.type === 'text' && typeof item.text === 'string') {
|
|
809
|
+
try { return JSON.parse(item.text); } catch (e) { return item.text; }
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
return content;
|
|
814
|
+
}
|
|
815
|
+
|
|
800
816
|
function readInjectedData() {
|
|
801
817
|
var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
|
|
802
818
|
if (typeof window !== 'undefined') {
|
|
803
819
|
if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
|
|
804
820
|
if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
|
|
805
821
|
if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
|
|
822
|
+
// The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
|
|
823
|
+
// (toolOutput is the tool's structuredContent).
|
|
824
|
+
var oa = window.openai;
|
|
825
|
+
if (oa && typeof oa === 'object') {
|
|
826
|
+
if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
|
|
827
|
+
if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
|
|
828
|
+
if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
|
|
829
|
+
if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
806
832
|
}
|
|
807
833
|
return data;
|
|
808
834
|
}
|
|
@@ -851,7 +877,24 @@ var OpenAIAdapter = {
|
|
|
851
877
|
if (sdk.displayMode) {
|
|
852
878
|
context.hostContext.displayMode = sdk.displayMode;
|
|
853
879
|
}
|
|
854
|
-
//
|
|
880
|
+
// The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
|
|
881
|
+
window.addEventListener('openai:set_globals', function(event) {
|
|
882
|
+
var globals = (event && event.detail && event.detail.globals) || window.openai || {};
|
|
883
|
+
if (globals.toolInput !== undefined && globals.toolInput !== null) {
|
|
884
|
+
context.toolInput = globals.toolInput;
|
|
885
|
+
window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
|
|
886
|
+
}
|
|
887
|
+
if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
|
|
888
|
+
context.structuredContent = globals.toolOutput;
|
|
889
|
+
context.notifyToolResult(globals.toolOutput);
|
|
890
|
+
}
|
|
891
|
+
if (globals.theme && globals.theme !== context.hostContext.theme) {
|
|
892
|
+
context.notifyContextChange({ theme: globals.theme });
|
|
893
|
+
}
|
|
894
|
+
if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
|
|
895
|
+
context.notifyContextChange({ displayMode: globals.displayMode });
|
|
896
|
+
}
|
|
897
|
+
});
|
|
855
898
|
return Promise.resolve();
|
|
856
899
|
},
|
|
857
900
|
callTool: function(context, name, args) {
|
|
@@ -998,9 +1041,10 @@ var ExtAppsAdapter = {
|
|
|
998
1041
|
window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
|
|
999
1042
|
break;
|
|
1000
1043
|
case 'ui/notifications/tool-result':
|
|
1001
|
-
|
|
1044
|
+
var resultOutput = extractToolOutput(params);
|
|
1045
|
+
context.toolOutput = resultOutput;
|
|
1002
1046
|
context.structuredContent = params.structuredContent;
|
|
1003
|
-
context.notifyToolResult(
|
|
1047
|
+
context.notifyToolResult(resultOutput);
|
|
1004
1048
|
window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
|
|
1005
1049
|
break;
|
|
1006
1050
|
case 'ui/notifications/host-context-changed':
|
|
@@ -1054,6 +1098,15 @@ var ExtAppsAdapter = {
|
|
|
1054
1098
|
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
1055
1099
|
});
|
|
1056
1100
|
},
|
|
1101
|
+
sendNotification: function(method, params) {
|
|
1102
|
+
// JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
|
|
1103
|
+
if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
1104
|
+
return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
|
|
1105
|
+
}
|
|
1106
|
+
var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
|
|
1107
|
+
window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
|
|
1108
|
+
return Promise.resolve();
|
|
1109
|
+
},
|
|
1057
1110
|
sendHandshake: function(context) {
|
|
1058
1111
|
// Send ui/initialize using '*' as target origin since TOFU hasn't
|
|
1059
1112
|
// been established yet. The response from the host will establish
|
|
@@ -1119,7 +1172,7 @@ var ExtAppsAdapter = {
|
|
|
1119
1172
|
var pending = this.pendingSize;
|
|
1120
1173
|
if (!pending) return;
|
|
1121
1174
|
this.pendingSize = null;
|
|
1122
|
-
this.
|
|
1175
|
+
this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
|
|
1123
1176
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
|
|
1124
1177
|
}, function(err) {
|
|
1125
1178
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
|
|
@@ -1146,13 +1199,11 @@ var ExtAppsAdapter = {
|
|
|
1146
1199
|
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
1147
1200
|
},
|
|
1148
1201
|
setSize: function(context, size) {
|
|
1149
|
-
//
|
|
1150
|
-
//
|
|
1151
|
-
var params = {
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
aspectRatio: size && size.aspectRatio
|
|
1155
|
-
};
|
|
1202
|
+
// Standard ext-apps sizing: the view tells the host its dimensions with the
|
|
1203
|
+
// ui/notifications/size-changed notification ({ width?, height? } in px).
|
|
1204
|
+
var params = {};
|
|
1205
|
+
if (size && typeof size.width === 'number') params.width = size.width;
|
|
1206
|
+
if (size && typeof size.height === 'number') params.height = size.height;
|
|
1156
1207
|
if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
1157
1208
|
// No host origin yet: keep only the latest size and send it when the
|
|
1158
1209
|
// handshake settles; every caller's promise follows that one request.
|
|
@@ -1163,7 +1214,7 @@ var ExtAppsAdapter = {
|
|
|
1163
1214
|
self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
|
|
1164
1215
|
});
|
|
1165
1216
|
}
|
|
1166
|
-
return this.
|
|
1217
|
+
return this.sendNotification('ui/notifications/size-changed', params);
|
|
1167
1218
|
},
|
|
1168
1219
|
requestClose: function(context) {
|
|
1169
1220
|
return this.sendRequest('ui/close', {});
|
|
@@ -1586,7 +1637,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
|
1586
1637
|
|
|
1587
1638
|
// Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
|
|
1588
1639
|
// Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
|
|
1589
|
-
// ext-apps sends ui/
|
|
1640
|
+
// ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
|
|
1590
1641
|
FrontMcpBridge.prototype.setSize = function(size) {
|
|
1591
1642
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1592
1643
|
if (!this._adapter.setSize) return Promise.resolve();
|
|
@@ -1688,7 +1739,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
|
|
|
1688
1739
|
target.classList.add('opacity-50', 'cursor-not-allowed');
|
|
1689
1740
|
|
|
1690
1741
|
// Add spinner for buttons
|
|
1691
|
-
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>';
|
|
1742
|
+
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>';
|
|
1692
1743
|
if (target.tagName === 'BUTTON') {
|
|
1693
1744
|
target.innerHTML = spinner + 'Loading...';
|
|
1694
1745
|
}
|
|
@@ -1769,7 +1820,22 @@ function minifyJS(code) {
|
|
|
1769
1820
|
if (code.length > MAX_MINIFY_CODE_LENGTH) {
|
|
1770
1821
|
return code;
|
|
1771
1822
|
}
|
|
1772
|
-
|
|
1823
|
+
const out = [];
|
|
1824
|
+
let inBlockComment = false;
|
|
1825
|
+
for (const raw of code.split("\n")) {
|
|
1826
|
+
const line = raw.trim();
|
|
1827
|
+
if (inBlockComment) {
|
|
1828
|
+
if (line.includes("*/")) inBlockComment = false;
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1831
|
+
if (line === "" || line.startsWith("//")) continue;
|
|
1832
|
+
if (line.startsWith("/*")) {
|
|
1833
|
+
if (!line.includes("*/")) inBlockComment = true;
|
|
1834
|
+
continue;
|
|
1835
|
+
}
|
|
1836
|
+
out.push(line);
|
|
1837
|
+
}
|
|
1838
|
+
return out.join("\n");
|
|
1773
1839
|
}
|
|
1774
1840
|
function generatePlatformBundle(platform, options = {}) {
|
|
1775
1841
|
const platformAdapters = {
|
|
@@ -2831,6 +2897,86 @@ function wrapDetectedContent(value) {
|
|
|
2831
2897
|
}
|
|
2832
2898
|
}
|
|
2833
2899
|
|
|
2900
|
+
// libs/uipack/src/adapters/markdown.ts
|
|
2901
|
+
var SAFE_HREF = /^(https?:|mailto:|\/|#)/i;
|
|
2902
|
+
function emphasis(text) {
|
|
2903
|
+
return text.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
|
2904
|
+
}
|
|
2905
|
+
function inline(text) {
|
|
2906
|
+
const stash = [];
|
|
2907
|
+
const hold = (html2) => `\uE000${stash.push(html2) - 1}\uE000`;
|
|
2908
|
+
let out = escapeHtml(text.replace(/\uE000/g, "")).replace(
|
|
2909
|
+
/`([^`]+)`/g,
|
|
2910
|
+
(_m, code) => hold(`<code>${code}</code>`)
|
|
2911
|
+
);
|
|
2912
|
+
out = out.replace(
|
|
2913
|
+
/\[([^\]]+)\]\(([^)\s]+)\)/g,
|
|
2914
|
+
(_m, label, href) => SAFE_HREF.test(href) ? hold(`<a href="${href}" rel="noopener noreferrer">${emphasis(label)}</a>`) : label
|
|
2915
|
+
);
|
|
2916
|
+
out = emphasis(out);
|
|
2917
|
+
for (let i = 0; i < 2; i++) out = out.replace(/\uE000(\d+)\uE000/g, (_m, n) => stash[Number(n)]);
|
|
2918
|
+
return out;
|
|
2919
|
+
}
|
|
2920
|
+
function markdownToHtml(source) {
|
|
2921
|
+
const lines = source.replace(/\r\n?/g, "\n").split("\n");
|
|
2922
|
+
const blocks = [];
|
|
2923
|
+
let paragraph = [];
|
|
2924
|
+
let list;
|
|
2925
|
+
const flushParagraph = () => {
|
|
2926
|
+
if (paragraph.length > 0) blocks.push(`<p>${inline(paragraph.join(" "))}</p>`);
|
|
2927
|
+
paragraph = [];
|
|
2928
|
+
};
|
|
2929
|
+
const flushList = () => {
|
|
2930
|
+
if (list)
|
|
2931
|
+
blocks.push(`<${list.tag}>${list.items.map((item) => `<li>${inline(item)}</li>`).join("")}</${list.tag}>`);
|
|
2932
|
+
list = void 0;
|
|
2933
|
+
};
|
|
2934
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2935
|
+
const line = lines[i];
|
|
2936
|
+
const fence = /^\s*(`{3,})/.exec(line);
|
|
2937
|
+
if (fence) {
|
|
2938
|
+
flushParagraph();
|
|
2939
|
+
flushList();
|
|
2940
|
+
const fenceLength = fence[1].length;
|
|
2941
|
+
const isClosing = (l) => {
|
|
2942
|
+
const m = /^\s*(`{3,})\s*$/.exec(l);
|
|
2943
|
+
return m !== null && m[1].length >= fenceLength;
|
|
2944
|
+
};
|
|
2945
|
+
const code = [];
|
|
2946
|
+
for (i++; i < lines.length && !isClosing(lines[i]); i++) code.push(lines[i]);
|
|
2947
|
+
blocks.push(`<pre><code>${escapeHtml(code.join("\n"))}
|
|
2948
|
+
</code></pre>`);
|
|
2949
|
+
continue;
|
|
2950
|
+
}
|
|
2951
|
+
const heading = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
2952
|
+
if (heading) {
|
|
2953
|
+
flushParagraph();
|
|
2954
|
+
flushList();
|
|
2955
|
+
blocks.push(`<h${heading[1].length}>${inline(heading[2].trim())}</h${heading[1].length}>`);
|
|
2956
|
+
continue;
|
|
2957
|
+
}
|
|
2958
|
+
const item = /^\s*([-*+]|\d+\.)\s+(.*)$/.exec(line);
|
|
2959
|
+
if (item) {
|
|
2960
|
+
flushParagraph();
|
|
2961
|
+
const tag = /\d/.test(item[1]) ? "ol" : "ul";
|
|
2962
|
+
if (list && list.tag !== tag) flushList();
|
|
2963
|
+
list ??= { tag, items: [] };
|
|
2964
|
+
list.items.push(item[2]);
|
|
2965
|
+
continue;
|
|
2966
|
+
}
|
|
2967
|
+
if (line.trim() === "") {
|
|
2968
|
+
flushParagraph();
|
|
2969
|
+
flushList();
|
|
2970
|
+
continue;
|
|
2971
|
+
}
|
|
2972
|
+
flushList();
|
|
2973
|
+
paragraph.push(line.trim());
|
|
2974
|
+
}
|
|
2975
|
+
flushParagraph();
|
|
2976
|
+
flushList();
|
|
2977
|
+
return blocks.join("\n");
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2834
2980
|
// libs/uipack/src/adapters/type-detector.ts
|
|
2835
2981
|
function detectUIType(template) {
|
|
2836
2982
|
if (template === null || template === void 0) {
|
|
@@ -2864,9 +3010,15 @@ function detectUIType(template) {
|
|
|
2864
3010
|
}
|
|
2865
3011
|
|
|
2866
3012
|
// libs/uipack/src/adapters/template-renderer.ts
|
|
2867
|
-
function buildCspConfig(resolver) {
|
|
3013
|
+
function buildCspConfig(resolver, extra) {
|
|
2868
3014
|
const cspResourceDomains = ["https://esm.sh"];
|
|
2869
3015
|
const cspConnectDomains = ["https://esm.sh"];
|
|
3016
|
+
for (const domain of extra?.resourceDomains ?? []) {
|
|
3017
|
+
if (!cspResourceDomains.includes(domain)) cspResourceDomains.push(domain);
|
|
3018
|
+
}
|
|
3019
|
+
for (const domain of extra?.connectDomains ?? []) {
|
|
3020
|
+
if (!cspConnectDomains.includes(domain)) cspConnectDomains.push(domain);
|
|
3021
|
+
}
|
|
2870
3022
|
if (resolver && "overrides" in resolver) {
|
|
2871
3023
|
const overrides = resolver.overrides;
|
|
2872
3024
|
if (overrides) {
|
|
@@ -2906,6 +3058,14 @@ function renderUnwrappedResult(rawResult, options) {
|
|
|
2906
3058
|
}
|
|
2907
3059
|
return rawResult;
|
|
2908
3060
|
}
|
|
3061
|
+
function isReactElement(value) {
|
|
3062
|
+
return typeof value === "object" && value !== null && "$$typeof" in value;
|
|
3063
|
+
}
|
|
3064
|
+
function isDefinitelyReactComponent(template) {
|
|
3065
|
+
if (typeof template !== "function") return false;
|
|
3066
|
+
const proto = template.prototype;
|
|
3067
|
+
return typeof proto?.render === "function" || template["$$typeof"] !== void 0;
|
|
3068
|
+
}
|
|
2909
3069
|
function renderToolTemplate(options) {
|
|
2910
3070
|
const { toolName, input, output, template, resolver, platformType, sizing } = options;
|
|
2911
3071
|
const uiType = detectUIType(template);
|
|
@@ -2916,30 +3076,43 @@ function renderToolTemplate(options) {
|
|
|
2916
3076
|
output,
|
|
2917
3077
|
includeBridge: true,
|
|
2918
3078
|
resolver,
|
|
2919
|
-
sizing
|
|
3079
|
+
sizing,
|
|
3080
|
+
csp: options.csp ? buildCspConfig(resolver, options.csp) : void 0
|
|
2920
3081
|
};
|
|
2921
3082
|
let html2;
|
|
2922
3083
|
let hash = "";
|
|
2923
3084
|
let size = 0;
|
|
2924
3085
|
if (typeof template === "object" && template !== null && "file" in template) {
|
|
2925
|
-
const cspConfig = buildCspConfig(resolver);
|
|
2926
3086
|
const result = renderComponent(
|
|
2927
3087
|
{ source: template, inlineReact: resourceMode === "inline" },
|
|
2928
|
-
{ ...shellConfig, csp:
|
|
3088
|
+
{ ...shellConfig, csp: buildCspConfig(resolver, options.csp) }
|
|
2929
3089
|
);
|
|
2930
3090
|
html2 = result.html;
|
|
2931
3091
|
hash = result.hash;
|
|
2932
3092
|
size = result.size;
|
|
2933
3093
|
} else if (typeof template === "function") {
|
|
2934
|
-
|
|
3094
|
+
const helpers = createTemplateHelpers();
|
|
3095
|
+
const ctx = { input, output, helpers };
|
|
3096
|
+
let rawResult;
|
|
3097
|
+
let isHtmlBuilder = uiType !== "react" || !isDefinitelyReactComponent(template);
|
|
3098
|
+
if (isHtmlBuilder) {
|
|
3099
|
+
try {
|
|
3100
|
+
rawResult = template(ctx);
|
|
3101
|
+
isHtmlBuilder = !isReactElement(rawResult);
|
|
3102
|
+
} catch (error) {
|
|
3103
|
+
if (uiType !== "react") throw error;
|
|
3104
|
+
isHtmlBuilder = false;
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
if (!isHtmlBuilder) {
|
|
3108
|
+
(options.logger ?? console).warn(
|
|
3109
|
+
`[FrontMCP] Tool "${toolName}": \`ui.template\` is a React component reference, which cannot be bundled (there is no source file to compile), so the widget would render an empty page. Use \`template: { file: './widget.tsx' }\` instead.`
|
|
3110
|
+
);
|
|
2935
3111
|
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
2936
3112
|
html2 = shellResult.html;
|
|
2937
3113
|
hash = shellResult.hash;
|
|
2938
3114
|
size = shellResult.size;
|
|
2939
3115
|
} else {
|
|
2940
|
-
const helpers = createTemplateHelpers();
|
|
2941
|
-
const ctx = { input, output, helpers };
|
|
2942
|
-
const rawResult = template(ctx);
|
|
2943
3116
|
const wrapped = isTrustedHtml(rawResult) ? void 0 : wrapDetectedContent(rawResult);
|
|
2944
3117
|
if (wrapped) {
|
|
2945
3118
|
html2 = wrapped;
|
|
@@ -2951,7 +3124,7 @@ function renderToolTemplate(options) {
|
|
|
2951
3124
|
}
|
|
2952
3125
|
}
|
|
2953
3126
|
} else if (typeof template === "string") {
|
|
2954
|
-
const shellResult = buildShell(template, shellConfig);
|
|
3127
|
+
const shellResult = buildShell(uiType === "markdown" ? markdownToHtml(template) : template, shellConfig);
|
|
2955
3128
|
html2 = shellResult.html;
|
|
2956
3129
|
hash = shellResult.hash;
|
|
2957
3130
|
size = shellResult.size;
|