@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.
@@ -265,12 +265,38 @@ function __applyHostTheme(theme) {
265
265
  } catch (e) {}
266
266
  }
267
267
 
268
+ function extractToolOutput(params) {
269
+ // A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
270
+ if (!params) return undefined;
271
+ if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
272
+ var content = params.content;
273
+ if (Array.isArray(content)) {
274
+ for (var i = 0; i < content.length; i++) {
275
+ var item = content[i];
276
+ if (item && item.type === 'text' && typeof item.text === 'string') {
277
+ try { return JSON.parse(item.text); } catch (e) { return item.text; }
278
+ }
279
+ }
280
+ }
281
+ return content;
282
+ }
283
+
268
284
  function readInjectedData() {
269
285
  var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
270
286
  if (typeof window !== 'undefined') {
271
287
  if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
272
288
  if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
273
289
  if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
290
+ // The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
291
+ // (toolOutput is the tool's structuredContent).
292
+ var oa = window.openai;
293
+ if (oa && typeof oa === 'object') {
294
+ if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
295
+ if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
296
+ if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
297
+ if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
298
+ }
299
+ }
274
300
  }
275
301
  return data;
276
302
  }
@@ -319,7 +345,24 @@ var OpenAIAdapter = {
319
345
  if (sdk.displayMode) {
320
346
  context.hostContext.displayMode = sdk.displayMode;
321
347
  }
322
- // Note: OpenAI SDK does not have an onContextChange equivalent
348
+ // The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
349
+ window.addEventListener('openai:set_globals', function(event) {
350
+ var globals = (event && event.detail && event.detail.globals) || window.openai || {};
351
+ if (globals.toolInput !== undefined && globals.toolInput !== null) {
352
+ context.toolInput = globals.toolInput;
353
+ window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
354
+ }
355
+ if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
356
+ context.structuredContent = globals.toolOutput;
357
+ context.notifyToolResult(globals.toolOutput);
358
+ }
359
+ if (globals.theme && globals.theme !== context.hostContext.theme) {
360
+ context.notifyContextChange({ theme: globals.theme });
361
+ }
362
+ if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
363
+ context.notifyContextChange({ displayMode: globals.displayMode });
364
+ }
365
+ });
323
366
  return Promise.resolve();
324
367
  },
325
368
  callTool: function(context, name, args) {
@@ -466,9 +509,10 @@ var ExtAppsAdapter = {
466
509
  window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
467
510
  break;
468
511
  case 'ui/notifications/tool-result':
469
- context.toolOutput = params.content;
512
+ var resultOutput = extractToolOutput(params);
513
+ context.toolOutput = resultOutput;
470
514
  context.structuredContent = params.structuredContent;
471
- context.notifyToolResult(params.content);
515
+ context.notifyToolResult(resultOutput);
472
516
  window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
473
517
  break;
474
518
  case 'ui/notifications/host-context-changed':
@@ -522,6 +566,15 @@ var ExtAppsAdapter = {
522
566
  window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
523
567
  });
524
568
  },
