@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/adapters/index.js
CHANGED
|
@@ -829,12 +829,38 @@ function __applyHostTheme(theme) {
|
|
|
829
829
|
} catch (e) {}
|
|
830
830
|
}
|
|
831
831
|
|
|
832
|
+
function extractToolOutput(params) {
|
|
833
|
+
// A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
|
|
834
|
+
if (!params) return undefined;
|
|
835
|
+
if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
|
|
836
|
+
var content = params.content;
|
|
837
|
+
if (Array.isArray(content)) {
|
|
838
|
+
for (var i = 0; i < content.length; i++) {
|
|
839
|
+
var item = content[i];
|
|
840
|
+
if (item && item.type === 'text' && typeof item.text === 'string') {
|
|
841
|
+
try { return JSON.parse(item.text); } catch (e) { return item.text; }
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return content;
|
|
846
|
+
}
|
|
847
|
+
|
|
832
848
|
function readInjectedData() {
|
|
833
849
|
var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
|
|
834
850
|
if (typeof window !== 'undefined') {
|
|
835
851
|
if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
|
|
836
852
|
if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
|
|
837
853
|
if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
|
|
854
|
+
// The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
|
|
855
|
+
// (toolOutput is the tool's structuredContent).
|
|
856
|
+
var oa = window.openai;
|
|
857
|
+
if (oa && typeof oa === 'object') {
|
|
858
|
+
if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
|
|
859
|
+
if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
|
|
860
|
+
if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
|
|
861
|
+
if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
838
864
|
}
|
|
839
865
|
return data;
|
|
840
866
|
}
|
|
@@ -883,7 +909,24 @@ var OpenAIAdapter = {
|
|
|
883
909
|
if (sdk.displayMode) {
|
|
884
910
|
context.hostContext.displayMode = sdk.displayMode;
|
|
885
911
|
}
|
|
886
|
-
//
|
|
912
|
+
// The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
|
|
913
|
+
window.addEventListener('openai:set_globals', function(event) {
|
|
914
|
+
var globals = (event && event.detail && event.detail.globals) || window.openai || {};
|
|
915
|
+
if (globals.toolInput !== undefined && globals.toolInput !== null) {
|
|
916
|
+
context.toolInput = globals.toolInput;
|
|
917
|
+
window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
|
|
918
|
+
}
|
|
919
|
+
if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
|
|
920
|
+
context.structuredContent = globals.toolOutput;
|
|
921
|
+
context.notifyToolResult(globals.toolOutput);
|
|
922
|
+
}
|
|
923
|
+
if (globals.theme && globals.theme !== context.hostContext.theme) {
|
|
924
|
+
context.notifyContextChange({ theme: globals.theme });
|
|
925
|
+
}
|
|
926
|
+
if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
|
|
927
|
+
context.notifyContextChange({ displayMode: globals.displayMode });
|
|
928
|
+
}
|
|
929
|
+
});
|
|
887
930
|
return Promise.resolve();
|
|
888
931
|
},
|
|
889
932
|
callTool: function(context, name, args) {
|
|
@@ -1030,9 +1073,10 @@ var ExtAppsAdapter = {
|
|
|
1030
1073
|
window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
|
|
1031
1074
|
break;
|
|
1032
1075
|
case 'ui/notifications/tool-result':
|
|
1033
|
-
|
|
1076
|
+
var resultOutput = extractToolOutput(params);
|
|
1077
|
+
context.toolOutput = resultOutput;
|
|
1034
1078
|
context.structuredContent = params.structuredContent;
|
|
1035
|
-
context.notifyToolResult(
|
|
1079
|
+
context.notifyToolResult(resultOutput);
|
|
1036
1080
|
window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
|
|
1037
1081
|
break;
|
|
1038
1082
|
case 'ui/notifications/host-context-changed':
|
|
@@ -1086,6 +1130,15 @@ var ExtAppsAdapter = {
|
|
|
1086
1130
|
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
1087
1131
|
});
|
|
1088
1132
|
},
|
|
1133
|
+
sendNotification: function(method, params) {
|
|
1134
|
+
// JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
|
|
1135
|
+
if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
1136
|
+
return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
|
|
1137
|
+
}
|
|
1138
|
+
var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
|
|
1139
|
+
window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
|
|
1140
|
+
return Promise.resolve();
|
|
1141
|
+
},
|
|
1089
1142
|
sendHandshake: function(context) {
|
|
1090
1143
|
// Send ui/initialize using '*' as target origin since TOFU hasn't
|
|
1091
1144
|
// been established yet. The response from the host will establish
|
|
@@ -1151,7 +1204,7 @@ var ExtAppsAdapter = {
|
|
|
1151
1204
|
var pending = this.pendingSize;
|
|
1152
1205
|
if (!pending) return;
|
|
1153
1206
|
this.pendingSize = null;
|
|
1154
|
-
this.
|
|
1207
|
+
this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
|
|
1155
1208
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
|
|
1156
1209
|
}, function(err) {
|
|
1157
1210
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
|
|
@@ -1178,13 +1231,11 @@ var ExtAppsAdapter = {
|
|
|
1178
1231
|
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
1179
1232
|
},
|
|
1180
1233
|
setSize: function(context, size) {
|
|
1181
|
-
//
|
|
1182
|
-
//
|
|
1183
|
-
var params = {
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
aspectRatio: size && size.aspectRatio
|
|
1187
|
-
};
|
|
1234
|
+
// Standard ext-apps sizing: the view tells the host its dimensions with the
|
|
1235
|
+
// ui/notifications/size-changed notification ({ width?, height? } in px).
|
|
1236
|
+
var params = {};
|
|
1237
|
+
if (size && typeof size.width === 'number') params.width = size.width;
|
|
1238
|
+
if (size && typeof size.height === 'number') params.height = size.height;
|
|
1188
1239
|
if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
1189
1240
|
// No host origin yet: keep only the latest size and send it when the
|
|
1190
1241
|
// handshake settles; every caller's promise follows that one request.
|
|
@@ -1195,7 +1246,7 @@ var ExtAppsAdapter = {
|
|
|
1195
1246
|
self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
|
|
1196
1247
|
});
|
|
1197
1248
|
}
|
|
1198
|
-
return this.
|
|
1249
|
+
return this.sendNotification('ui/notifications/size-changed', params);
|
|
1199
1250
|
},
|
|
1200
1251
|
requestClose: function(context) {
|
|
1201
1252
|
return this.sendRequest('ui/close', {});
|
|
@@ -1618,7 +1669,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
|
1618
1669
|
|
|
1619
1670
|
// Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
|
|
1620
1671
|
// Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
|
|
1621
|
-
// ext-apps sends ui/
|
|
1672
|
+
// ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
|
|
1622
1673
|
FrontMcpBridge.prototype.setSize = function(size) {
|
|
1623
1674
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1624
1675
|
if (!this._adapter.setSize) return Promise.resolve();
|
|
@@ -1720,7 +1771,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
|
|
|
1720
1771
|
target.classList.add('opacity-50', 'cursor-not-allowed');
|
|
1721
1772
|
|
|
1722
1773
|
// Add spinner for buttons
|
|
1723
|
-
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>';
|
|
1774
|
+
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>';
|
|
1724
1775
|
if (target.tagName === 'BUTTON') {
|
|
1725
1776
|
target.innerHTML = spinner + 'Loading...';
|
|
1726
1777
|
}
|
|
@@ -1801,7 +1852,22 @@ function minifyJS(code) {
|
|
|
1801
1852
|
if (code.length > MAX_MINIFY_CODE_LENGTH) {
|
|
1802
1853
|
return code;
|
|
1803
1854
|
}
|
|
1804
|
-
|
|
1855
|
+
const out = [];
|
|
1856
|
+
let inBlockComment = false;
|
|
1857
|
+
for (const raw of code.split("\n")) {
|
|
1858
|
+
const line = raw.trim();
|
|
1859
|
+
if (inBlockComment) {
|
|
1860
|
+
if (line.includes("*/")) inBlockComment = false;
|
|
1861
|
+
continue;
|
|
1862
|
+
}
|
|
1863
|
+
if (line === "" || line.startsWith("//")) continue;
|
|
1864
|
+
if (line.startsWith("/*")) {
|
|
1865
|
+
if (!line.includes("*/")) inBlockComment = true;
|
|
1866
|
+
continue;
|
|
1867
|
+
}
|
|
1868
|
+
out.push(line);
|
|
1869
|
+
}
|
|
1870
|
+
return out.join("\n");
|
|
1805
1871
|
}
|
|
1806
1872
|
function generatePlatformBundle(platform, options = {}) {
|
|
1807
1873
|
const platformAdapters = {
|
|
@@ -2863,6 +2929,86 @@ function wrapDetectedContent(value) {
|
|
|
2863
2929
|
}
|
|
2864
2930
|
}
|
|
2865
2931
|
|
|
2932
|
+
// libs/uipack/src/adapters/markdown.ts
|
|
2933
|
+
var SAFE_HREF = /^(https?:|mailto:|\/|#)/i;
|
|
2934
|
+
function emphasis(text) {
|
|
2935
|
+
return text.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
|
2936
|
+
}
|
|
2937
|
+
function inline(text) {
|
|
2938
|
+
const stash = [];
|
|
2939
|
+
const hold = (html2) => `\uE000${stash.push(html2) - 1}\uE000`;
|
|
2940
|
+
let out = escapeHtml(text.replace(/\uE000/g, "")).replace(
|
|
2941
|
+
/`([^`]+)`/g,
|
|
2942
|
+
(_m, code) => hold(`<code>${code}</code>`)
|
|
2943
|
+
);
|
|
2944
|
+
out = out.replace(
|
|
2945
|
+
/\[([^\]]+)\]\(([^)\s]+)\)/g,
|
|
2946
|
+
(_m, label, href) => SAFE_HREF.test(href) ? hold(`<a href="${href}" rel="noopener noreferrer">${emphasis(label)}</a>`) : label
|
|
2947
|
+
);
|
|
2948
|
+
out = emphasis(out);
|
|
2949
|
+
for (let i = 0; i < 2; i++) out = out.replace(/\uE000(\d+)\uE000/g, (_m, n) => stash[Number(n)]);
|
|
2950
|
+
return out;
|
|
2951
|
+
}
|
|
2952
|
+
function markdownToHtml(source) {
|
|
2953
|
+
const lines = source.replace(/\r\n?/g, "\n").split("\n");
|
|
2954
|
+
const blocks = [];
|
|
2955
|
+
let paragraph = [];
|
|
2956
|
+
let list;
|
|
2957
|
+
const flushParagraph = () => {
|
|
2958
|
+
if (paragraph.length > 0) blocks.push(`<p>${inline(paragraph.join(" "))}</p>`);
|
|
2959
|
+
paragraph = [];
|
|
2960
|
+
};
|
|
2961
|
+
const flushList = () => {
|
|
2962
|
+
if (list)
|
|
2963
|
+
blocks.push(`<${list.tag}>${list.items.map((item) => `<li>${inline(item)}</li>`).join("")}</${list.tag}>`);
|
|
2964
|
+
list = void 0;
|
|
2965
|
+
};
|
|
2966
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2967
|
+
const line = lines[i];
|
|
2968
|
+
const fence = /^\s*(`{3,})/.exec(line);
|
|
2969
|
+
if (fence) {
|
|
2970
|
+
flushParagraph();
|
|
2971
|
+
flushList();
|
|
2972
|
+
const fenceLength = fence[1].length;
|
|
2973
|
+
const isClosing = (l) => {
|
|
2974
|
+
const m = /^\s*(`{3,})\s*$/.exec(l);
|
|
2975
|
+
return m !== null && m[1].length >= fenceLength;
|
|
2976
|
+
};
|
|
2977
|
+
const code = [];
|
|
2978
|
+
for (i++; i < lines.length && !isClosing(lines[i]); i++) code.push(lines[i]);
|
|
2979
|
+
blocks.push(`<pre><code>${escapeHtml(code.join("\n"))}
|
|
2980
|
+
</code></pre>`);
|
|
2981
|
+
continue;
|
|
2982
|
+
}
|
|
2983
|
+
const heading = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
2984
|
+
if (heading) {
|
|
2985
|
+
flushParagraph();
|
|
2986
|
+
flushList();
|
|
2987
|
+
blocks.push(`<h${heading[1].length}>${inline(heading[2].trim())}</h${heading[1].length}>`);
|
|
2988
|
+
continue;
|
|
2989
|
+
}
|
|
2990
|
+
const item = /^\s*([-*+]|\d+\.)\s+(.*)$/.exec(line);
|
|
2991
|
+
if (item) {
|
|
2992
|
+
flushParagraph();
|
|
2993
|
+
const tag = /\d/.test(item[1]) ? "ol" : "ul";
|
|
2994
|
+
if (list && list.tag !== tag) flushList();
|
|
2995
|
+
list ??= { tag, items: [] };
|
|
2996
|
+
list.items.push(item[2]);
|
|
2997
|
+
continue;
|
|
2998
|
+
}
|
|
2999
|
+
if (line.trim() === "") {
|
|
3000
|
+
flushParagraph();
|
|
3001
|
+
flushList();
|
|
3002
|
+
continue;
|
|
3003
|
+
}
|
|
3004
|
+
flushList();
|
|
3005
|
+
paragraph.push(line.trim());
|
|
3006
|
+
}
|
|
3007
|
+
flushParagraph();
|
|
3008
|
+
flushList();
|
|
3009
|
+
return blocks.join("\n");
|
|
3010
|
+
}
|
|
3011
|
+
|
|
2866
3012
|
// libs/uipack/src/adapters/type-detector.ts
|
|
2867
3013
|
function detectUIType(template) {
|
|
2868
3014
|
if (template === null || template === void 0) {
|
|
@@ -2896,9 +3042,15 @@ function detectUIType(template) {
|
|
|
2896
3042
|
}
|
|
2897
3043
|
|
|
2898
3044
|
// libs/uipack/src/adapters/template-renderer.ts
|
|
2899
|
-
function buildCspConfig(resolver) {
|
|
3045
|
+
function buildCspConfig(resolver, extra) {
|
|
2900
3046
|
const cspResourceDomains = ["https://esm.sh"];
|
|
2901
3047
|
const cspConnectDomains = ["https://esm.sh"];
|
|
3048
|
+
for (const domain of extra?.resourceDomains ?? []) {
|
|
3049
|
+
if (!cspResourceDomains.includes(domain)) cspResourceDomains.push(domain);
|
|
3050
|
+
}
|
|
3051
|
+
for (const domain of extra?.connectDomains ?? []) {
|
|
3052
|
+
if (!cspConnectDomains.includes(domain)) cspConnectDomains.push(domain);
|
|
3053
|
+
}
|
|
2902
3054
|
if (resolver && "overrides" in resolver) {
|
|
2903
3055
|
const overrides = resolver.overrides;
|
|
2904
3056
|
if (overrides) {
|
|
@@ -2938,6 +3090,14 @@ function renderUnwrappedResult(rawResult, options) {
|
|
|
2938
3090
|
}
|
|
2939
3091
|
return rawResult;
|
|
2940
3092
|
}
|
|
3093
|
+
function isReactElement(value) {
|
|
3094
|
+
return typeof value === "object" && value !== null && "$$typeof" in value;
|
|
3095
|
+
}
|
|
3096
|
+
function isDefinitelyReactComponent(template) {
|
|
3097
|
+
if (typeof template !== "function") return false;
|
|
3098
|
+
const proto = template.prototype;
|
|
3099
|
+
return typeof proto?.render === "function" || template["$$typeof"] !== void 0;
|
|
3100
|
+
}
|
|
2941
3101
|
function renderToolTemplate(options) {
|
|
2942
3102
|
const { toolName, input, output, template, resolver, platformType, sizing } = options;
|
|
2943
3103
|
const uiType = detectUIType(template);
|
|
@@ -2948,30 +3108,43 @@ function renderToolTemplate(options) {
|
|
|
2948
3108
|
output,
|
|
2949
3109
|
includeBridge: true,
|
|
2950
3110
|
resolver,
|
|
2951
|
-
sizing
|
|
3111
|
+
sizing,
|
|
3112
|
+
csp: options.csp ? buildCspConfig(resolver, options.csp) : void 0
|
|
2952
3113
|
};
|
|
2953
3114
|
let html2;
|
|
2954
3115
|
let hash = "";
|
|
2955
3116
|
let size = 0;
|
|
2956
3117
|
if (typeof template === "object" && template !== null && "file" in template) {
|
|
2957
|
-
const cspConfig = buildCspConfig(resolver);
|
|
2958
3118
|
const result = renderComponent(
|
|
2959
3119
|
{ source: template, inlineReact: resourceMode === "inline" },
|
|
2960
|
-
{ ...shellConfig, csp:
|
|
3120
|
+
{ ...shellConfig, csp: buildCspConfig(resolver, options.csp) }
|
|
2961
3121
|
);
|
|
2962
3122
|
html2 = result.html;
|
|
2963
3123
|
hash = result.hash;
|
|
2964
3124
|
size = result.size;
|
|
2965
3125
|
} else if (typeof template === "function") {
|
|
2966
|
-
|
|
3126
|
+
const helpers = createTemplateHelpers();
|
|
3127
|
+
const ctx = { input, output, helpers };
|
|
3128
|
+
let rawResult;
|
|
3129
|
+
let isHtmlBuilder = uiType !== "react" || !isDefinitelyReactComponent(template);
|
|
3130
|
+
if (isHtmlBuilder) {
|
|
3131
|
+
try {
|
|
3132
|
+
rawResult = template(ctx);
|
|
3133
|
+
isHtmlBuilder = !isReactElement(rawResult);
|
|
3134
|
+
} catch (error) {
|
|
3135
|
+
if (uiType !== "react") throw error;
|
|
3136
|
+
isHtmlBuilder = false;
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
if (!isHtmlBuilder) {
|
|
3140
|
+
(options.logger ?? console).warn(
|
|
3141
|
+
`[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.`
|
|
3142
|
+
);
|
|
2967
3143
|
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
2968
3144
|
html2 = shellResult.html;
|
|
2969
3145
|
hash = shellResult.hash;
|
|
2970
3146
|
size = shellResult.size;
|
|
2971
3147
|
} else {
|
|
2972
|
-
const helpers = createTemplateHelpers();
|
|
2973
|
-
const ctx = { input, output, helpers };
|
|
2974
|
-
const rawResult = template(ctx);
|
|
2975
3148
|
const wrapped = isTrustedHtml(rawResult) ? void 0 : wrapDetectedContent(rawResult);
|
|
2976
3149
|
if (wrapped) {
|
|
2977
3150
|
html2 = wrapped;
|
|
@@ -2983,7 +3156,7 @@ function renderToolTemplate(options) {
|
|
|
2983
3156
|
}
|
|
2984
3157
|
}
|
|
2985
3158
|
} else if (typeof template === "string") {
|
|
2986
|
-
const shellResult = buildShell(template, shellConfig);
|
|
3159
|
+
const shellResult = buildShell(uiType === "markdown" ? markdownToHtml(template) : template, shellConfig);
|
|
2987
3160
|
html2 = shellResult.html;
|
|
2988
3161
|
hash = shellResult.hash;
|
|
2989
3162
|
size = shellResult.size;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal, safe Markdown to HTML conversion for `ui.template` strings.
|
|
3
|
+
*
|
|
4
|
+
* Supports headings, paragraphs, fenced code, ordered/unordered lists and inline
|
|
5
|
+
* bold, italic, code and http(s)/mailto links. Everything else is escaped text; raw HTML in the
|
|
6
|
+
* source is never passed through.
|
|
7
|
+
*/
|
|
8
|
+
export declare function markdownToHtml(source: string): string;
|
|
9
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/adapters/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA2BH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAgErD"}
|
|
@@ -36,6 +36,14 @@ export interface RenderToolTemplateOptions {
|
|
|
36
36
|
* shell, and is mirrored onto the returned `meta` as `ui/preferredHeight` etc.
|
|
37
37
|
*/
|
|
38
38
|
sizing?: WidgetSizing;
|
|
39
|
+
/**
|
|
40
|
+
* Extra origins the widget may load from (`resourceDomains`) or call (`connectDomains`), from the
|
|
41
|
+
* tool's `ui.csp`. Merged into the page's own Content-Security-Policy on every template path.
|
|
42
|
+
*/
|
|
43
|
+
csp?: {
|
|
44
|
+
connectDomains?: string[];
|
|
45
|
+
resourceDomains?: string[];
|
|
46
|
+
};
|
|
39
47
|
/**
|
|
40
48
|
* HTML-escape a plain string returned by a template function; results built with
|
|
41
49
|
* `html` / `trustedHtml` stay markup. When unset, strings that look like HTML render as
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template-renderer.d.ts","sourceRoot":"","sources":["../../src/adapters/template-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAIxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"template-renderer.d.ts","sourceRoot":"","sources":["../../src/adapters/template-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAIxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAQnD;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,gBAAgB;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,kBAAkB;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,qDAAqD;IACrD,QAAQ,EAAE,OAAO,CAAC;IAClB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAChC;;;;OAIG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;OAGG;IACH,GAAG,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAChE;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,yEAAyE;IACzE,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;CAC9C;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AA+ED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,wBAAwB,CAsH/F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"iife-generator.d.ts","sourceRoot":"","sources":["../../src/bridge-runtime/iife-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,uDAAuD;IACvD,QAAQ,CAAC,EAAE,CAAC,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC,EAAE,CAAC;IACvE,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,2CAA2C;IAC3C,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,wBAAwB;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,oBAAyB,GAAG,MAAM,CA8G7E;
|
|
1
|
+
{"version":3,"file":"iife-generator.d.ts","sourceRoot":"","sources":["../../src/bridge-runtime/iife-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,uDAAuD;IACvD,QAAQ,CAAC,EAAE,CAAC,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC,EAAE,CAAC;IACvE,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,2CAA2C;IAC3C,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,wBAAwB;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,oBAAyB,GAAG,MAAM,CA8G7E;AA8xCD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,EACvD,OAAO,GAAE,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAM,GACnD,MAAM,CAYR;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,QAAuB,CAAC;AAE5D;;GAEG;AACH,eAAO,MAAM,kBAAkB;;;;;CAK9B,CAAC"}
|
package/bridge-runtime/index.js
CHANGED
|
@@ -294,12 +294,38 @@ function __applyHostTheme(theme) {
|
|
|
294
294
|
} catch (e) {}
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
+
function extractToolOutput(params) {
|
|
298
|
+
// A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
|
|
299
|
+
if (!params) return undefined;
|
|
300
|
+
if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
|
|
301
|
+
var content = params.content;
|
|
302
|
+
if (Array.isArray(content)) {
|
|
303
|
+
for (var i = 0; i < content.length; i++) {
|
|
304
|
+
var item = content[i];
|
|
305
|
+
if (item && item.type === 'text' && typeof item.text === 'string') {
|
|
306
|
+
try { return JSON.parse(item.text); } catch (e) { return item.text; }
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return content;
|
|
311
|
+
}
|
|
312
|
+
|
|
297
313
|
function readInjectedData() {
|
|
298
314
|
var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
|
|
299
315
|
if (typeof window !== 'undefined') {
|
|
300
316
|
if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
|
|
301
317
|
if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
|
|
302
318
|
if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
|
|
319
|
+
// The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
|
|
320
|
+
// (toolOutput is the tool's structuredContent).
|
|
321
|
+
var oa = window.openai;
|
|
322
|
+
if (oa && typeof oa === 'object') {
|
|
323
|
+
if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
|
|
324
|
+
if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
|
|
325
|
+
if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
|
|
326
|
+
if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
303
329
|
}
|
|
304
330
|
return data;
|
|
305
331
|
}
|
|
@@ -348,7 +374,24 @@ var OpenAIAdapter = {
|
|
|
348
374
|
if (sdk.displayMode) {
|
|
349
375
|
context.hostContext.displayMode = sdk.displayMode;
|
|
350
376
|
}
|
|
351
|
-
//
|
|
377
|
+
// The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
|
|
378
|
+
window.addEventListener('openai:set_globals', function(event) {
|
|
379
|
+
var globals = (event && event.detail && event.detail.globals) || window.openai || {};
|
|
380
|
+
if (globals.toolInput !== undefined && globals.toolInput !== null) {
|
|
381
|
+
context.toolInput = globals.toolInput;
|
|
382
|
+
window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
|
|
383
|
+
}
|
|
384
|
+
if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
|
|
385
|
+
context.structuredContent = globals.toolOutput;
|
|
386
|
+
context.notifyToolResult(globals.toolOutput);
|
|
387
|
+
}
|
|
388
|
+
if (globals.theme && globals.theme !== context.hostContext.theme) {
|
|
389
|
+
context.notifyContextChange({ theme: globals.theme });
|
|
390
|
+
}
|
|
391
|
+
if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
|
|
392
|
+
context.notifyContextChange({ displayMode: globals.displayMode });
|
|
393
|
+
}
|
|
394
|
+
});
|
|
352
395
|
return Promise.resolve();
|
|
353
396
|
},
|
|
354
397
|
callTool: function(context, name, args) {
|
|
@@ -495,9 +538,10 @@ var ExtAppsAdapter = {
|
|
|
495
538
|
window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
|
|
496
539
|
break;
|
|
497
540
|
case 'ui/notifications/tool-result':
|
|
498
|
-
|
|
541
|
+
var resultOutput = extractToolOutput(params);
|
|
542
|
+
context.toolOutput = resultOutput;
|
|
499
543
|
context.structuredContent = params.structuredContent;
|
|
500
|
-
context.notifyToolResult(
|
|
544
|
+
context.notifyToolResult(resultOutput);
|
|
501
545
|
window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
|
|
502
546
|
break;
|
|
503
547
|
case 'ui/notifications/host-context-changed':
|
|
@@ -551,6 +595,15 @@ var ExtAppsAdapter = {
|
|
|
551
595
|
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
552
596
|
});
|
|
553
597
|
},
|
|
598
|
+
sendNotification: function(method, params) {
|
|
599
|
+
// JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
|
|
600
|
+
if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
601
|
+
return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
|
|
602
|
+
}
|
|
603
|
+
var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
|
|
604
|
+
window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
|
|
605
|
+
return Promise.resolve();
|
|
606
|
+
},
|
|
554
607
|
sendHandshake: function(context) {
|
|
555
608
|
// Send ui/initialize using '*' as target origin since TOFU hasn't
|
|
556
609
|
// been established yet. The response from the host will establish
|
|
@@ -616,7 +669,7 @@ var ExtAppsAdapter = {
|
|
|
616
669
|
var pending = this.pendingSize;
|
|
617
670
|
if (!pending) return;
|
|
618
671
|
this.pendingSize = null;
|
|
619
|
-
this.
|
|
672
|
+
this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
|
|
620
673
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
|
|
621
674
|
}, function(err) {
|
|
622
675
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
|
|
@@ -643,13 +696,11 @@ var ExtAppsAdapter = {
|
|
|
643
696
|
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
644
697
|
},
|
|
645
698
|
setSize: function(context, size) {
|
|
646
|
-
//
|
|
647
|
-
//
|
|
648
|
-
var params = {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
aspectRatio: size && size.aspectRatio
|
|
652
|
-
};
|
|
699
|
+
// Standard ext-apps sizing: the view tells the host its dimensions with the
|
|
700
|
+
// ui/notifications/size-changed notification ({ width?, height? } in px).
|
|
701
|
+
var params = {};
|
|
702
|
+
if (size && typeof size.width === 'number') params.width = size.width;
|
|
703
|
+
if (size && typeof size.height === 'number') params.height = size.height;
|
|
653
704
|
if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
654
705
|
// No host origin yet: keep only the latest size and send it when the
|
|
655
706
|
// handshake settles; every caller's promise follows that one request.
|
|
@@ -660,7 +711,7 @@ var ExtAppsAdapter = {
|
|
|
660
711
|
self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
|
|
661
712
|
});
|
|
662
713
|
}
|
|
663
|
-
return this.
|
|
714
|
+
return this.sendNotification('ui/notifications/size-changed', params);
|
|
664
715
|
},
|
|
665
716
|
requestClose: function(context) {
|
|
666
717
|
return this.sendRequest('ui/close', {});
|
|
@@ -1083,7 +1134,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
|
1083
1134
|
|
|
1084
1135
|
// Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
|
|
1085
1136
|
// Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
|
|
1086
|
-
// ext-apps sends ui/
|
|
1137
|
+
// ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
|
|
1087
1138
|
FrontMcpBridge.prototype.setSize = function(size) {
|
|
1088
1139
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1089
1140
|
if (!this._adapter.setSize) return Promise.resolve();
|
|
@@ -1185,7 +1236,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
|
|
|
1185
1236
|
target.classList.add('opacity-50', 'cursor-not-allowed');
|
|
1186
1237
|
|
|
1187
1238
|
// Add spinner for buttons
|
|
1188
|
-
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>';
|
|
1239
|
+
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>';
|
|
1189
1240
|
if (target.tagName === 'BUTTON') {
|
|
1190
1241
|
target.innerHTML = spinner + 'Loading...';
|
|
1191
1242
|
}
|
|
@@ -1266,7 +1317,22 @@ function minifyJS(code) {
|
|
|
1266
1317
|
if (code.length > MAX_MINIFY_CODE_LENGTH) {
|
|
1267
1318
|
return code;
|
|
1268
1319
|
}
|
|
1269
|
-
|
|
1320
|
+
const out = [];
|
|
1321
|
+
let inBlockComment = false;
|
|
1322
|
+
for (const raw of code.split("\n")) {
|
|
1323
|
+
const line = raw.trim();
|
|
1324
|
+
if (inBlockComment) {
|
|
1325
|
+
if (line.includes("*/")) inBlockComment = false;
|
|
1326
|
+
continue;
|
|
1327
|
+
}
|
|
1328
|
+
if (line === "" || line.startsWith("//")) continue;
|
|
1329
|
+
if (line.startsWith("/*")) {
|
|
1330
|
+
if (!line.includes("*/")) inBlockComment = true;
|
|
1331
|
+
continue;
|
|
1332
|
+
}
|
|
1333
|
+
out.push(line);
|
|
1334
|
+
}
|
|
1335
|
+
return out.join("\n");
|
|
1270
1336
|
}
|
|
1271
1337
|
function generatePlatformBundle(platform, options = {}) {
|
|
1272
1338
|
const platformAdapters = {
|