569
+ sendNotification: function(method, params) {
570
+ // JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
571
+ if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
572
+ return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
573
+ }
574
+ var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
575
+ window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
576
+ return Promise.resolve();
577
+ },
525
578
  sendHandshake: function(context) {
526
579
  // Send ui/initialize using '*' as target origin since TOFU hasn't
527
580
  // been established yet. The response from the host will establish
@@ -587,7 +640,7 @@ var ExtAppsAdapter = {
587
640
  var pending = this.pendingSize;
588
641
  if (!pending) return;
589
642
  this.pendingSize = null;
590
- this.sendRequest('ui/setSize', pending.params).then(function(result) {
643
+ this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
591
644
  for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
592
645
  }, function(err) {
593
646
  for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
@@ -614,13 +667,11 @@ var ExtAppsAdapter = {
614
667
  return this.sendRequest('ui/setDisplayMode', { mode: mode });
615
668
  },
616
669
  setSize: function(context, size) {
617
- // FrontMCP sizing channel \u2014 parallels ui/setDisplayMode. Reports the
618
- // measured/desired widget dimensions to the host.
619
- var params = {
620
- height: size && size.height,
621
- width: size && size.width,
622
- aspectRatio: size && size.aspectRatio
623
- };
670
+ // Standard ext-apps sizing: the view tells the host its dimensions with the
671
+ // ui/notifications/size-changed notification ({ width?, height? } in px).
672
+ var params = {};
673
+ if (size && typeof size.width === 'number') params.width = size.width;
674
+ if (size && typeof size.height === 'number') params.height = size.height;
624
675
  if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
625
676
  // No host origin yet: keep only the latest size and send it when the
626
677
  // handshake settles; every caller's promise follows that one request.
@@ -631,7 +682,7 @@ var ExtAppsAdapter = {
631
682
  self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
632
683
  });
633
684
  }
634
- return this.sendRequest('ui/setSize', params);
685
+ return this.sendNotification('ui/notifications/size-changed', params);
635
686
  },
636
687
  requestClose: function(context) {
637
688
  return this.sendRequest('ui/close', {});
@@ -1054,7 +1105,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
1054
1105
 
1055
1106
  // Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
1056
1107
  // Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
1057
- // ext-apps sends ui/setSize, OpenAI forwards to its SDK when available.
1108
+ // ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
1058
1109
  FrontMcpBridge.prototype.setSize = function(size) {
1059
1110
  if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1060
1111
  if (!this._adapter.setSize) return Promise.resolve();
@@ -1156,7 +1207,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
1156
1207
  target.classList.add('opacity-50', 'cursor-not-allowed');
1157
1208
 
1158
1209
  // Add spinner for buttons
1159
- 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>';
1210
+ 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>';
1160
1211
  if (target.tagName === 'BUTTON') {
1161
1212
  target.innerHTML = spinner + 'Loading...';
1162
1213
  }
@@ -1237,7 +1288,22 @@ function minifyJS(code) {
1237
1288
  if (code.length > MAX_MINIFY_CODE_LENGTH) {
1238
1289
  return code;
1239
1290
  }
1240
- return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1").replace(/\s+/g, " ").replace(/\s*([{};,:()[\]])\s*/g, "$1").replace(/;\}/g, "}").trim();
1291
+ const out = [];
1292
+ let inBlockComment = false;
1293
+ for (const raw of code.split("\n")) {
1294
+ const line = raw.trim();
1295
+ if (inBlockComment) {
1296
+ if (line.includes("*/")) inBlockComment = false;
1297
+ continue;
1298
+ }
1299
+ if (line === "" || line.startsWith("//")) continue;
1300
+ if (line.startsWith("/*")) {
1301
+ if (!line.includes("*/")) inBlockComment = true;
1302
+ continue;
1303
+ }
1304
+ out.push(line);
1305
+ }
1306
+ return out.join("\n");
1241
1307
  }
1242
1308
  function generatePlatformBundle(platform, options = {}) {
1243
1309
  const platformAdapters = {
@@ -1092,12 +1092,38 @@ function __applyHostTheme(theme) {
1092
1092
  } catch (e) {}
1093
1093
  }
1094
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
+
1095
1111
  function readInjectedData() {
1096
1112
  var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
1097
1113
  if (typeof window !== 'undefined') {
1098
1114
  if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
1099
1115
  if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
1100
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
+ }
1101
1127
  }
1102
1128
  return data;
1103
1129
  }
@@ -1146,7 +1172,24 @@ var OpenAIAdapter = {
1146
1172
  if (sdk.displayMode) {
1147
1173
  context.hostContext.displayMode = sdk.displayMode;
1148
1174
  }
1149
- // Note: OpenAI SDK does not have an onContextChange equivalent
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
+ });
1150
1193
  return Promise.resolve();
1151
1194
  },
1152
1195
  callTool: function(context, name, args) {
@@ -1293,9 +1336,10 @@ var ExtAppsAdapter = {
1293
1336
  window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
1294
1337
  break;
1295
1338
  case 'ui/notifications/tool-result':
1296
- context.toolOutput = params.content;
1339
+ var resultOutput = extractToolOutput(params);
1340
+ context.toolOutput = resultOutput;
1297
1341
  context.structuredContent = params.structuredContent;
1298
- context.notifyToolResult(params.content);
1342
+ context.notifyToolResult(resultOutput);
1299
1343
  window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
1300
1344
  break;
1301
1345
  case 'ui/notifications/host-context-changed':
@@ -1349,6 +1393,15 @@ var ExtAppsAdapter = {
1349
1393
  window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
1350
1394
  });
1351
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
+ },
1352
1405
  sendHandshake: function(context) {
1353
1406
  // Send ui/initialize using '*' as target origin since TOFU hasn't
1354
1407
  // been established yet. The response from the host will establish
@@ -1414,7 +1467,7 @@ var ExtAppsAdapter = {
1414
1467
  var pending = this.pendingSize;
1415
1468
  if (!pending) return;
1416
1469
  this.pendingSize = null;
1417
- this.sendRequest('ui/setSize', pending.params).then(function(result) {
1470
+ this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
1418
1471
  for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
1419
1472
  }, function(err) {
1420
1473
  for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
@@ -1441,13 +1494,11 @@ var ExtAppsAdapter = {
1441
1494
  return this.sendRequest('ui/setDisplayMode', { mode: mode });
1442
1495
  },
1443
1496
  setSize: function(context, size) {
1444
- // FrontMCP sizing channel \u2014 parallels ui/setDisplayMode. Reports the
1445
- // measured/desired widget dimensions to the host.
1446
- var params = {
1447
- height: size && size.height,
1448
- width: size && size.width,
1449
- aspectRatio: size && size.aspectRatio
1450
- };
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;
1451
1502
  if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
1452
1503
  // No host origin yet: keep only the latest size and send it when the
1453
1504
  // handshake settles; every caller's promise follows that one request.
@@ -1458,7 +1509,7 @@ var ExtAppsAdapter = {
1458
1509
  self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
1459
1510
  });
1460
1511
  }
1461
- return this.sendRequest('ui/setSize', params);
1512
+ return this.sendNotification('ui/notifications/size-changed', params);
1462
1513
  },
1463
1514
  requestClose: function(context) {
1464
1515
  return this.sendRequest('ui/close', {});
@@ -1881,7 +1932,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
1881
1932
 
1882
1933
  // Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
1883
1934
  // Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
1884
- // ext-apps sends ui/setSize, OpenAI forwards to its SDK when available.
1935
+ // ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
1885
1936
  FrontMcpBridge.prototype.setSize = function(size) {
1886
1937
  if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1887
1938
  if (!this._adapter.setSize) return Promise.resolve();
@@ -1983,7 +2034,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
1983
2034
  target.classList.add('opacity-50', 'cursor-not-allowed');
1984
2035
 
1985
2036
  // Add spinner for buttons
1986
- 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>';
1987
2038
  if (target.tagName === 'BUTTON') {
1988
2039
  target.innerHTML = spinner + 'Loading...';
1989
2040
  }
@@ -2064,7 +2115,22 @@ function minifyJS(code) {
2064
2115
  if (code.length > MAX_MINIFY_CODE_LENGTH) {
2065
2116
  return code;
2066
2117
  }
2067
- return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1").replace(/\s+/g, " ").replace(/\s*([{};,:()[\]])\s*/g, "$1").replace(/;\}/g, "}").trim();
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");
2068
2134
  }
2069
2135
  function generatePlatformBundle(platform, options = {}) {
2070
2136
  const platformAdapters = {